From dcde8c8d66ca907436289ebba9f2ae1fdad82791 Mon Sep 17 00:00:00 2001 From: goon-foss Date: Thu, 15 Oct 2026 14:17:52 +0200 Subject: [PATCH] chore(scripts): scalanie tych samych odcinkow hentaimama i xanimeporn Co-Authored-By: Claude Opus 5.5 --- scripts/merge_hentai_episode_dupes.py | 79 +++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 scripts/merge_hentai_episode_dupes.py diff --git a/scripts/merge_hentai_episode_dupes.py b/scripts/merge_hentai_episode_dupes.py new file mode 100644 index 0000000..2ae27a5 --- /dev/null +++ b/scripts/merge_hentai_episode_dupes.py @@ -0,0 +1,79 @@ +"""Scala te same odcinki hentai z hentaimama i xanimeporn (2026-10-15). + +Tytuly hentaimama mialy do 15.10 ogon "- Watch Online Free in HD - Hentaimama", a xanime +dokleja "[Sub-ENG]", wiec ten sam odcinek byl dwiema scenami. Para = scena tylko z +hentaimama i scena tylko z xanimeporn o tym samym title_normalized po zdjeciu +"sub eng"/"raw"/"uncensored" z konca. Scalamy wylacznie pary jeden do jednego +(scena hentaimama zostaje, ma dlugosc i miniature). + + python scripts/merge_hentai_episode_dupes.py # podglad + python scripts/merge_hentai_episode_dupes.py --apply +""" +from __future__ import annotations + +import argparse +import logging +from collections import defaultdict + +import sqlalchemy as sa + +from app.db import session_scope +from app.resolve.scene_merge import merge_scenes + +log = logging.getLogger(__name__) + +_PAIRS_SQL = """ +with x as ( + select s.id, regexp_replace(s.title_normalized, '\\s+(sub eng|eng sub|english sub|raw|uncensored)$', '') k + from scenes s + where exists (select 1 from playback_sources p where p.scene_id = s.id and p.origin = 'tube:xanimeporn') + and not exists (select 1 from playback_sources p where p.scene_id = s.id and p.origin = 'tube:hentaimama') +), h as ( + select s.id, s.title_normalized k + from scenes s + where exists (select 1 from playback_sources p where p.scene_id = s.id and p.origin = 'tube:hentaimama') + and not exists (select 1 from playback_sources p where p.scene_id = s.id and p.origin = 'tube:xanimeporn') +) +select h.id as keep_id, x.id as drop_id, h.k from x join h on h.k = x.k +""" + + +def run(apply: bool) -> dict[str, int]: + with session_scope() as s: + rows = s.execute(sa.text(_PAIRS_SQL)).all() + by_keep: dict = defaultdict(list) + by_drop: dict = defaultdict(list) + for keep_id, drop_id, _k in rows: + by_keep[keep_id].append(drop_id) + by_drop[drop_id].append(keep_id) + pairs = [(k, d, t) for k, d, t in rows if len(by_keep[k]) == 1 and len(by_drop[d]) == 1] + c = {"candidates": len(rows), "one_to_one": len(pairs), "merged": 0, "errors": 0} + for keep_id, drop_id, title in pairs[:5]: + print(f"{title!r}: keep {keep_id} <- {drop_id}") + if not apply: + return c + for keep_id, drop_id, _t in pairs: + try: + with session_scope() as s: + merge_scenes(s, keep_id=keep_id, drop_id=drop_id, resolved_by="merge_hentai_episode_dupes") + s.commit() + c["merged"] += 1 + except Exception as e: + c["errors"] += 1 + log.warning("merge %s <- %s padl: %s", keep_id, drop_id, e) + if c["merged"] % 200 == 0 and c["merged"]: + log.info("hentai dupes %s", c) + return c + + +def main() -> int: + logging.basicConfig(level=logging.INFO) + p = argparse.ArgumentParser() + p.add_argument("--apply", action="store_true") + args = p.parse_args() + print(run(args.apply)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())