summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node/indexer/title_parse.py
diff options
context:
space:
mode:
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.py31
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