From f8b1e801ef749ab21cb2cb72149629a646e4d053 Mon Sep 17 00:00:00 2001 From: jtrzupek Date: Mon, 8 Jun 2026 11:50:45 +0200 Subject: [PATCH] fix(api): collapse same-origin playback sources on scene detail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A merged scene often aggregates several uploads from ONE tube (re-encodes / 4K dups). bug-report aa79a995 "why 2 links, both porntrex?" = same scene std + 4K (porntrex 2591377 + 2593449 "...in 4K"). In the UI these are indistinguishable links to one hoster (same extractor). Keep one best per origin: prefer duration matching the scene → any duration → first (origin-asc stable). Dead already filtered. Co-Authored-By: Claude Opus 4.8 --- app/api/scenes.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/app/api/scenes.py b/app/api/scenes.py index 6c025fb..67670f5 100644 --- a/app/api/scenes.py +++ b/app/api/scenes.py @@ -709,6 +709,27 @@ def _build_scene_out(session: Session, scene: Scene) -> SceneOut: .scalars() .all() ) + # Collapse źródła dzielące ten sam origin (hoster). Zmergowana scena często agreguje + # kilka uploadów z JEDNEGO tube'a (re-enkody / wersje 4K: bug-report aa79a995 "2 linki, + # oba do porntrex" = ta sama scena std+4K) — w UI to nierozróżnialne linki do tego + # samego hostera (resolvują tym samym extractorem). Zostawiamy jeden najlepszy per + # origin: preferuj długość zgodną ze sceną (realny match) → jakąkolwiek długość → + # pierwszy (stabilnie, query jest origin-asc). Martwe już odfiltrowane (dead_at). + def _origin_pick_key(p: PlaybackSource) -> tuple[int, int]: + dur_match = ( + 0 if (scene.duration_sec and p.duration_sec + and abs(p.duration_sec - scene.duration_sec) <= 5) else 1 + ) + return (dur_match, 0 if p.duration_sec else 1) + + _best_by_origin: dict[str, PlaybackSource] = {} + for p in playback_rows: + key = p.origin or "" + cur = _best_by_origin.get(key) + if cur is None or _origin_pick_key(p) < _origin_pick_key(cur): + _best_by_origin[key] = p + playback_rows = list(_best_by_origin.values()) + playback_out: list[PlaybackSourceOut] = [] for p in playback_rows: out = PlaybackSourceOut.model_validate(p)