diff --git a/app/scheduler/bulk_dedup.py b/app/scheduler/bulk_dedup.py index bf7f3c9..7ea3b34 100644 --- a/app/scheduler/bulk_dedup.py +++ b/app/scheduler/bulk_dedup.py @@ -24,6 +24,7 @@ from sqlalchemy.orm import Session from app.config import get_settings from app.db import session_scope +from app.scheduler.phash_blacklist import DEFAULT_MIN_SCENES from app.models.merge_candidate import MergeCandidate, MergeKind, MergeStatus from app.models.scene import Scene, SceneFingerprint, ScenePerformer from app.resolve.scene_merge import merge_scenes @@ -187,13 +188,37 @@ def _pairs_exact_phash(session: Session) -> Iterable[tuple[uuid.UUID, uuid.UUID] tylko ≥auto_t, więc przypadkowe kolizje z innym performerem/duration wpadną w review). Skalowalne dla setek tys. phashy gdzie Hamming-fuzzy O(N²) jest nierealne.""" from sqlalchemy import text + + # Wartości ZDEGENEROWANE (zaślepka, czarna klatka, intro studia) odsiewamy TUTAJ, + # nie tylko przez `phash_blacklist`. Dwa powody: + # + # 1. Blacklista jest odświeżana raz na dobę, więc świeżo wpisana zaślepka ma + # całą dobę okna. Do tej pory to nie bolało, ale backfill z sierpnia 2026 + # dokłada ~240 tys. odcisków z xvideos/xnxx/epornera, a te serwują zaślepki + # przy usuniętych filmach. Wystarczy jedna taka wartość, żeby wpuścić do + # potoku tysiące scen, których nic nie łączy poza obrazkiem. + # 2. To samozłączenie jest KWADRATOWE po liczbie scen dzielących wartość: + # zaślepka przy 5 tys. scen to 12,5 mln par, czyli job pada na pamięci, + # zanim cokolwiek zdąży ją zablacklistować. + # + # Próg jest ten sam, co w `phash_blacklist` (≥10 scen = nie identyfikuje sceny), + # więc obie ścieżki mówią to samo, tylko ta działa natychmiast. rows = session.execute(text(""" + WITH degenerate AS ( + SELECT value + FROM scene_fingerprints + WHERE kind = 'phash' + GROUP BY value + HAVING count(DISTINCT scene_id) >= :min_scenes + ) SELECT DISTINCT a.scene_id AS sa, b.scene_id AS sb FROM scene_fingerprints a JOIN scene_fingerprints b ON a.value = b.value AND a.scene_id < b.scene_id WHERE a.kind = 'phash' AND b.kind = 'phash' - """)) + AND NOT EXISTS (SELECT 1 FROM degenerate d WHERE d.value = a.value) + AND NOT EXISTS (SELECT 1 FROM phash_blacklist b2 WHERE b2.value = a.value) + """), {"min_scenes": DEFAULT_MIN_SCENES}) for sa, sb in rows: yield (sa, sb)