"""One-shot: re-extract titles dla freshporno scen z pre-fix truncation bug. Tło: `meta_content` regex sprzed 2026-05-20 obcinał title na pierwszym apostrofie (`` → `She`). Fix wszedł 2026-05-20, ale scenes scrapped przed fixem mają broken titles w DB. Delta-ingest skipuje je przez external_id match — bez backfill nigdy się nie naprawią. Bug-report `2fbf1c73` 2026-05-23 (kontekstowo, brak BE scen): część brakujących Brazzers Exxtra scen to faktycznie pre-fix victims które nie zmergowały z canonical TPDB record bo title się nie zgadzał. Heurystyka: - origin = tube:freshpornoorg - created_at < 2026-05-20 (pre-fix) - title length < 15 - slug freshporno URL ma więcej tokenów niż title (sygnał obcięcia) Idempotent: po update tylko jeśli nowy title różni się od bieżącego. """ from __future__ import annotations import logging import re from datetime import UTC, datetime import httpx from sqlalchemy import select from app.connectors.direct_scrapers._browse_base import meta_content from app.db import session_scope from app.models import Scene from app.models.playback_source import PlaybackSource from app.normalize.text import normalize, slugify log = logging.getLogger(__name__) CUTOFF_DATE = datetime(2026, 5, 20, tzinfo=UTC) TITLE_MAX_LEN = 15 USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/140.0.0.0" def _slug_token_count(url: str) -> int: """Liczy ile tokenów ma URL slug (np. `/videos/girls-night-gets-girth/` → 4).""" m = re.search(r"/videos/([^/]+)/?", url) if not m: return 0 return sum(1 for tok in m.group(1).split("-") if tok and tok != "s") def main() -> int: logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") with session_scope() as session: rows = session.execute( select(Scene.id, Scene.title, PlaybackSource.page_url) .join(PlaybackSource, PlaybackSource.scene_id == Scene.id) .where(PlaybackSource.origin == "tube:freshpornoorg") .where(Scene.created_at < CUTOFF_DATE) ).all() log.info("pre-fix freshporno scenes: %d", len(rows)) # Filter: krótki title + slug ma więcej tokenów niż title (sygnał obcięcia) candidates = [] for scene_id, title, page_url in rows: if title is None: continue if len(title) >= TITLE_MAX_LEN: continue title_tokens = len([t for t in title.split() if t]) slug_tokens = _slug_token_count(page_url) if slug_tokens <= title_tokens: continue # title już ma tyle samo/więcej tokenów co slug — pewnie legit krótki candidates.append((scene_id, title, page_url)) log.info("candidates with slug>>title heurystyka: %d", len(candidates)) client = httpx.Client( timeout=15.0, follow_redirects=True, headers={"User-Agent": USER_AGENT}, ) updated = 0 skipped = 0 errors = 0 for scene_id, old_title, page_url in candidates: try: r = client.get(page_url) if r.status_code != 200: errors += 1 continue new_title = meta_content(r.text, property="og:title") if not new_title: m = re.search(r"