#!/usr/bin/env python3 """ parse_release.py — Test ParsedRelease interactively or via CLI args. Usage: uv run testing/parse_release.py "Oz.S03.1080p.WEBRip.x265-KONTRAST" uv run testing/parse_release.py "Oz.S03.1080p.WEBRip.x265-KONTRAST" --tmdb uv run testing/parse_release.py "Inception.2010.1080p.BluRay.x265-GROUP" --tmdb-title "Inception" --tmdb-year 2010 uv run testing/parse_release.py --interactive """ import argparse import sys from pathlib import Path _PROJECT_ROOT = Path(__file__).resolve().parents[1] if str(_PROJECT_ROOT) not in sys.path: sys.path.insert(0, str(_PROJECT_ROOT)) # --------------------------------------------------------------------------- # Colours # --------------------------------------------------------------------------- RESET = "\033[0m" BOLD = "\033[1m" DIM = "\033[2m" GREEN = "\033[32m" YELLOW = "\033[33m" RED = "\033[31m" CYAN = "\033[36m" BLUE = "\033[34m" USE_COLOR = True def c(text: str, *codes: str) -> str: if not USE_COLOR: return str(text) return "".join(codes) + str(text) + RESET def kv(key: str, val: str, color: str = CYAN) -> None: print(f" {c(key + ':', BOLD)} {c(val, color)}") def hr() -> None: print(c("─" * 64, DIM)) # --------------------------------------------------------------------------- # TMDB lookup # --------------------------------------------------------------------------- def _fetch_tmdb(title: str) -> tuple[str | None, int | None]: """ Call TMDBClient.search_media() and return (canonical_title, year). Returns (None, None) on failure. """ try: from alfred.infrastructure.api.tmdb import TMDBClient client = TMDBClient() result = client.search_media(title) year: int | None = None if result.release_date: try: year = int(result.release_date[:4]) except (ValueError, IndexError): pass print( c( f" TMDB → {result.title} ({year}) [{result.media_type}] imdb={result.imdb_id}", DIM, ) ) return result.title, year except Exception as e: print(c(f" TMDB lookup failed: {e}", YELLOW)) return None, None # --------------------------------------------------------------------------- # Display # --------------------------------------------------------------------------- def _show( release_name: str, tmdb_title: str | None, tmdb_year: int | None, tmdb_episode_title: str | None, ext: str, ) -> None: from alfred.domain.release import parse_release p = parse_release(release_name) # Auto-fetch TMDB if requested and not already provided if not (tmdb_title and tmdb_year): fetched_title, fetched_year = _fetch_tmdb(p.title.replace(".", " ")) tmdb_title = tmdb_title or fetched_title tmdb_year = tmdb_year or fetched_year print() print(c("━" * 64, BOLD)) print(c(f" ParsedRelease — {p.raw}", BOLD, CYAN)) print(c("━" * 64, BOLD)) # Core fields hr() kv("raw", p.raw) kv("normalised", p.normalised) kv("title", p.title) kv("year", str(p.year) if p.year else c("None", DIM)) kv("season", str(p.season) if p.season is not None else c("None", DIM)) kv("episode", str(p.episode) if p.episode is not None else c("None", DIM)) kv( "episode_end", str(p.episode_end) if p.episode_end is not None else c("None", DIM), ) kv("quality", p.quality or c("None", DIM)) kv("source", p.source or c("None", DIM)) kv("codec", p.codec or c("None", DIM)) kv("group", p.group, YELLOW if p.group == "UNKNOWN" else GREEN) kv("tech_string", p.tech_string or c("(empty)", DIM)) # Derived booleans hr() kv("is_movie", c(str(p.is_movie), GREEN if p.is_movie else DIM)) kv("is_season_pack", c(str(p.is_season_pack), GREEN if p.is_season_pack else DIM)) # Generated names hr() title_for_names = tmdb_title or p.title.replace(".", " ") year_for_names = tmdb_year or p.year or 0 if p.is_movie: kv("movie_folder_name", p.movie_folder_name(title_for_names, year_for_names)) kv("movie_filename", p.movie_filename(title_for_names, year_for_names, ext)) else: kv("show_folder_name", p.show_folder_name(title_for_names, year_for_names)) kv("season_folder_name", p.season_folder_name()) if not p.is_season_pack: kv("episode_filename", p.episode_filename(tmdb_episode_title, ext)) else: kv("episode_filename", c("(season pack — no episode filename)", DIM)) if tmdb_title or tmdb_year or tmdb_episode_title: hr() print(c(" TMDB data used:", DIM)) if tmdb_title: kv(" tmdb_title", tmdb_title) if tmdb_year: kv(" tmdb_year", str(tmdb_year)) if tmdb_episode_title: kv(" tmdb_episode_title", tmdb_episode_title) print(c("━" * 64, BOLD)) print() # --------------------------------------------------------------------------- # Interactive mode # --------------------------------------------------------------------------- def _interactive() -> None: print(c("\n Alfred — Release Parser REPL", BOLD, CYAN)) print(c(" Type a release name, or 'q' to quit.", DIM)) print( c( " Inline overrides: ::title=Oz ::year=1997 ::ep=The.Routine ::ext=.mkv\n", DIM, ) ) while True: try: raw = input(c(" release> ", BOLD)).strip() except (EOFError, KeyboardInterrupt): print() break if not raw or raw.lower() in ("q", "quit", "exit"): break # Parse inline overrides: "Oz.S03E01... ::title=Oz ::year=1997 ::tmdb" parts = raw.split("::") release = parts[0].strip() overrides: dict[str, str] = {} for part in parts[1:]: part = part.strip() if "=" in part: k, _, v = part.partition("=") overrides[k.strip()] = v.strip() else: overrides[part] = "1" # flag-style: ::tmdb tmdb_title = overrides.get("title") tmdb_year = int(overrides["year"]) if "year" in overrides else None tmdb_episode_title = overrides.get("ep") ext = overrides.get("ext", ".mkv") try: _show(release, tmdb_title, tmdb_year, tmdb_episode_title, ext) except Exception as e: print(c(f" Error: {e}", RED)) # --------------------------------------------------------------------------- # CLI # --------------------------------------------------------------------------- def main() -> None: global USE_COLOR parser = argparse.ArgumentParser( description="Test ParsedRelease from domain/release/release_parser.py", formatter_class=argparse.RawDescriptionHelpFormatter, ) parser.add_argument("release", nargs="?", help="Release name to parse") parser.add_argument( "-i", "--interactive", action="store_true", help="Interactive REPL mode" ) parser.add_argument( "--tmdb-title", metavar="TITLE", help="Override TMDB title for name generation" ) parser.add_argument( "--tmdb-year", metavar="YEAR", type=int, help="Override TMDB year for name generation", ) parser.add_argument( "--episode-title", metavar="TITLE", help="TMDB episode title for episode_filename()", ) parser.add_argument( "--ext", default=".mkv", metavar="EXT", help="File extension for filename generation (default: .mkv)", ) parser.add_argument("--no-color", action="store_true") args = parser.parse_args() if args.no_color or not sys.stdout.isatty(): USE_COLOR = False if args.interactive: _interactive() return if not args.release: parser.print_help() sys.exit(1) try: _show( args.release, args.tmdb_title, args.tmdb_year, args.episode_title, args.ext ) except Exception as e: print(c(f"Error: {e}", RED), file=sys.stderr) sys.exit(1) if __name__ == "__main__": main()