Detectare duplicate: Q&A 18.08.2026 este acelasi video cu 11.08.2026
audio_fingerprint.py - amprenta pe anvelopa de energie, fara transcriere. md5 nu prinde duplicatele (urcari encodate separat, durate la 0.08s distanta). Validat: 1.000 pe perechea duplicat, 0.038 pe sesiuni diferite. check_duplicates.py - acelasi lucru pe transcrieri, cu --selftest. Scanat tot modulul Q&A: un singur duplicat. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MtSTyTmt6AbCL9j5ajEDm1
This commit is contained in:
62
check_duplicates.py
Normal file
62
check_duplicates.py
Normal file
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
Găsește sesiuni Q&A duplicate (același videoclip urcat sub două date).
|
||||
Compară transcrierile la nivel de cuvinte normalizate (fără diacritice, fără
|
||||
punctuație), pentru că whisper produce text ușor diferit la două rulări pe
|
||||
aceeași sursă — un md5 pe audio nu le prinde.
|
||||
|
||||
Prag: >60% cuvinte potrivite ȘI un bloc comun de >=40 de cuvinte consecutive.
|
||||
Blocul lung e cel care contează: două sesiuni diferite pe aceleași subiecte
|
||||
împart multe cuvinte, dar niciodată 40 la rând.
|
||||
"""
|
||||
import difflib
|
||||
import re
|
||||
import sys
|
||||
import unicodedata
|
||||
from itertools import combinations
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def words(path: Path) -> list[str]:
|
||||
t = unicodedata.normalize("NFKD", path.read_text(encoding="utf-8").lower())
|
||||
return re.findall(r"[a-z]+", t.encode("ascii", "ignore").decode())
|
||||
|
||||
|
||||
def is_duplicate(a: list[str], b: list[str]) -> tuple[bool, float, int]:
|
||||
if not a or not b or min(len(a), len(b)) / max(len(a), len(b)) < 0.7:
|
||||
return False, 0.0, 0 # lungimi prea diferite ca să fie același video
|
||||
blocks = difflib.SequenceMatcher(None, a, b, autojunk=False).get_matching_blocks()
|
||||
pct = 200 * sum(bl.size for bl in blocks) / (len(a) + len(b))
|
||||
longest = max(bl.size for bl in blocks)
|
||||
return pct > 60 and longest >= 40, pct, longest
|
||||
|
||||
|
||||
def selftest():
|
||||
base = "cand pretul ajunge in suport major si apare un buy doji intri cu toata pozitia".split() * 12
|
||||
same = base[:-3] + "intri cu jumatate".split() # mici diferente de transcriere
|
||||
other = "volumul arata cine domina piata in fiecare moment al zilei".split() * 12
|
||||
assert is_duplicate(base, same)[0], "acelasi video nu a fost detectat"
|
||||
assert not is_duplicate(base, other)[0], "sesiuni diferite marcate ca duplicat"
|
||||
assert not is_duplicate(base, [])[0], "transcriere goala (inca se scrie) trebuie ignorata"
|
||||
assert not is_duplicate(base, base[:20])[0], "lungimi foarte diferite trebuie ignorate"
|
||||
print("selftest ok")
|
||||
|
||||
|
||||
def main():
|
||||
if "--selftest" in sys.argv:
|
||||
selftest()
|
||||
return 0
|
||||
d = Path(sys.argv[1] if len(sys.argv) > 1 else "transcripts/09_Q_and_A")
|
||||
docs = {p.stem: words(p) for p in sorted(d.glob("*.txt"))}
|
||||
found = []
|
||||
for x, y in combinations(docs, 2):
|
||||
dup, pct, longest = is_duplicate(docs[x], docs[y])
|
||||
if dup:
|
||||
found.append((x, y))
|
||||
print(f"DUPLICAT: {x} == {y} ({pct:.1f}% potrivite, bloc comun {longest} cuvinte)")
|
||||
if not found:
|
||||
print(f"Niciun duplicat între cele {len(docs)} transcrieri.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user