diff options
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/indexer/title_parse.py')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/indexer/title_parse.py | 31 |
1 files changed, 16 insertions, 15 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/indexer/title_parse.py b/packages/meshbay-node/src/meshbay_node/indexer/title_parse.py index beb0d3f..14728ff 100644 --- a/packages/meshbay-node/src/meshbay_node/indexer/title_parse.py +++ b/packages/meshbay-node/src/meshbay_node/indexer/title_parse.py @@ -1,18 +1,18 @@ """ Filename -> title/year/season/episode parsing for the Videos group app. -Wraps `guessit` and layers the fixes from docs/mediacenter.md §3.3/§3.4 on +Wraps `guessit` and layers the fixes from docs/MESHBAY_DESIGN.md §9.7 on top of it: none of them are per-title hacks, each is a generic rule found by validating guessit's raw output against real TMDB search results over a ~1950-file library (movies, TV shows, and a small franchise set). -Scope is deliberately narrow (§3.5): title, year, season, episode. Technical +Scope is deliberately narrow: title, year, season, episode. Technical facts (resolution, codec, duration) come from ffprobe, never the filename — a mislabeled `1080p` tag is a real, observed failure mode. This module never touches the filesystem or the network. The orchestration that decides *which* file supplies a show's title (a representative episode -filename, not the folder name — §3.4) lives in the indexer, which has the +filename, not the folder name) lives in the indexer, which has the directory listing; this module only parses strings it's handed. """ @@ -91,7 +91,7 @@ _YEAR_RE = re.compile(r"(?<!\d)(?:19|20)\d{2}(?!\d)") def year_in(text: str) -> int | None: """First 19xx/20xx in `text`, or None — used to lift a year off a show - folder name ("Some.Show.2022.S01") for the search fallback (§10.1/V8).""" + folder name ("Some.Show.2022.S01") for the search fallback (V8).""" m = _YEAR_RE.search(text or "") return int(m.group(0)) if m else None @@ -102,7 +102,7 @@ def clean_query(s: str) -> str: parenthesized-year stripping `naive_title` does. `naive_title` assumes a real filename; a show's `display_title` is a folder basename ("Some.Show.Name" — `rsplit('.', 1)` would eat ".Name"), so it needs a - gentler normaliser (§10.1/V8). + gentler normaliser (V8). """ s = re.sub(r"[._-]+", " ", s or "") s = _strip_editions(s) @@ -115,7 +115,7 @@ def _strip_editions(title: str) -> str: def naive_title(filename: str) -> str: """ - The mandated fallback (§3.6, §4.1): strip the extension, replace every + The mandated fallback: strip the extension, replace every `.`/`_`/`-` with a space, drop a trailing parenthesized year, collapse whitespace. Always computable, never fails, used both as the flat-mode display name of last resort and as a second TMDB query candidate. @@ -150,14 +150,14 @@ def sequel_variants(title: str) -> list[str]: A trailing sequel index often has no exact match in the real TMDB title: the file has a digit where TMDB uses a Roman numeral (or the reverse), spells the number out, or wraps it as "Part N" / "Chapitre N" - (§3.3 row 4, §10.1/V10). Returns extra candidate titles to try — the + (V10). Returns extra candidate titles to try — the index re-rendered as digit and as Roman numeral, plus (only when there is no "Part"/"Episode"/… keyword) the bare base. The bare base is withheld for a keyword'd index — "<Saga> Chapter III" → "<Saga>" — because a franchise's bare name is very often a real, *different* film (the series' first entry), and that variant matched - every later entry to it (§10.1/V14). Without the keyword ("<Franchise> + every later entry to it (V14). Without the keyword ("<Franchise> 3") the number is decoration and the bare base is the right thing to try. """ @@ -190,7 +190,7 @@ def sequel_variants(title: str) -> list[str]: def season_from_folder_name(name: str) -> int | None: """ - §3.4: a season-like ancestor folder, vocabulary-driven rather than + A season-like ancestor folder, vocabulary-driven rather than assuming a numeric convention everywhere. A specials/bonus/extras folder maps to season 0 (matching TMDB's own `season_number: 0`). Returns None if `name` doesn't look like a season folder at all. @@ -212,7 +212,8 @@ def season_from_folder_name(name: str) -> int | None: @dataclass class ParsedName: display_title: str | None # None => caller must supply from elsewhere (e.g. a sibling file) - alt_title: str | None = None # guessit's alternative_title, a second query candidate (§3.3 row 1) + alt_title: str | None = None # guessit's alternative_title, a second + # query candidate naive_title: str = "" # always available, fully punctuation-normalized fallback year: int | None = None season: int | None = None @@ -249,7 +250,7 @@ def parse_movie_filename(filename: str) -> ParsedName: -# ── Music app (docs/musicbay.md §2.1) ──────────────────────────────────────── +# ── Music app (docs/MESHBAY_DESIGN.md §9.8) ──────────────────────────────────────── # # Filename parsing is the *fallback* here, not the primary source (unlike # Videos, where guessit does all the work): embedded ID3/Vorbis tags are read @@ -307,7 +308,7 @@ def strip_track_prefix(text: str) -> str: # "Season 1"/"Saison 1". guessit will also invent a season+episode from a # bare 3-4 digit run ("1080p" truncated to "108" -> S01E08; "1280" -> # S12E80), which is how a plain movie ends up shelved as a series -# (§10.1/V14). The indexer uses this to tell a real flat-library episode +# (V14). The indexer uses this to tell a real flat-library episode # from that hallucination. _EPISODE_MARKER_RE = re.compile( r"s\d{1,2}[\s._-]*e\d{1,3}" @@ -326,9 +327,9 @@ def has_episode_marker(filename: str) -> bool: def parse_episode_filename(filename: str) -> ParsedName: """ Parse an episode filename. `display_title` may come back None (e.g. - `S08E02.SUBFRENCH.720p.mkv` carries no show name at all, §3.2) — the + `S08E02.SUBFRENCH.720p.mkv` carries no show name at all) — the indexer then supplies the show title from a representative sibling - filename in the same folder rather than the folder name itself (§3.4). + filename in the same folder rather than the folder name itself. """ g = guessit(filename) title = g.get("title") @@ -351,7 +352,7 @@ def parse_episode_filename(filename: str) -> ParsedName: ) -# A bare leading episode number, no show name attached (§3.4c) — the same +# A bare leading episode number, no show name attached — the same # shape as music's _TRACK_PREFIX_RE, capped at 3 digits for the same reason: # a leading year ("2010 - Episode.mkv") is 4 digits and must not match. # guessit's own `episode` is not a substitute here: given exactly 3 digits it |