Audyt duplikatow po phashu miniatur wykazal dwa rozne zjawiska, ktore wygladaly jak jedno. 1. Zdegenerowane phashe. 73 wartosci wystepowaly przy >=10 scenach kazda, lacznie przy 4375 scenach; rekordzistka byla dzielona przez 892 sceny o 892 roznych tytulach i 1886 roznych performerach. To zaslepki i czarne klatki, nie odciski scen. find_by_phash_within bierze najblizsza wartosc z calej tabeli, wiec taka zaslepka zawsze wygrywala z prawdziwym duplikatem (dist 0). Do tej pory bronila nas bramka dur_prox i nic sie nie skleilo, ale to zabezpieczenie drugiej linii. Blacklista jest tabela, nie jednorazowym DELETE, bo sam DELETE nic nie daje: zaslepka wraca przy kolejnym ingescie. Trzeba pamietac, ze wartosc jest bezuzyteczna. Job co 24h dopisuje nowe i czysci odciski. Po czyszczeniu zero grup >=10, najwieksza pozostala ma 9. 2. Realne duplikaty. 4647 scalonych. Przyczyna byla jedna: 97 procent par ma perverzije po dokladnie jednej stronie, bo pisze tytuly z prefiksem studia i performera, a reszta tubow daje goly tytul. Wbrew mojej pierwszej diagnozie NIE trzeba tu ruszac scoringu tytulu (token_set_ratio i tak radzi sobie z prefiksem, a sciezka phash idzie przed composite): 80 procent par to dlug sprzed 60+ dni, a biezacy wyciek to okolo 1 dziennie. Nadmiarowe wiersze: 18452 na 9449. merge_phash_exact_dupes.py dostal wykluczenie blacklisty. Bez tego byl grozny: sam klaster 892 scen dawal ~397 tys. par do rozwazenia. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
65 lines
2.6 KiB
Python
65 lines
2.6 KiB
Python
"""Wykrywanie i czyszczenie zdegenerowanych phashy.
|
|
|
|
Phash identyfikuje scenę tylko wtedy, gdy miniaturka pokazuje treść sceny. Gdy tube
|
|
serwuje zaślepkę, czarną klatkę albo wspólne intro studia, ta sama wartość ląduje
|
|
przy setkach niepowiązanych scen i przestaje cokolwiek znaczyć — a `find_by_phash_within`
|
|
bierze najbliższą wartość z CAŁEJ tabeli, więc taki wpis jest czystym szumem.
|
|
|
|
Próg `min_scenes=10` wynika z rozkładu zmierzonego 2026-07-27: 11 000 wartości
|
|
występowało przy dokładnie 2 scenach (to realne duplikaty, chcemy je zachować),
|
|
1346 przy 3-9, a dopiero od 10 w górę zaczynają się grupy, w których liczba różnych
|
|
tytułów równa się liczbie scen — czyli nic ich nie łączy poza obrazkiem.
|
|
|
|
Kolejność ma znaczenie: najpierw `refresh` (zapisz wartości na stałe), potem `purge`
|
|
(skasuj wiersze). Odwrotnie skasowalibyśmy dowody i blacklista wyszłaby pusta.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from sqlalchemy import text
|
|
from sqlalchemy.orm import Session
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
DEFAULT_MIN_SCENES = 10
|
|
|
|
_REFRESH_SQL = """
|
|
INSERT INTO phash_blacklist (value, scene_count)
|
|
SELECT value, count(DISTINCT scene_id)
|
|
FROM scene_fingerprints
|
|
WHERE kind = 'phash'
|
|
GROUP BY value
|
|
HAVING count(DISTINCT scene_id) >= :min_scenes
|
|
ON CONFLICT (value) DO UPDATE SET scene_count = EXCLUDED.scene_count
|
|
"""
|
|
|
|
_PURGE_SQL = """
|
|
DELETE FROM scene_fingerprints f
|
|
USING phash_blacklist b
|
|
WHERE f.kind = 'phash' AND f.value = b.value
|
|
"""
|
|
|
|
|
|
def refresh_phash_blacklist(session: Session, *, min_scenes: int = DEFAULT_MIN_SCENES) -> int:
|
|
"""Dopisz do blacklisty wartości dzielone przez ≥min_scenes scen. Zwraca rozmiar
|
|
blacklisty po odświeżeniu (wpisy raz dodane NIE znikają — o to chodzi)."""
|
|
session.execute(text(_REFRESH_SQL), {"min_scenes": min_scenes})
|
|
return int(session.execute(text("SELECT count(*) FROM phash_blacklist")).scalar_one())
|
|
|
|
|
|
def purge_blacklisted_fingerprints(session: Session) -> int:
|
|
"""Skasuj wiersze `scene_fingerprints` o zablacklistowanej wartości. Zwraca liczbę
|
|
skasowanych. Sceny zostają nietknięte — znika tylko bezużyteczny odcisk."""
|
|
return int(session.execute(text(_PURGE_SQL)).rowcount or 0)
|
|
|
|
|
|
def run_phash_blacklist(*, min_scenes: int = DEFAULT_MIN_SCENES) -> dict[str, int]:
|
|
from app.db import session_scope
|
|
|
|
with session_scope() as session:
|
|
size = refresh_phash_blacklist(session, min_scenes=min_scenes)
|
|
purged = purge_blacklisted_fingerprints(session)
|
|
session.commit()
|
|
log.info("phash blacklist: %d wartości na liście, skasowano %d odcisków", size, purged)
|
|
return {"blacklist_size": size, "purged": purged}
|