aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests/test_title_parse.py
Commit message (Collapse)AuthorAgeFilesLines
* style: ruff's own fixes, mechanically appliedChristophe Besson6 days1-1/+0
| | | | | | | | | | | | | | | | | | | | `ruff check .` had gone unrun long enough to report 568 errors, which is the same as having no linter: the next real finding would have been invisible in the noise. This is the 521 it fixes by itself, in 173 files, and nothing else — the 98 it cannot fix are the next commit. What actually changed: import sorting (225), imports nobody used (87, none of them a re-export — no `__init__.py` is touched, which was the one way this could have broken an import elsewhere), `datetime.timezone.utc` to `datetime.UTC` (69) and `asyncio.TimeoutError` to `TimeoutError` (18), both plain aliases on the 3.12 this project requires, `Optional[X]` to `X | None` (24), and f-strings with nothing to interpolate (19). Checked rather than assumed: every module in the three packages still imports, and the suite is 2893 passed — the same count, test for test, as the merge before it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: cite MESHBAY_DESIGN.md and a section instead of the merged notesChristophe Besson7 days1-6/+6
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The per-feature design notes were merged into docs/MESHBAY_DESIGN.md and deleted from the tree on 2026-09-11, but ~230 comments across the three packages still named them — usually written `docs/musicbay.md §3.2`, as though the file were still in docs/. A reader had to know §16 existed to resolve any of them. They now name the section directly. Every mapping comes from §16, the concordance, which already records where each old section landed: musicbay -> §9.8, mediacenter -> §9.7 for the Videos app and §6.5 where the subject is derived data, photos -> §9.9, auth-confirm -> §3.6, refactoring-search -> §9.11, invite-pairing-v1 -> §3.4, per-node-identity-v1 -> §3.2, captcha -> §7.7, chat-sender-keys -> §4.5, apps/refactor-groups -> §9.1–§9.4, desktop-client-v1 per section. Bare citations of the same documents (`draft-v6 §2.11`, `§4.8`, `§3.4`) are retargeted too: those collide with real section numbers in the design document, so leaving them would have been worse than the named form. Four cases the concordance does not cover, each decided rather than guessed: Sub-item references into documents that no longer exist — mediacenter's `§3.3 row 4`, `§3.4b/c`, `apps.md §3b` — name rows and sub-items §9.7 and §9.2 do not reproduce. The module-level citation stays; the sub-item pointer is dropped. The V-findings keep their labels but lose the dead `§10.1/` prefix. §13.8 lists V1–V13 as per-application open items, which is not what the labels mean in these comments, so pointing them at §13.8 would have been a false citation. `apps.md §5`'s virtualization requirement has no counterpart anywhere in the design document. The requirement is stated in the comment itself, so the citation is dropped rather than aimed at a section that does not say it. Comments that attributed a *sentence* to an old note — musicbay's "several thousand files" example, its "what got measured" note, its measured ~11%/~26% cover-art figures, the "original no root, whole shared tree" call — state the fact without attribution now. §9.8 does not contain those sentences and citing it for them would have been wrong. CLAUDE.md's "a reference to a document that no longer exists" row now says the concordance is for git history and out-of-tree material; the code cites sections directly. Verified: 2851 passed, 4 skipped. The 12 errors in the run are the Firefox leg of test_sticky_header.py's browser harness, which is broken at the browser level on this machine — headless Firefox (snap) dies with `[GFX1-]: RenderCompositorSWGL failed mapping default framebuffer`, renders nothing, and the probe exits `{"error": "no measurement"}` after its full 90s wait. Chrome runs the same 12 assertions in 3.2s and passes. Nothing here can affect it: every changed line in style.css is inside a comment. Also checked: ast.parse on every changed .py, `node --check` on every changed .js, the /* */ balance in style.css, and that no changed line exceeds the width its file already used. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore: scrub copyrighted names from tests, comments and docsChristophe Besson2026-08-301-6/+6
| | | | | | | | | | | | | | Real franchise / show / release-group names had crept back into test fixtures, code comments, a docstring and docs/mediacenter.md while fixing the saga-match and misclassification bugs. Replace them all with invented placeholders ("Some Saga", "A Different Show") and shape descriptions ("a franchise-origin film", "a 3-season show"). Behaviour and assertions unchanged; 738 node tests still pass. Record the rule in CLAUDE.md so it stops recurring. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BMLQjqFGCize2KtNBT79v
* fix(node): stop a numbered saga all matching its first filmChristophe Besson2026-08-301-3/+14
| | | | | | | | | | | | | | | | | | | | | Every "<Saga> Episode <N> - <subtitle>" file in a numbered franchise resolved to the series' first entry (a real, older film). `sequel_variants` stripped "Episode <N>" and offered the bare "<Saga>" as a candidate query; that matches the first film's original_title at ratio 1.0 and beat PASS 1's correct-but-lower hit. A franchise's bare name is very often a real, different film. When a Part/Episode/Chapitre/… keyword carried the index, sequel_variants no longer emits the bare base — only "<base> <digit>" and "<base> <roman>". Without a keyword ("<Franchise> 3") the bare base is still offered, so that fix is untouched. Verified live against TMDB: the franchise's episodes each resolve to their own entry; the earlier numbered-sequel, two-part-film and franchise-subtitle regressions all hold. docs/mediacenter.md §10.3. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BMLQjqFGCize2KtNBT79v
* fix(node): stop a movie with a mangled quality tag being shelved as a seriesChristophe Besson2026-08-291-0/+22
| | | | | | | | | | | | | | | | | | | | | | Found on demo35: "Some.Film.2017.MULTI.108.grp.mkv" — the release name's "1080p" truncated to "108" — makes guessit read S01E08, so enrich.py's flat-library branch (elif ep.episode is not None) filed a standalone film as a series. "Fix match" then only offered TV results for the phantom show, so there was no way out from the UI. - title_parse.has_episode_marker(): true only for an explicit SxxExx / 1x08 / "Episode N" / "Season N" token, not a bare 3-4 digit run. - enrich.py: the flat-library branch now needs ep.season AND ep.episode, plus either a real marker or the absence of a "(2019)"-style year. Every genuine flat-dumped episode in the corpus carries a marker, so real shows are untouched; the same misparse on "1280" ("...2013.1280...") is covered too. docs/mediacenter.md §10.2. Known gap left open: no operator control over the movie/show kind itself — a "this is a movie / a show" toggle would. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BMLQjqFGCize2KtNBT79v
* feat(node): V8–V11 — show-branch ladder, year-aware _best_match, wider ↵Christophe Besson2026-08-291-0/+44
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | sequel_variants V8: the TV/show branch of _tmdb_search used the old "first candidate over 0.6 wins" shape. It now shares one _tmdb_ladder helper with the movie branch — score every candidate query, keep the best, fast-path a confident primary hit. A year lifted off the show's folder name (title_parse.year_in, e.g. "Some.Show.2022.S01") rescues a sub-0.6 hit that lands on the exact year. title_parse.clean_query de-dots a folder-derived title without naive_title's extension-stripping trap. V9: _best_match gains an optional `year`. When the top result is not a confident textual hit (ratio < 0.6) and a year was requested, a different result of that exact release year is preferred — TMDB already year-filtered the search, so this is a hard corroboration, not the fuzzy re-rank §3.3 warns against. A confident top hit is never overridden. search_movie/search_tv forward the year. V10: sequel_variants widened — trailing Roman→digit as well as digit→Roman, spelled-out indices (one..twelve / un..douze / ordinals), and a "Part N" / "Chapitre N" wrapper. Still empty for a trailing word that is not an index or a 4-digit year. V11: when the primary hit is already decent (>= 0.6) and there is nothing more specific to try (no alternative_title, no sequel variant — only a punctuation restatement left), the ladder returns without the extra requests. The clean-title common case is back to one call. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BMLQjqFGCize2KtNBT79v
* fix(node): correct TMDB movie matching, per-file overrides, rematchChristophe Besson2026-08-291-0/+20
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | A batch of wrong poster-grid matches found live on a real library (2026-08-29): a two-volume film's second part matched the first; a numbered sequel matched a same-year making-of documentary; several entries of one franchise matched a single early entry whose localized TMDB title is the franchise name; one matched nothing. One mechanism: _tmdb_search returned the first candidate query whose title-similarity ratio merely cleared 0.6, before alternative_title / the Roman-numeral variant was ever tried. Matching: - title_parse: fold guessit's volume/part number back into display_title so the parts of a multi-part film stay distinct in the query, the card and the override. - _tmdb_search: keep a strong PASS 1 fast path (ratio >= 0.85, one request), otherwise score every candidate query and pick the best. A year-exact rescue lifts a sub-0.6 top hit to the confidence floor only when TMDB's own year-filtered result lands exactly on the filename's year. No local re-ranking of any single result list; no tmdb.py change. Fix match / rematch: - _admin_exec_tmdb_override: a movie override touches its own file only (guessit gives a whole franchise one display_title); a show override still fans out. Corrected files are marked in media_cache.tmdb_override. - media_cache: tmdb_override table; clear_file_tmdb / clear_tmdb_matches drop auto-resolved matches while sparing manual corrections. - ops.rematch_video + `meshbay-node video rematch` (loopback endpoint + CLI verb): re-resolve a group's video matches after a matcher fix. file_tmdb is keyed by content hash and otherwise only pruned on deletion, so nothing dislodged a cached match before. - a rename now drops the stale auto match too (daemon _reenrich_renamed_video_entries). UI: - VideoDetailModal shows the source filename and resolved TMDB id; an unmatched poster gets a badge (3 new video.* i18n keys x 10 locales). So a wrong match can actually be identified before hitting Fix match. docs/mediacenter.md 10.1 records this and the V8-V13 follow-up backlog (show-branch ladder, year-aware _best_match, wider sequel_variants, the 0.6-0.85 extra calls, movie grid merge, per-card rematch). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BMLQjqFGCize2KtNBT79v
* fix(video): recognize "S1"/"S2"-style season folders, not just full wordsChristophe Besson2026-08-261-0/+18
| | | | | | | | | | | | | | | | | Found live: a real show organized its first season as "House.of.the.Dragon. S01E01...mkv" inside a folder named "S1" (the show name embedded in every filename, so grouping worked by guessit's own title alone), but its later seasons as "S02E01. Episode's Own Title.mkv" inside "S2"/"S3" — no show name in any filename at all, relying entirely on the folder. Those seasons showed up as loose individual entries instead of grouped under the show: season_from_folder_name only recognized "season"/"saison"/"livre" as full words, so "S2" didn't register as a season folder at all, and the ancestor-based show-grouping fix (previous commits) never triggered for those seasons. A bare "S" + 1-2 digits as the *whole* folder name is now recognized too — anchored to the entire name so it can't match some unrelated folder that merely starts with "s" followed by digits.
* fix(video): fix two remaining bugs in Specials numbering and long-season ↵Christophe Besson2026-08-261-0/+25
| | | | | | | | | | | | | | | | | | | | | | | episodes 1. A season spanning more than one folder (a per-book Bonus folder nested inside every numbered season) got the same synthetic episode numbers handed out again in each folder independently — three unrelated Specials all showing up as "S0E01". _synthetic_episode_number now ranks across the whole show for the target season, not just one file's own folder. 2. guessit reads a bare 3-digit leading episode number as a concatenated season+episode guess rather than a plain episode number — "100" parses as season=1, episode=0, not episode=100, with nothing in its output distinguishing that from a real 2-digit episode. A season-like ancestor already overrides guessit's season (previous commit); this applies the same fix to episode via a direct regex on the leading number, capped at 3 digits so a leading year (4 digits) is never misread the same way. Both confirmed against the real library this whole fix was found on: per-book Bonus features across two books no longer collide, and a real 100th-episode file now resolves to episode 100 instead of 0.
* fix(video): group every episode under the show's own folder, not a per-file ↵Christophe Besson2026-08-261-0/+7
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | guessit title The mechanism itself was wrong, not just the TMDB matching: a bare episode numbering convention with no show name in the filename at all (`001 Episode's Own Title.ext`, no SxxExx, no show prefix — entirely ordinary on its own) makes guessit invent a "title" from whatever text follows the number. That text is the individual episode's own name, and differs for every episode in the folder — trusting it, as the code did, groups nothing together at all: every episode became its own single- episode "show", searched against TMDB by that one-off title alone. Whenever a season-like ancestor folder exists (a numbered season, or Specials/Bonus/Extras -> season 0), its own root folder now names the show unconditionally — never a per-file guessit title, which cannot tell a show's real name from an individual episode's one-off name when the filename carries no reliable ShowName/SxxExx structure. The walk continues past *every* consecutive season-like ancestor, not just the first: a per-season Bonus folder (Show/Season N/Bonus/file.ext) is nested two levels inside the show, both "Bonus" and "Season N" season-like on their own, and stopping at the first would hand back "Season N" as the show's name instead of "Show". Also adds "livre" ("book") to the season-word vocabulary (§3.4) — some shows number their seasons that way (Roman numerals) rather than "season"/"saison". Supersedes _title_from_show_siblings from the previous commit (removed): that fallback assumed a per-file title could still be trusted often enough to be worth borrowing from a sibling season folder — this fix means it never needs trusting in the first place once a season-like ancestor exists. Verified directly against the real library this was found on, not just synthetic tests: every sample file (a numbered episode, a Book/Bonus feature, a Book-root "making of") now resolves to the show's real name.
* feat(node,hub): add Videos group app (poster grid, flat list, TMDB metadata)Christophe Besson2026-08-241-0/+129
Implements docs/mediacenter.md: a "Videos" group application built on the existing files index rather than a separate catalogue. On the node side, new indexer enrichment (technical probe, filename/season parsing, thumbnail generation) runs per-file once an operator has chosen a video_root for the group, plus a TMDB client for on-demand poster/metadata lookups (never client-side, thumbnails delivered over the existing chunk path). On the hub side, a new video-app.js renders a lazily-mounted poster grid or a thumbnail-only flat list, with TMDB entirely optional per group. Along the way: the global apps registry now drives Settings' default-tab picker instead of a hardcoded list, and the video_root is configured from group Settings (like uploads) rather than from Files, with the node refusing to run any TMDB/thumbnail work until one is set. Fixes several bugs found via live testing against a real library, notably a race between two effects writing the same "image ready" state that could leave a poster grid spinning forever on a same-tab revisit — see mediacenter.md §5.4 for the full account of each one.