Add movie support to Fetch Data: local title/year guessing plus TMDB movie lookup

- renamr-core::matching: guess_movie() extracts a movie title (and release year, if
  present) from a filename using the same year/quality-token boundary heuristics real
  release names follow, as a fallback when guess_episode() finds no season/episode
  marker.
- renamr-core::web::tmdb: search_movie()/lookup_movie() mirror the TV client, reusing
  the same Sorensen-Dice title-disambiguation as lookup_episode(), narrowed further by
  TMDB's own primary_release_year filter when a year was locally guessed. Also exposed
  search_tv_best_match() and fixed a latent gap where the real Fetch Data command path
  (metadata.rs::fetch_season) bypassed the fuzzy show-name disambiguation entirely,
  doing its own raw first-result selection instead of reusing it.
- src-tauri: new guess_movie command and fetch_movie_metadata command/types
  (MovieFetchQuery/Outcome/Result), registered alongside the existing TV commands.
- Frontend: Item now tracks mediaKind ("tv"/"movie"/null); Match tries guess_episode
  then falls back to guess_movie; Fetch Data resolves TV and movie targets via their
  own commands concurrently and merges results. A single batch can mix TV and movie
  files - each only fills in the fields that apply to it. Movie fields ({movie name},
  {year}, reusing {airdate} for release date, matching FileBot's own unified binding)
  are documented in the naming-scheme field reference.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
Renamr Scaffold
2026-08-29 04:04:16 +00:00
co-authored by Claude Sonnet 5
parent 93b5ac5e56
commit 45a9f5d523
6 changed files with 616 additions and 127 deletions
+49 -40
View File
@@ -5,15 +5,17 @@ fork), built from scratch as its own project: new stack, new architecture, new b
**Status: early scaffold, but the core loop works end to end.** The naming-expression
engine is ported and tested, the faithfully-recreated two-pane UI is wired to real
native file/folder loading and drag-and-drop, local filename-based season/episode
detection ("Match") is working, real online metadata lookups against TMDB ("Fetch
Data") are working — including fuzzy show-name disambiguation, so a search that
returns several similarly-named shows doesn't just take TMDB's top-ranked hit — and the
actual on-disk rename operation is wired up (skip-on-conflict, with a confirmation step
showing the full old-path/new-path plan before anything touches disk). Rename can also
organize files into subfolders — including ones that don't exist yet — when the naming
scheme includes a `/`. Every completed rename is logged to a history panel that can
undo it, moving files back to their original paths. Full media-metadata bindings are
native file/folder loading and drag-and-drop, local filename-based season/episode *and
movie title/year* detection ("Match") is working, real online metadata lookups against
TMDB ("Fetch Data") are working for both TV episodes and movies — including fuzzy
title disambiguation, so a search that returns several similarly-named results doesn't
just take TMDB's top-ranked hit — and the actual on-disk rename operation is wired up
(skip-on-conflict, with a confirmation step showing the full old-path/new-path plan
before anything touches disk). Rename can also organize files into subfolders —
including ones that don't exist yet — when the naming scheme includes a `/`. Every
completed rename is logged to a history panel that can undo it, moving files back to
their original paths. A single loaded batch can freely mix TV episodes and movies —
each file only fills in the fields that apply to it. Full media-metadata bindings are
not yet ported. See "Porting roadmap" below.
The naming scheme is fully user-editable from the app (the "Naming Scheme" button next
@@ -106,12 +108,13 @@ that item is skipped rather than overwritten.
```
crates/renamr-core/ Pure Rust core: naming-expression engine (ported), local
filename-based season/episode matching (ported, `matching/`),
text-similarity scoring for disambiguating search results
(`similarity/`, partial `EpisodeMetrics` port), a TMDB metadata
client (`web/tmdb.rs` — TV search + episode lookup, disambiguated
via `similarity/`, tested against a mocked HTTP server via
`mockito`), the on-disk rename engine (`rename/`) —
filename-based season/episode *and movie title/year* matching
(ported, `matching/`), text-similarity scoring for disambiguating
search results (`similarity/`, partial `EpisodeMetrics` port), a
TMDB metadata client (`web/tmdb.rs` — TV search + episode lookup
and movie search + lookup, both disambiguated via `similarity/`,
tested against a mocked HTTP server via `mockito`), the on-disk
rename engine (`rename/`) —
skip-on-conflict, can also organize into (auto-created)
subfolders when the naming scheme includes `/` — and a rename
history/undo log (`history/`, a partial `HistoryDialog` port).
@@ -119,18 +122,19 @@ crates/renamr-core/ Pure Rust core: naming-expression engine (ported), local
datasource clients (TVDB/AniDB/etc).
No Tauri dependency, so it builds and unit-tests standalone.
src-tauri/ Tauri app shell: exposes renamr-core as commands to the frontend
(naming-scheme rendering, filename matching, TMDB metadata fetch,
executing a rename plan, native file/folder dialogs, drag-and-drop
expansion), plus `settings.rs` (TMDB token) and `history.rs`
(rename history/undo log) — both stored as JSON in the OS
app-config directory — app metadata/icons/window config
(tauri.conf.json).
(naming-scheme rendering, filename matching for both TV and
movies, TMDB metadata fetch — `metadata.rs` for TV episodes,
plus a movie-fetch command alongside it — executing a rename
plan, native file/folder dialogs, drag-and-drop expansion), plus
`settings.rs` (TMDB token) and `history.rs` (rename history/undo
log) — both stored as JSON in the OS app-config directory — app
metadata/icons/window config (tauri.conf.json).
src/ React + TypeScript UI (Vite). Faithful recreation of the original
two-pane rename screen (Aurora theme) wired to real file loading,
drag-and-drop, live naming-scheme preview, local matching, TMDB
fetch (behind a Settings modal for the API token), a
confirm-before-touching-disk modal for the actual rename, and a
History panel that can undo a past rename.
drag-and-drop, live naming-scheme preview, local TV/movie
matching, TMDB fetch for both (behind a Settings modal for the
API token), a confirm-before-touching-disk modal for the actual
rename, and a History panel that can undo a past rename.
scripts/gen_icons.py Regenerates src-tauri/icons/ (placeholder branding — swap the
colors/glyph in the script, or replace the icons directly, to
change the look).
@@ -191,25 +195,30 @@ ported from, for anyone picking this up:
3. **Matching/similarity engine** — `net.filebot.similarity.*`. Local filename-based
season/episode detection is done (`crates/renamr-core/src/matching`, ported from the
pattern-matching half of `SeasonEpisodeMatcher.java`, adapted to Rust's `regex` crate
which lacks lookahead/lookbehind — wired to the "Match" button). Show-name
disambiguation is also done (`crates/renamr-core/src/similarity`, a partial port of
the *scoring* idea behind `EpisodeMetrics.java`'s fuzzy title-matching cascade —
Sorensen-Dice similarity over normalized-title character bigrams): "Fetch Data" now
picks whichever TMDB search result is textually closest to the local guess rather
than just trusting TMDB's relevance/popularity-ranked first hit, and reports "not
found" instead of guessing when nothing in the results is a plausible match at all.
`DateMatcher` (numbering/air-date-based identification, the other half of the
original `EpisodeMetrics`) is not ported — Renamr doesn't do date-based episode
identification.
which lacks lookahead/lookbehind — wired to the "Match" button), and so is a local
movie title/year heuristic (`guess_movie` in the same module) used as a fallback
whenever a filename carries no season/episode marker — a release name is either a TV
episode or a movie, never both. Title disambiguation is also done
(`crates/renamr-core/src/similarity`, a partial port of the *scoring* idea behind
`EpisodeMetrics.java`'s fuzzy title-matching cascade — Sorensen-Dice similarity over
normalized-title character bigrams), shared by both the TV and movie TMDB lookups:
"Fetch Data" picks whichever TMDB search result is textually closest to the local
guess rather than just trusting TMDB's relevance/popularity-ranked first hit, and
reports "not found" instead of guessing when nothing in the results is a plausible
match at all. `DateMatcher` (numbering/air-date-based identification, the other half
of the original `EpisodeMetrics`) is not ported — Renamr doesn't do date-based
episode identification.
4. **Media detection** — `net.filebot.media.MediaDetection` (1356 lines): ties file-name
parsing + matching together to auto-identify what a file is.
5. **Web datasource clients** — `net.filebot.web.*` (~40 files): TheMovieDB, TheTVDB,
AniDB, OMDb, TVmaze, OpenSubtitles, etc. TMDB is done for TV (`crates/renamr-core/src/web/tmdb.rs`,
`reqwest` + `serde_json`, Bearer-token auth) — search-by-name plus per-season episode
lookup, wired to "Fetch Data", disambiguating multi-result searches via the
AniDB, OMDb, TVmaze, OpenSubtitles, etc. TMDB is done for both TV and movies
(`crates/renamr-core/src/web/tmdb.rs`, `reqwest` + `serde_json`, Bearer-token auth) —
search-by-name plus per-season episode lookup for TV, search-by-title (optionally
narrowed by release year via TMDB's own `primary_release_year` filter) for movies —
both wired to "Fetch Data", disambiguating multi-result searches via the
similarity-scoring pass described in item 3 above rather than blindly trusting
TMDB's top-ranked hit. TMDB movie lookup and all other sources (TheTVDB, AniDB,
OMDb, TVmaze, OpenSubtitles) are not yet started.
TMDB's top-ranked hit. All other sources (TheTVDB, AniDB, OMDb, TVmaze,
OpenSubtitles) are not yet started.
6. **Rename engine + CLI** — `net.filebot.cli.CmdlineOperations` (1235 lines) and
`ArgumentBean` (415 lines, the full flag surface: `-rename`, `--db`, `--format`,
`--action`, `--conflict`, `-get-subtitles`, `-script`, etc.). The rename operation is