TV episodes can now resolve against either TMDB (default) or TVmaze - picked
via a segmented source picker in the renamed "Fetch Data Settings" modal.
TVmaze is free and needs no API token at all; movies still always resolve
against TMDB regardless of the choice, since TVmaze has no movie catalog.
- New TvmazeClient (crates/aurora-curator-core/src/web/tvmaze.rs): search +
season/episode lookup against api.tvmaze.com, filtering out unnumbered
specials rather than panicking on them (TVmaze returns every season's
episodes in one flat list, unlike TMDB's per-season endpoint).
- New TvMetadataSource setting (settings.rs), persisted alongside the TMDB
token, defaulting to Tmdb for schema-evolution safety.
- metadata.rs now branches TV fetches through an AnyTvClient wrapper so the
existing per-(series, season) caching loop doesn't care which source
produced a result; the movie-fetch path is untouched.
- Frontend: TMDB/TVmaze picker in Fetch Data Settings, persisted on change;
Fetch Data's token gating now only requires a TMDB token when it's actually
needed (movies always, TV only when TMDB is the chosen source) instead of
unconditionally blocking on it.
cargo test --workspace: 88/88 passing (81 + 7 new TVmaze + 3 new settings).
npm run build passing. Playwright verification: 18/18 checks (picker renders
and persists across reload, TV Fetch Data succeeds on TVmaze with zero TMDB
token configured, movies still correctly demand a token even with TVmaze
selected, switching back to TMDB still works end-to-end).
New ConflictPolicy enum in rename/ (Skip = original/default behavior,
AutoNumber = save as "Name (1).ext" counting up to the first free slot,
Override = replace the conflicting file outright), threaded through
execute_renames/rename_one alongside RenameAction. Override never deletes
a conflicting directory (falls back to Skip for that case); AutoNumber
sidesteps directories the same way by just trying the next number.
execute_rename now takes a conflict argument; undo/history are
deliberately NOT conflict-aware and always use their own fixed
skip-on-conflict policy regardless of what the original rename used.
Frontend: a second segmented selector in the rename confirmation modal
("On conflict: Skip / Auto-number / Override"), with Override styled in
red and its warning copy calling out that it can't be undone.
Align Up/Down now swaps the selected file with its neighbor in the shared
item list (Original Files and New Names stay positionally paired, matching
the original FileBot's "align" buttons for manually lining up mismatched
lists).
Naming-scheme Presets: up to 9 saved expressions, one per digit slot,
persisted as presets.json (src-tauri/src/presets.rs, same load/save
pattern as settings.rs/history.rs). A new Presets panel lists all 9 slots
with Save Current / Apply / Clear, and a global 1-9 keydown handler applies
a slot instantly from anywhere in the app (ignored while a text field is
focused or a modal is open).
Match now also parses filename-only release-tag text - resolution, source
(BluRay/HDTV/WEB-DL/...), video/audio codec, and release group - via a new
guess_release_info() in renamr-core's matching module, wired through a new
guess_release_info Tauri command and called alongside the existing TV/movie
guess for every file. Naming schemes can now reference
{resolution}/{source}/{video codec}/{audio codec}/{release group}, or
FileBot's own short names ({vf}/{source}/{vc}/{ac}/{group}).
- renamr-core::rename: new RenameAction enum (Move/Copy/Symlink, default Move,
mirroring net.filebot.cli.CmdlineOperations's --action modes). execute_renames() now
takes an action applied to the whole batch. Copy uses fs::copy (source untouched);
Symlink uses std::os::unix::fs::symlink / std::os::windows::fs::symlink_file behind a
cfg(unix)/cfg(windows) split. Same conflict policy (skip, never overwrite) and
auto-created destination folders apply to all three actions. 8 new tests.
- renamr-core::history: HistoryBatch now records which action produced it
(#[serde(default)] so old history.json entries - all implicitly Move - still
deserialize). revert_one() branches on it: Move still moves the file back; Copy/
Symlink undo by deleting the copy/symlink (using symlink_metadata so a broken link is
still recognized as present and removable) - the original was never touched by those
actions, so there's nothing to move back. 3 new tests.
- src-tauri: execute_rename command takes the chosen action and threads it through to
both execute_renames and record_batch.
- Frontend: a Move/Copy/Symlink segmented selector in the "Confirm Rename" modal
(remembered across modal opens, defaults to Move), action-aware subtitle/button
copy, and a status message using the right verb (Renamed/Copied/Symlinked). Only Move
reconciles a loaded item's id/name/bindings to the new path afterward, since Copy/
Symlink leave the source file exactly where it was. History panel now shows a Copy/
Symlink badge on non-Move batches and explains what Undo does for each.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Each row in the "Confirm Rename" plan now has a small x button that drops just that
file from the batch without closing the modal or needing to cancel and re-select
beforehand (previously it was all-or-nothing - "Exclude Selected" on the file list was
the only way to leave a file out, and only before opening Rename at all). Excluding
every row disables the confirm button and shows an empty-plan hint instead of sending
an empty batch. No Rust changes needed - this is pure plan-state filtering in the
frontend, RenamePlanEntry's shape is unchanged.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
- 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]>
- New crates/renamr-core/src/history module: HistoryBatch/HistoryEntry
records, revert_batch() moves files back to their original paths
(skip-on-conflict, same policy as rename::execute_renames; recreates a
deleted destination folder defensively), push_batch() maintains a
most-recent-first log capped at 50 batches. A partial port of the idea
behind net.filebot.HistoryDialog, as a flat undo stack rather than a full
session/element log. 6 new tests.
- New src-tauri/src/history.rs: persists the log as history.json in the OS
app-config directory (same location as settings.json). execute_rename now
takes an AppHandle and calls history::record_batch after every rename,
recording only the items that actually got renamed. Two new commands:
list_rename_history and undo_rename_batch.
- Frontend: the History button (previously a no-op) now opens a panel
listing past rename batches with a timestamp, file count, and the
old-name/new-name pairs, each with an Undo button. Undoing reconciles any
currently-loaded item back to its pre-rename path/name, same pattern as
post-rename reconciliation.
- Verified via cargo test (45 total)/check + npm build + a full Playwright
pass: loaded mocked files, renamed them, opened History, undid the batch,
and confirmed both the UI and the (mocked) backend state matched at every
step.
- rename::rename_one now resolves new_name relative to the source file's
own directory, allowing / as a subdirectory separator (e.g. a naming
scheme like {show name}/{episode name}) instead of rejecting it. Missing
intermediate directories are created automatically via create_dir_all.
- New validate_relative_target() replaces the old is_plain_filename():
still rejects anything that could escape the source directory (empty
path segments - which also catches a leading-slash absolute path -
and . / .. segments) plus backslashes (Renamr always treats / as the
scheme's folder separator regardless of OS), but now allows a
legitimate multi-segment relative path through.
- The "Unchanged" check now compares the full resolved target path to the
source, not just the filename, since a computed name can now include
directory components.
- 6 new tests: organizing into a new subfolder, nested subfolders that
don't exist yet, conflict detection still works for organized targets,
and rejection of backslashes / doubled / leading / trailing slashes.
- Frontend: naming-scheme editor hint explains the / convention, and the
rename confirmation modal notes when a plan will create new subfolders.
- No changes needed in the naming-expression engine itself - it already
treats / between two {} scriptlets as ordinary literal text.
- New crates/renamr-core/src/similarity module: a partial port of the
scoring idea behind FileBot's EpisodeMetrics.java - normalizes titles
(lowercase, strips a trailing release-year parenthetical, collapses
punctuation/whitespace) and scores them with a Sorensen-Dice coefficient
over character bigrams. best_title_match() picks whichever candidate is
textually closest to the query, provided it clears a minimum threshold;
ties keep the earlier (i.e. TMDB-ranked) candidate.
- TmdbClient::lookup_episode now uses best_title_match instead of blindly
taking the first search result, so a more popular but differently-named
show can no longer beat an exact-but-lower-ranked match to the top - and
reports NotFound instead of guessing when nothing in the results is a
plausible match at all.
- 7 new tests (5 in similarity, 2 in tmdb) including the concrete regression
case this fixes: searching "Chuck" with TMDB returning "Chucky" ranked
above "Chuck" now correctly picks "Chuck".
- Also fixes a gitignore gap from last delivery: the _to_delete*/ scratch-dir
pattern used to work around this repo's mounted-folder git-lock quirk had
only ever been applied directly on the device, never carried back into the
zip this delivery is built from - so the last delivery's cp -rf silently
reintroduced the untracked-lock-file risk. Fixed in both places now.
- ExpressionFormat::compile_scriptlet now falls back to treating a {token} as a
literal multi-word binding key when it's neither a valid Rhai identifier nor
valid Rhai script, but still looks like a plain human-typed field name (only
letters/digits/underscores/spaces). This is what lets `{show name}` work
without anyone needing to know Rhai, while real Rhai script (even containing
spaces) still takes priority and genuine syntax errors still surface.
- New default naming scheme app-wide: `{show name} - {season number}x
{episode number} - {episode name}` (previously the terser {n} - {s00e00} -
{t} form). Match/Fetch Data auto-upgrade targets updated to match, and
sample items carry both the short FileBot-style keys and the new
human-readable ones side by side.
- Naming scheme editor UI rewritten: explains the field is fully editable,
lists available fields grouped by when they become available (always /
after Match / after Fetch Data), and clarifies Rhai script is also
supported for anyone who wants it.
- 3 new unit tests for the plain-field-name fallback (multi-word lookup
works, real Rhai still takes priority, genuine syntax errors still error).