Files
aurora-curator/README.md
T

792 lines
56 KiB
Markdown
Raw Normal View History

# Aurora Curator
A fast, cross-platform media renamer — a ground-up rewrite of FileBot (via its FB-Mod
fork), built from scratch as its own project: new stack, new architecture, new branding.
**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 *and
movie title/year* detection ("Match") is working, real online metadata lookups
("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 the source's top-ranked hit. TV episodes can resolve against TMDB, TVmaze, or
OMDb, and movies against TMDB or OMDb — each picked independently in Fetch Data
Settings, TMDB stays the default for both, TVmaze needs no API token at all (it has no
movie catalog, so it's TV-only), and TMDB/OMDb each need their own free saved
credential before a batch resolved against them will succeed. The actual on-disk operation is wired up (with a
confirmation step showing the full old-path/new-path plan before anything touches disk,
a per-row ✕ to drop an individual file from that plan at the last step, a Move/Copy/
Symlink selector for what to actually do at each target, and — independently — a Skip/
Auto-number/Override selector for what happens when the computed name already exists:
leave it alone (the original, and still default, behavior), save alongside it as
"Name (1).ext", or replace it outright). It can also organize files into subfolders —
including ones that don't exist yet — when the naming scheme includes a `/`. Every
completed batch is logged to a history panel that can undo it (moving files back for
Move, or just removing what was created for Copy/Symlink — undoing always uses its own
skip-on-conflict policy regardless of what the original rename used, so an undo can
never overwrite something else as a side effect). A single loaded batch can freely mix
TV episodes and movies
— each file only fills in the fields that apply to it. "Match" also parses release-tag
text straight out of the filename — resolution, source (BluRay/HDTV/WEB-DL/…),
video/audio codec, release group — so naming schemes can reference
`{resolution}`/`{source}`/`{video codec}`/`{audio codec}`/`{release group}` (or
FileBot's own `{vf}`/`{source}`/`{vc}`/`{ac}`/`{group}`), and now also probes each
file's own container header for its real resolution, frame rate, duration, and
codecs — no ffmpeg/MediaInfo dependency required — filling in `{resolution}`/
`{video codec}`/`{audio codec}` whenever the filename itself didn't already carry
that tag, plus `{width}`/`{height}`/`{fps}`/`{duration}`/`{file size}`, which have no
filename-tag equivalent at all; see "Real media-file probing" below. A separate **Hash
Files** button computes real `{crc32}`/`{md5}`/`{sha1}` checksums on demand (deliberately
not automatic, since it reads a file's entire contents — see "Hash Files" below). The Align
Up/Down buttons reorder the (shared) file list — the two
panes always stay positionally paired — and up to 9 naming schemes can be saved as
presets, recalled with a click or a `1`-`9` keypress (persisted locally, same pattern as
Settings/History). The app can also be driven entirely from the command line —
`aurora-curator -rename --format "<naming scheme>" [options] <file-or-folder>...` — for
scripting batch renames without opening the window at all; see "Command-line usage"
below. A new Subtitles panel searches and downloads subtitle files from
[OpenSubtitles](https://www.opensubtitles.com/) — load a video, its title/season/episode
(or movie title/year) auto-fills from the same local matching "Match" uses, pick a
result, and the subtitle is saved right next to the video ready for any player to pick
up automatically; see "Subtitles" below. A new Episodes panel searches a series by
name and browses a full season's episode list — episode number, title, air date — using
whichever TV source (TMDB/TVmaze/OMDb) is already configured in Fetch Data Settings, no
separate credentials needed; see "Episodes" below. A new SFV panel computes and verifies
CRC32/MD5/SHA1 checksums for a loaded set of files, and reads and writes the classic
`.sfv`/`.md5`/`.sha1` sidecar-file formats — entirely local, no network access or
credentials of any kind; see "SFV" below. A new Filter panel loads a batch of files and
narrows it down by extension, TV/Movie/Unmatched kind, filename text, and (for TV) season
— all computed from the same local, filename-only matching "Match" uses, with a Copy File
Paths export for whatever's left; see "Filter" below. A new List panel renders a
user-editable naming-scheme-style expression per loaded file (the same `{ }` syntax and
`render_expression` engine the Rename panel's live preview uses, fed by the same local
matching "Match"/Filter use), with a live "N of M lines rendered" count, a Copy List
clipboard export, and an Export to File… button that writes the whole list to a `.txt`
file via a native Save dialog — this closes out the full original panel set (Rename/
Episodes/Subtitles/SFV/Filter/List); see "List" below. Real media-file probing (real
resolution, frame rate, duration, and codecs read straight from a video file's own
container header, not guessed from its filename) is wired into the Rename panel's
"Match" step, and now the Filter and List panels' own local scans too — Filter gained
Resolution/Video codec filter chips from it, and List's expressions can reference the
same probed fields — see "Real media-file probing" below. The naming-expression engine's Rhai
scripting layer also gained a batch of FileBot-style string helpers — `.pad()`,
`.sort_name()`/`.sort_initial()`, `.upper_initial()`/`.lower_initial()`, `.acronym()`,
`.validate_file_name()`, `.space()`/`.space_with()`, `.colon()`/`.colon_with()` — callable
inside any `{ }` naming-scheme expression, e.g. `{n.sort_name()}/S{s.pad(2)}E{e.pad(2)}`;
see "Porting roadmap" below.
The naming scheme is fully user-editable from the app (the "Naming Scheme" button next
to the New Names list) — the default is `{show name} - {season number}x{episode
number} - {episode name}`, but it's just a starting point, not a fixed template.
Plain, human-readable, multi-word field names like `{show name}` work directly (no
Rhai knowledge needed), FileBot's own short field names (`{n}`, `{s00e00}`, `{t}`)
still work side by side with them, and real Rhai script is available for anyone who
wants it (e.g. `{show name.to_upper()}`). See "Naming scheme syntax" below for how
that's implemented. Every field name shown in the editor is also a clickable button —
click one to insert it at the cursor instead of typing it out by hand. Whatever's
currently in the editor is saved to disk as you type (debounced) and restored the next
time the app opens, so a custom scheme survives a restart instead of resetting to the
sample expression above; the 1-9 preset slots (a separate, explicitly-named save) are
still there alongside this for switching between several schemes on purpose.
## Why a rewrite, and why this stack
The original is a ~20k+ line Java/Swing app built with Ant. The goals were: faster
startup and UI, faster matching/renaming, a codebase that's actually pleasant to work
in, and a genuine rebrand — not a coat of paint.
Aurora Curator is **Tauri**: a Rust core (`crates/aurora-curator-core`, `src-tauri`) plus a
TypeScript/React frontend (`src`), compiled to a small native binary per platform
(Windows/macOS/Linux) with a system webview instead of shipping a bundled browser
engine. That's the concrete "faster" win over both the original JVM/Swing app and
an Electron-style rewrite: quick cold start, small binary, real OS-native windowing.
An earlier attempt used Kotlin + Compose Multiplatform to stay on the JVM and reuse
FileBot's Java logic directly. That was abandoned (nothing was salvaged into this repo)
after discovering the sandbox this was originally built in blocks Maven Central and the
Gradle Plugin Portal, making it impossible to compile or test. Tauri was chosen as the
replacement because both halves — Cargo via crates.io, npm via the public npm registry
— are reachable, so every piece described as "done" below is actually compiled/tested,
not just written and hoped for.
### Compatibility notes
Goal: keep CLI flags, config file locations, and the *outer* `{ }` naming-scheme
template syntax compatible with FileBot conventions, so existing automation mostly
keeps working.
One deliberate gap: FileBot's `{ }` expressions ran arbitrary **Groovy** code
(JVM-only). Aurora Curator's Rust core has no Groovy engine, so expressions are evaluated with
[Rhai](https://rhai.rs/) instead. Simple bindings (`{n}`, `{s00e00}`, `{t}`) are
identical. More complex chained-method expressions will need small syntax tweaks
(e.g. Groovy's `.toUpperCase()` → Rhai's `.to_upper()`) — Rhai already ships most of the
common string/array methods FileBot's expression helpers wrapped, so most patterns
translate directly.
### Naming scheme syntax
Every `{ }` token in an expression is resolved in this order
(`crates/aurora-curator-core/src/format/expression.rs::compile_scriptlet`):
1. **A single bare identifier** (`{n}`, `{season}`) is a direct binding lookup — the
fast path, and how FileBot's own short field names keep working unmodified.
2. Otherwise it's **compiled as Rhai script** (`{n.to_upper()}`, `{n + " " + t}`) —
real code, with every current binding available as a script variable.
3. If it's *neither* a valid identifier *nor* valid Rhai, but it still only contains
letters, digits, underscores and spaces (e.g. `{show name}`, `{episode number}`),
it falls back to a **literal multi-word binding key** instead of raising a syntax
error — this is what lets a human-typed field name with spaces work as a plain
lookup without anyone needing to know Rhai. Anything that isn't a plausible field
name (stray punctuation, unmatched brackets, etc.) still surfaces as a real syntax
error at that point, exactly as before.
An undefined binding contributes an empty string rather than failing outright (so
`{n}{missing}` still renders); the overall expression only errors if the *total*
rendered output would be empty.
`/` between two `{ }` scriptlets (or anywhere in the literal text) needs no special
handling in the expression engine at all — it's just ordinary literal text, the same as
a space or a dash. It only becomes meaningful one layer up, at rename time (see
"Organizing into subfolders" below): a scheme like `{show name}/{episode name}` renders
to a name containing a real `/`, and the rename engine treats that as "put this file in
a subfolder" rather than rejecting it.
### Organizing into subfolders
A naming scheme that includes `/` (e.g. `{show name}/Season {season number}/{episode
number} - {episode name}`) moves the file into that subfolder as part of renaming,
creating any missing intermediate folders automatically
(`crates/aurora-curator-core/src/rename/mod.rs::rename_one` → `fs::create_dir_all`). This is
still always scoped to the file's own directory tree — the target is resolved relative
to the source file's parent directory, never as an absolute path, and every path
component is validated (`validate_relative_target`) to reject empty segments (a
leading, trailing, or doubled `/` — which also catches an absolute-looking
`/etc/passwd`-style path), `.`/`..` segments, and backslashes (Aurora Curator always uses `/`
for the naming scheme's folder separator regardless of OS). If the resolved target
already exists and isn't the same underlying file, what happens next is up to the
chosen conflict policy — Skip (default), Auto-number, or Override; see item 6 of the
porting roadmap below for the full behavior of each.
## Project layout
```
crates/aurora-curator-core/ Pure Rust core: naming-expression engine (ported), local
filename-based season/episode *and movie title/year* matching
(ported, `matching/`), plus filename-based release-tag parsing in
the same module (`guess_release_info` — resolution, source, video/
audio codec, release group), text-similarity scoring for
disambiguating search results (`similarity/`, partial
`EpisodeMetrics` port), three web metadata clients — `web/tmdb.rs`
(TV search + episode lookup and movie search + lookup),
`web/tvmaze.rs` (TV search + episode lookup only — TVmaze has no
movie catalog, and no API key/auth at all), and `web/omdb.rs` (TV
*and* movie search + lookup, single flat root-URL REST API, plain
`apikey` query-param auth rather than TMDB's Bearer token) — all
three disambiguated via
`similarity/` and tested against a mocked HTTP server via
`mockito`, plus a fourth web client that isn't a metadata
lookup at all — `web/opensubtitles.rs` (search + login +
download against OpenSubtitles' REST API; search only needs an
app-level API key, download additionally needs the end user's own
account login, since downloads are quota-limited per account —
see "Subtitles" below). The Episodes panel adds no new core-crate
code of its own — it reuses `metadata::AnyTvClient::fetch_season`
(the same season-resolving call `AnyTvClient` already made available
to "Fetch Data" and the CLI) from the Tauri side, so `web/tmdb.rs`/
`tvmaze.rs`/`omdb.rs`'s existing season-episode-list methods are
exactly what it browses. `sfv/` computes CRC32/MD5/SHA1 checksums
(streamed in 64KB chunks so large video files aren't loaded whole
into memory) and parses/builds both sidecar-file conventions the
SFV panel supports — the traditional `.sfv` layout (CRC32 only,
`<name> <checksum>` per line) and the GNU coreutils
`md5sum`/`sha1sum` layout (`<checksum> <name>`) used for `.md5`/
`.sha1` — dispatched on a shared `ChecksumKind` enum, plus
entry-verification against real files on disk (`Ok`/`Mismatch`/
`Missing` per entry). No network access, no credentials — entirely
local, unlike every other panel added this session. The on-disk
rename engine (`rename/` — Move/Copy/Symlink actions,
Skip/Auto-number/Override conflict policies, can also organize
into (auto-created) subfolders when
the naming scheme includes `/`), a rename history/undo log
(`history/`, a partial `HistoryDialog` port, undo-aware of which
action produced each batch), pure `-rename` command-line
argument parsing/file expansion (`cli/` — no Tauri or network
dependency at all, see "Command-line usage" below), and real
media-file container probing (`probe/` — real resolution, frame
rate, duration, and codecs read straight from a video file's own
header via two pure-Rust container parsers, the `mp4` crate for
ISO-BMFF containers and the `matroska` crate for EBML/Matroska
containers, no ffmpeg/MediaInfo system dependency; see "Real
media-file probing" below). Not yet ported: other web datasource
clients (TheTVDB/AniDB/etc), and everything MediaBindingBean
derives that neither a filename tag nor `probe/`'s two supported
container families can provide (other containers like .avi/.wmv,
audio-only metadata like bitrate/sample rate). No Tauri
dependency, so it builds and unit-tests standalone.
src-tauri/ Tauri app shell: exposes aurora-curator-core as commands to the frontend
(naming-scheme rendering, filename matching for both TV and
movies, metadata fetch — `metadata.rs` for TV episodes (against
whichever of TMDB/TVmaze/OMDb is the stored TV source) plus a
movie-fetch command alongside it (against whichever of TMDB/OMDb
is the stored movie source — independent of the TV source)
— executing a rename
plan, native file/folder dialogs, drag-and-drop expansion), plus
`settings.rs` (TMDB token, OMDb API key, OpenSubtitles API key +
account login, and which TV *and* movie
metadata source Fetch Data uses — independent settings; the
OpenSubtitles password is the one credential this module never
returns to the frontend, only whether one is saved),
`subtitles.rs` (search/download commands wrapping
`aurora-curator-core::web::opensubtitles`, saving a downloaded
subtitle right next to its video file), `episodes.rs` (the
`browse_episodes` command backing the Episodes panel — series
search + one season's episode list, via a `build_tv_client` helper
now shared with `metadata.rs`'s `fetch_metadata`, so both read the
same stored TV-source setting and credential), `sfv.rs` (four
commands backing the SFV panel and the Rename panel's Hash Files
button — `compute_checksums` over a batch of loaded files with
per-file error isolation, `save_checksum_file` via a native "Save
File" dialog, `verify_checksum_file`, which reads and parses a
picked `.sfv`/`.md5`/`.sha1` file and resolves each entry's bare
filename relative to that file's own parent directory, and
`compute_naming_hashes` — all four wrapping
`aurora-curator-core::sfv`, which does the actual algorithm/format
work; `compute_naming_hashes` calls the crate's
`compute_all_checksums` rather than `compute_checksum` three times,
streaming each file once instead of three),
`history.rs` (rename history/undo
log), `presets.rs` (up to 9 saved naming schemes) — all three
stored as JSON in the OS app-config directory — `list.rs` (a
single `export_text_list` command backing the List panel's Export
to File… button: opens a native "Save File" dialog and writes
whatever already-rendered, newline-joined text the frontend hands
it to a `.txt` file — the only new Tauri surface List needed, since
matching each file and rendering its line both reuse existing
commands, see below), `probe.rs` (a single `probe_media_files`
command backing real media probing in the Rename panel's Match
step: batch-probes every loaded file's real container header off
the main thread, wrapping `aurora-curator-core::probe` — see "Real
media-file probing" below) — `cli.rs` (the
`-rename` command-line mode's orchestration: builds and executes a
rename plan against `aurora-curator-core::cli`'s parsed arguments,
with no Tauri `App`/window ever created for it — `main.rs` branches
to it before the GUI is touched at all), and 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 TV/movie
matching, real media-file probing (`probe_media_files`, batched
alongside the local matching calls in `RenamePanel.tsx`'s
`runMatching`), metadata fetch for both (behind a Fetch Data Settings
modal holding independent TV-source (TMDB/TVmaze/OMDb) and
movie-source (TMDB/OMDb) pickers plus the TMDB token and OMDb key
inputs), a
confirm-before-touching-disk modal for the actual
rename, a History panel that can undo a past rename, Align Up/Down
list reordering, and a Presets panel (plus `1`-`9` hotkeys) for
saved naming schemes. `SubtitlesPanel.tsx` is a separate top-level
panel (its own Subtitle Settings modal, its own credential state)
reached from the sidebar's "Subtitles" entry — the first sidebar
entry besides Rename to be a real screen rather than a stub.
`EpisodesPanel.tsx` is the second: a series-search + season-browse
screen with no settings modal of its own at all — it deliberately
has no separate credentials UI, reading whatever TV source/token
Fetch Data Settings (in the Rename panel) already has saved.
`SfvPanel.tsx` is the third: load a set of files, compute
checksums in any of CRC32/MD5/SHA1, save them as a sidecar
checksum file via a native Save dialog, or pick an existing
checksum file and verify the files it lists against what's
actually on disk (OK/mismatch/missing per entry) — no settings
modal at all, since the feature needs no credentials or network
access. `FilterPanel.tsx` is the fourth: load a set of files and
narrow them down with toggleable extension/kind chips, a
filename-contains text field, and a season number field (TV
only) — adds no new Rust/Tauri commands at all, reusing
`pick_files`/`guess_episode`/`guess_movie`/`guess_release_info`
exactly as the Rename panel's own "Match" does, just applied to
a second, independently-loaded file list and rendered as
criteria instead of a rename preview. `ListPanel.tsx` is the
fifth and last: load a set of files, edit a naming-scheme-style
expression (rendered per file via the same `render_expression`
command the Rename panel's live preview calls, fed by the same
local matching Filter uses), see a live "N of M lines rendered"
list, Copy List to the clipboard, or Export to File… via the one
new `export_text_list` command above — closing out the full
original panel set.
scripts/gen_icons.py Regenerates src-tauri/icons/ from the real logo mark
(`scripts/logo/icon-source.png`, cropped from the supplied
lockup) — replace that source image to change the look.
```
## Building
Prerequisites: Rust (stable) + Cargo, Node.js 18+, and Tauri's platform prerequisites
(see https://v2.tauri.app/start/prerequisites/ for the full, up-to-date list).
**Linux:** `libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev
librsvg2-dev` plus a C toolchain (`build-essential` on Debian/Ubuntu). Bundling the
installers additionally needs `patchelf` (for the `.deb`/`.rpm`) — `npm run tauri build`
will also download a few small `linuxdeploy` helper tools from GitHub the first time it
builds an `.AppImage`, so a network connection is needed for that step.
**macOS:** Xcode (or at least the Xcode Command Line Tools — `xcode-select --install`).
No other system packages are needed.
**Windows:** Rust via [rustup](https://rustup.rs/), using the default MSVC toolchain
(not the GNU one). [Visual Studio Build Tools](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio)
(or full Visual Studio) with the **"Desktop development with C++"** workload, which
provides the linker Rust's MSVC toolchain needs. The **WebView2 Runtime** — this is
already preinstalled on any up-to-date Windows 10/11 machine, so you usually don't need
to do anything; if you hit a WebView2-related error, grab the Evergreen bootstrapper from
Microsoft. You do **not** need to separately install NSIS or WiX — Tauri's bundler
downloads NSIS automatically the first time it builds the `.exe` installer.
```sh
# Rust core (no UI deps needed):
cargo test -p aurora-curator-core
# Full app in dev mode (opens a native window):
npm install
npm run tauri dev
# Production build (installer/bundle for your current OS):
npm run tauri build
```
`npm run tauri build` produces a native installer for whichever OS you run it on — there
is no cross-compilation; build on Windows to get a `.exe`/`.msi`, on Linux for
`.deb`/`.rpm`/`.AppImage`, on macOS for a `.app`/`.dmg`.
Verified so far: `cargo test --workspace` (199 tests passing, including the TMDB,
TVmaze, OMDb, and OpenSubtitles client tests — those run against a local mock HTTP
server via `mockito`, no live network or API key needed to build/test), `cargo check -p
aurora-curator` (compiles clean against real Tauri + GTK/WebKit system libraries), and
`npm run build` (TypeScript type-checks, Vite bundles). The production build
(`npm run tauri build`) has been run end-to-end and the resulting app **launches and
runs correctly outside of dev mode** on both macOS (`.app`, confirmed by hand) and Linux
(`.deb`/`.rpm`/`.AppImage`, built and smoke-tested in a real Ubuntu environment). Windows
is the one platform not yet built/verified — if you hit anything building there, please
open an issue.
### Setting up metadata sources (for "Fetch Data")
Everything else in the app works fully offline; "Fetch Data" is the one feature that
talks to the network, and it's the only one that needs any setup at all — and even that
setup is optional for TV.
TV episodes and movies each have their own independent source picker in the app (click
the key icon, "Fetch Data Settings", next to Fetch Data) — a batch can freely mix TV and
movie files resolved against two different sources at once:
- **[TVmaze](https://www.tvmaze.com/)** — TV only (it has no movie catalog). Free, no
account or API token needed at all. Just pick it and go.
- **[TMDB](https://www.themoviedb.org/)** — the default for both TV and movies. Needs a
free account:
1. Create a TMDB account, then go to Settings → API in your account and generate an
**API Read Access Token** (not the older numeric "API Key" — Aurora Curator uses the
Bearer token method TMDB documents as the default).
2. In the app, open "Fetch Data Settings" and paste the token in. It's saved locally
in the OS app-config directory and only ever sent to `api.themoviedb.org`.
- **[OMDb](https://www.omdbapi.com/)** — TV and movies, an alternative to TMDB for
either or both. Needs a free API key:
1. Request a free key at <https://www.omdbapi.com/apikey.aspx> (OMDb emails it to you).
2. In the app, open "Fetch Data Settings" and paste the key into the OMDb field. It's
saved locally in the OS app-config directory and only ever sent to
`www.omdbapi.com`.
A batch of TV-only files can run entirely on TVmaze with no credential configured at
all. Otherwise, whichever of TMDB/OMDb is actually selected — for TV, for movies, or
both — needs its own credential saved first; picking TMDB for TV and OMDb for movies (or
any other mix) only needs the two credentials that combination actually uses. Note OMDb
returns a normal-looking (HTTP 200) "no results" response for both a genuine no-match and
an invalid API key, so a bad key currently surfaces as "not found" rather than a clearer
credential error — a known OMDb API limitation, not something Aurora Curator can
distinguish from its response alone.
## Command-line usage
The same binary that opens the GUI can also run a batch rename straight from a script or
terminal — a port of FileBot's `net.filebot.cli` flag surface (`-rename`, `--format`,
`--db`, `--action`, `--conflict`), with a couple of deliberate differences noted below.
Passing `-rename` (or `-h`/`--help`) on the command line skips the window entirely:
```sh
aurora-curator -rename --format "{show name} - {season number}x{episode number} - {episode name}" \
--action move --conflict skip ~/Downloads/some-show/
```
- `--format "<naming scheme>"` (required) — the same expression syntax as the GUI's
Naming Scheme field: FileBot short names (`{n}`, `{s00e00}`, `{t}`), human-readable
aliases (`{show name}`, `{episode name}`, `{movie name}`, `{resolution}`, `{video
codec}`, `{release group}`, …), or real Rhai script. `{name}`/`{ext}` fall back to the
file's own current filename/extension when nothing else matched it.
- `--db <tmdb|tvmaze|omdb>` (optional) — resolves TV episodes against that online source
before rendering names, the command-line equivalent of clicking "Fetch Data" after
Match. Movies are also looked up online whenever this is `tmdb` or `omdb` — TVmaze has
no movie catalog, so `--db tvmaze` only affects the TV half of a batch — and with no
`--db` at all every file uses local filename matching only, same as Match without Fetch
Data in the GUI. `--db tmdb`/`--db omdb` each need the matching token/key that's already
been saved through the app's own Fetch Data Settings at least once — **the CLI never
accepts an API token or key as an argument**, so there's nothing secret to leak into
your shell history or a script.
- `--action <move|copy|symlink|test>` (default `move`) — what to do at each computed
target; `test` prints the full plan and touches nothing on disk, useful for checking a
naming scheme before committing to it.
- `--conflict <skip|auto-number|override>` (default `skip`, `autonumber`/`auto` also
accepted) — what happens when the computed target already exists.
- `-non-strict` — accepted for compatibility with existing FileBot scripts, but is a
genuine no-op here: the local matcher has no confidence score to loosen, and online
disambiguation (`similarity::best_title_match`) always runs regardless of this flag.
- Every remaining argument is treated as an input file or folder; folders are expanded
recursively (hidden files/dotfolders skipped), matching exactly what the GUI's own
"Select Folder…" and drag-and-drop do.
Deliberately **not** ported: FileBot's `--output <dir>` flag. The rename engine is
scoped, as a safety invariant, to never move a file outside its own source directory
tree (see "Organizing into subfolders" above) — a naming scheme's own `/` already covers
organizing into a subfolder, and relaxing that invariant just for `--output` wasn't
judged worth it.
## Episodes
The Episodes sidebar panel searches for a TV series by name and browses one season's
full episode list — episode number, title, and air date — as a standalone reference,
independent of whatever files (if any) are loaded in the Rename panel.
Type a series name, pick a season (the +/− stepper re-queries automatically once you've
searched at least once), and click **Browse**. A season with a real series match but no
episodes of its own (e.g. asking for season 99 of a 5-season show) comes back as a clear
"no episodes found for season N" message rather than an error; a series name that
doesn't match anything at all surfaces the underlying source's own not-found message. A
**Copy list** button copies every row as plain text (`Series Name - SxEE - Episode
Title`, one per line) to the clipboard — handy for pasting into a naming reference or
anywhere outside the app.
Unlike Subtitles, this panel has **no settings or credentials of its own**: it reuses
whichever TV source — TMDB, TVmaze, or OMDb — is already configured in the Rename
panel's Fetch Data Settings, and the same season-lookup call ("Fetch Data" and the
`-rename --db` CLI both already made) is exactly what it browses. If nothing is
configured yet (only relevant for TMDB/OMDb — TVmaze needs no credential), Browse
reports the same "open Settings to add one" message Fetch Data itself would.
## Subtitles
The Subtitles sidebar panel searches and downloads subtitle files from
[OpenSubtitles](https://www.opensubtitles.com/) — the first thing in Aurora Curator that
fetches an actual media file (a subtitle) rather than title/air-date text.
**Load Video File…** picks one video, then reuses the exact same local filename-based
matching "Match" uses (`guess_episode`/`guess_movie`) to pre-fill the search form with a
show title + season/episode, or a movie title + year — all editable before searching,
and usable on their own with no video loaded at all if you'd rather just type a title.
Results show the release name, language, an **HD** badge, a **Hash match** badge when
OpenSubtitles matched the file by an exact content hash rather than a text search (a
much stronger signal than a title match alone), an **HI** badge for hearing-impaired
tracks, the uploader, and the download count. Downloading saves the file right next to
the video with the language code inserted before the extension (`Show.S01E01.mkv` →
`Show.S01E01.en.srt`) — the convention most media players look for automatically, so
there's nothing further to configure.
OpenSubtitles splits credentials in a way no other source in this app does:
- **Searching** only needs a free **API key** — register an account at
[opensubtitles.com](https://www.opensubtitles.com/) and create one under Account → API
Consumers, then paste it into Subtitle Settings.
- **Downloading** additionally needs your own OpenSubtitles account **username and
password**, entered in the same Settings panel — OpenSubtitles logs you in to check a
daily download quota tied to your account (shown after every successful download as
"N/M downloads left today"; the exact number depends on your account's standing with
OpenSubtitles and can change on their end, so Aurora Curator always shows the live
figure it gets back rather than a hardcoded promise). Search works with no login at
all, so you can browse what's available before deciding whether to save a password.
Like every other credential in this app, both are stored locally in the OS app-config
directory and only ever sent to `api.opensubtitles.com` — with one extra precaution
specific to a real account password: Aurora Curator never sends the saved password back
to its own interface once it's saved (every other credential's "already saved" check
does briefly see the real value; the password doesn't), and the Settings panel only ever
shows *whether* a password is on file, never the value itself.
## SFV
The SFV sidebar panel computes and verifies file checksums — CRC32, MD5, or SHA1 — and
reads and writes the classic sidecar checksum-file formats, recreating original
FileBot's SFV panel. Unlike every other panel added this session, it's **entirely
local**: no network access, no credentials, nothing to configure.
**Load Files…** picks one or more files, then choose a checksum type from the segmented
control (CRC32 defaults to the traditional `.sfv` file format; MD5/SHA1 use the GNU
coreutils `md5sum`/`sha1sum` sidecar convention instead). **Compute** hashes every loaded
file (streamed in 64KB chunks, so even large video files don't need to fit in memory) and
lists each one's checksum, or an **ERROR** badge for any individual file that couldn't be
read — one bad file never aborts the rest of the batch. **Save Checksum File…** opens a
native Save dialog (defaulted to the loaded files' own folder name, e.g. `movies.sfv`)
and writes every successfully-computed checksum out in the chosen format; only files that
computed successfully are included.
**Verify Checksum File…** goes the other direction: pick an existing `.sfv`/`.md5`/
`.sha1` file (no need to have loaded its files first) and every entry it lists is
resolved against the real files sitting next to it on disk and reported as **OK**,
**MISMATCH** (actual checksum shown alongside the expected one), or **MISSING** (the
referenced file isn't there). Checksum comparison is case-insensitive, since some tools
write hex digests in uppercase and others in lowercase.
## Filter
The Filter sidebar panel loads a batch of files (independent of whatever's loaded in the
Rename panel) and narrows it down before you decide what to do with it elsewhere — copy
the matching paths out, or just get a sense of what's actually in a folder. It scans every
loaded file with the exact same local, filename-only matchers "Match" uses
(`guess_episode`/`guess_movie`/`guess_release_info`) and turns the results into filter
criteria instead of a rename preview — no network calls, so scanning even a large batch is
effectively instant. It also runs the same batched `probe_media_files` real container-header
probe "Match" and List do (see "Real media-file probing" below), so a file's resolution/
video codec can come from real probing even when nothing in its filename says so.
**Load Files…** loads and scans a batch in one step. Toggleable chips then appear for
every **extension** actually present (e.g. MKV/MP4/TXT) and every **kind** Aurora Curator
locally recognized (TV/Movies/Unmatched) — click a chip to exclude that group, click it
again to bring it back. **Resolution** and **Video codec** chip rows appear the same way
whenever at least one loaded file actually has a value for either (a filename tag wins
when present, otherwise a real probed value fills in — same override rule as everywhere
else this delivery touches; files with neither fall into their own "Unknown" chip rather
than being silently excluded). A **filename contains** field does a live, case-insensitive
substring search, and a **season** field (shown only while the TV chip is enabled) narrows
to a single season number, automatically excluding anything that isn't a TV episode in
that season. Each result row's subtitle also shows a resolved resolution when one exists
(e.g. `Unmatched · 480p` for a file Match couldn't identify as TV or a movie but still got
a real probed resolution for). The results list updates live with an "N of M files match"
count, and **Copy File Paths** writes every currently-matching file's full path, one per
line, to the clipboard.
## List
The List sidebar panel loads a batch of files (independent of whatever's loaded
elsewhere) and renders a naming-scheme-style expression against each one, so the result
can be copied or exported as a plain text list — a playlist, a batch-rename preview to
check by eye, a checklist for another tool, whatever the expression is written for.
It's the last of the six original sidebar panels (Rename/Episodes/Subtitles/SFV/Filter/
List) and, like Filter, adds no new local-matching logic — it reuses the exact same
`guess_episode`/`guess_movie`/`guess_release_info` calls "Match" and Filter already use.
The one genuinely new piece is a single Tauri command, `export_text_list`, which opens a
native Save dialog and writes whatever text the frontend hands it to a `.txt` file —
everything else (matching, and rendering each file's line) reuses commands that already
existed.
**Load Files…** loads a batch and scans it locally, the same way Filter does — including
the same batched real container-header probe (see "Real media-file probing" below), so
List's expressions get real probed values too, not just filename-tag ones. The **Format**
field holds the expression rendered against each file — the exact same `{ }` syntax the
naming scheme uses, run through the same `render_expression` command the Rename panel's
live preview calls. `{name}`/`{ext}` always work (they're the file's own name/extension);
`{show name}`/`{season number}`/`{episode number}`, `{movie name}`/`{year}`, and the
release-tag fields (`{resolution}`/`{source}`/`{video codec}`/`{audio codec}`/`{release
group}`) fill in once the local scan recognizes a file; `{width}`/`{height}`/`{fps}`/
`{duration}`/`{file size}` come straight from real probing (no filename-tag equivalent
exists), and a probed `{resolution}`/`{video codec}`/`{audio codec}` fills in whenever the
filename didn't already carry that tag — there's no Fetch Data step here, List stays
local-only like Filter. A file whose rendered line comes out empty (e.g. an expression
that only references a field this particular file never matched) shows an "(empty)"
placeholder rather than blocking the rest of the list, and the results header tracks
"N of M lines rendered" live as the expression changes. **Copy List** copies every line, in
file order, newline-joined, to the clipboard; **Export to File…** opens a native Save
dialog (defaulted to `list.txt`) and writes the same text to disk.
## Real media-file probing
Unlike every panel above, this isn't a new sidebar screen — it's a naming-scheme
binding source wired into the Rename panel's "Match" step, the Filter panel's scan, and
the List panel's scan alike, closing (part of) the gap the porting roadmap has called out
since the beginning: FileBot's
`MediaBindingBean` doesn't just parse tags out of a release name, it also reads a
video file's own container header for its real resolution, frame rate, duration, and
codecs — a naming scheme can reference those even when the filename itself doesn't
carry a tag for them, or came from a mislabeled/renamed-already release. Aurora
Curator ships no ffmpeg/MediaInfo dependency for this — two pure-Rust container
parsers do the actual reading: the `mp4` crate for ISO-BMFF containers (`.mp4`/`.m4v`/
`.mov`) and the `matroska` crate for EBML/Matroska containers (`.mkv`/`.webm`). Any
other extension, or a file that fails to parse as its container, just yields no probed
values — probing is always a best-effort enrichment layered on top of filename
matching, never a hard requirement, so an unsupported or corrupt file can't break
Match.
Clicking **Match** (Rename), or loading a batch into **Filter** or **List**, now also
probes every loaded file's real container header (`probe_media_files`, one batched Tauri
round trip off the main thread) alongside the existing filename-based guessing — all
three panels call the exact same command the exact same way. The result feeds the same
bindings the release-tag parser already populates — `{resolution}`/`{vf}`, `{video
codec}`/`{vc}`, `{audio codec}`/`{ac}` — but only when the filename itself didn't already
carry that tag; an explicit release-group tag always wins over a probed value, since a
release group's own tag is usually deliberate. Five bindings have no filename-tag
equivalent at all, so they're only ever available this way: `{width}`, `{height}`,
`{fps}`, `{duration}` (`h:mm:ss`, or `m:ss` under an hour), and `{file size}`
(human-readable, e.g. `1.4 GB`) — all formatted once on the Rust side so the UI never
reimplements that logic. Rename's Match status line, List's scan status, and Filter's
scan status all report how many loaded files were actually probed (`"N probed from
file"`), the naming-scheme editor's field-reference list has a "Real file probing" group
listing all five width/height/fps/duration/file-size bindings, and the Filter panel adds
its own **Resolution**/**Video codec** chip rows (populated from the same merged
filename-tag-or-probed value) so a batch can be narrowed by either, not just by
extension/kind/name/season.
## Hash Files
A naming-scheme binding source, same idea as real media-file probing above, but for
`{crc32}`/`{md5}`/`{sha1}` — reuses the SFV panel's own hasher
(`aurora_curator_core::sfv`) rather than a separate implementation. Unlike Match's other
local steps (filename parsing, container-header probing), this is deliberately **not**
wired into Match automatically: a checksum needs the file's *entire* contents streamed
through, not just a small header or filename, so hashing a batch of large video files
can take real time — running it unasked on every Match click would make Match
unpredictably slow for schemes that don't even use a hash field. Instead, the Rename
panel's New Names list has its own **Hash Files** button next to Fetch Data; clicking it
computes all three checksums for every loaded file in one pass per file (not three
separate passes) and fills in `{crc32}`/`{md5}`/`{sha1}` for whichever files succeeded,
reporting how many hashed vs. failed (a file that's moved or deleted between loading and
hashing, a permissions problem, etc. — kept per-file, so one bad file doesn't block the
rest of the batch). The naming-scheme editor's field-reference list has a "File hashing"
group with all three as clickable buttons.
## Porting roadmap (not yet done)
Roughly in priority order — each references the original FileBot source file it's
ported from, for anyone picking this up:
1. **Full naming-scheme bindings** — `net.filebot.format.MediaBindingBean` (1354 lines):
all the `{n}`, `{s00e00}`, `{airdate}`, `{vf}` (video format), etc. binding
properties. A filename-tag-based subset is ported and wired to "Match"
(`guess_release_info` in `crates/aurora-curator-core/src/matching`): `{vf}`/`{resolution}`,
`{source}`, `{vc}`/`{video codec}`, `{ac}`/`{audio codec}`, `{group}`/`{release
group}`. Real container probing (`crates/aurora-curator-core/src/probe`, see "Real
media-file probing" below) now covers the other half MediaBindingBean derives from
actually reading the file — real resolution/video codec/audio codec fill in
whenever the filename didn't already carry that tag, plus `{width}`/`{height}`/
`{fps}`/`{duration}`/`{file size}`, which have no filename-tag equivalent at all —
for the two container families it supports (MP4/M4V/MOV and MKV/WebM). Probed
bindings are now wired into all three panels that do local matching — Rename's
Match step, and the Filter and List panels' own scans (Filter also gained
Resolution/Video codec filter chips from it). File hashing is done too —
`{crc32}`/`{md5}`/`{sha1}` reuse the SFV panel's own hasher (all three computed in a
single pass over the file, rather than reading it three times), wired to the Rename
panel's own "Hash Files" button rather than Match, since a full-file read is much
slower than everything else Match does and shouldn't run unasked for every batch
(see "Hash Files" below). Still missing: probing for other containers (.avi, .wmv,
...) and audio-only metadata (bitrate, sample rate, channel layout).
2. **String/expression helper methods** — `net.filebot.format.ExpressionFormatMethods`
(636 lines): FileBot-specific Groovy string extensions like `.pad()`, `.sortName()`.
Ported so far (`crates/aurora-curator-core/src/format/functions.rs`): `any`, `all_of`,
`concat`, plus a newer batch of naming/scripting helpers — `pad`/`pad_with`
(left-pad, non-mutating; note this is a deliberately different name collision-avoidance
from Rhai's own built-in `pad(length, char)`, which right-pads and mutates in place —
not a fit for FileBot's zero-padded season/episode numbers), `sort_name`/`sort_initial`
(moves a leading "The"/"A"/"An" article to the end, e.g. `{n.sort_name()}` turns
`"The Office"` into `"Office, The"`), `upper_initial`/`lower_initial`, `acronym`,
`validate_file_name` (strips characters illegal in filenames, collapses whitespace,
trims trailing dots), and `space`/`space_with`/`colon`/`colon_with` (separator/colon
normalization). Rhai's built-ins cover some of the rest already. Still not ported:
FileBot's CSV/file-reading and shell-quoting helpers (`csv`, `read_lines`,
`quote_bash`, `quote_powershell`, etc.).
3. **Matching/similarity engine** — `net.filebot.similarity.*`. Local filename-based
season/episode detection is done (`crates/aurora-curator-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), 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/aurora-curator-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 — Aurora Curator 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. Three are done: TMDB, for both TV and movies
(`crates/aurora-curator-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 —
TVmaze, for TV only (`crates/aurora-curator-core/src/web/tvmaze.rs`, same `reqwest` +
`serde_json` shape, no auth at all — TVmaze has no movie catalog to search, and its
per-show episode endpoint returns every season in one flat list rather than TMDB's
per-season lookup, so the client fetches once and filters locally, skipping any
unnumbered/special episodes rather than panicking on them) — and OMDb, for both TV
and movies (`crates/aurora-curator-core/src/web/omdb.rs`, same `reqwest` + `serde_json`
shape but a single flat root-URL REST API rather than TMDB's per-resource paths, plain
`apikey` query-param auth rather than a Bearer token, and human-text release dates
(`"16 Jul 2010"`/`"N/A"`) normalized to ISO form locally; a movie search result only
carries a bare year, so `lookup_movie` follows up with a by-ID detail request to get
the actual release date). All three are wired to "Fetch
Data" — independent "TV episode source" (TMDB/TVmaze/OMDb) and "Movie source"
(TMDB/OMDb) pickers in Fetch Data Settings choose which source a batch's TV and movie
items resolve against respectively (TMDB stays the default for both; a batch can mix
sources across its TV and movie halves) — disambiguating multi-result searches via
the similarity-scoring pass described in item 3 above rather than blindly trusting
any source's top-ranked hit. The same per-season episode-list methods these three
clients already exposed for Fetch Data are also what the new Episodes panel browses
directly (see "Episodes" above) — no new client code, just a new UI on top of what
was already there. **OpenSubtitles is also done, but as a subtitle source
rather than a fourth title-metadata source** — `crates/aurora-curator-core/src/web/opensubtitles.rs`,
wired to the new Subtitles panel (see "Subtitles" above) rather than to Fetch Data's
TV/movie pickers, since it fetches subtitle files, not episode/movie titles. All
other sources (TheTVDB, AniDB) are not yet started — **TheTVDB was deliberately
skipped**: as of this writing its v4 API requires either a paid commercial license or
the "user-supported" model, where every end user needs their own paid TheTVDB
subscription (~$12/year) just to get the PIN its login flow requires, unlike the
three free-to-use sources above — worth revisiting if that changes.
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
done (`crates/aurora-curator-core/src/rename`, wired to the UI's Rename button behind a
confirmation step) including the `move`-into-subfolder `--action` variant — a naming
scheme like `{n}/{s00e00}` organizes files into subfolders, creating any missing
folders automatically, always scoped to the file's own directory tree. The
`copy`/`symlink` `--action` variants are done too — an action selector in the
confirmation modal picks Move/Copy/Symlink for the whole batch; Copy and Symlink both
leave the source file untouched and produce a new file/link at the computed target,
and both are undoable from the History panel the same as Move (undo for Copy/Symlink
just removes what was created, since there's nothing to move back). The `--conflict`
variants are done too — a second selector in the same modal picks Skip (default,
leaves the conflicting file alone), Auto-number (`"Name (1).ext"`, counting up to the
first free slot), or Override (replaces the conflicting file outright — the one
irreversible option, called out with red styling in the UI; undoing that batch later
restores the *source*, never what got overwritten, since undo always uses its own
fixed skip-on-conflict policy no matter which policy the original rename used). The
CLI flag surface is done too — `-rename`/`--format`/`--db`/`--action`/`--conflict`/
`-non-strict`/`-h` (`crates/aurora-curator-core/src/cli` for pure argument parsing and
directory expansion, `src-tauri/src/cli.rs` for the orchestration — local matching,
optional TMDB/TVmaze/OMDb lookup, and the actual rename, all without ever constructing a
Tauri `App`); see "Command-line usage" above. `--output <dir>` and `-script`/the rest
of FileBot's non-rename CLI modes are not ported. `-get-subtitles` has no CLI analog
yet — subtitle fetching (see "Subtitles" above) is GUI-only so far, reached from its
own sidebar panel rather than a command-line flag.
7. **The rest of the UI** — the Rename panel (file loading, drag-drop, live preview,
local TV/movie matching, confirmed rename with per-item exclude, rename history/undo,
Align Up/Down reordering, naming-scheme Presets with `1`-`9` hotkeys) is built, and so
are the Subtitles panel (search + download via OpenSubtitles, see "Subtitles" above),
the Episodes panel (series search + season browse with a Copy list export, no
credentials of its own, see "Episodes" above), the SFV panel (compute/verify
CRC32/MD5/SHA1 checksums, read/write `.sfv`/`.md5`/`.sha1` sidecar files, entirely
local with no credentials at all, see "SFV" above), the Filter panel (extension/
kind/filename/season narrowing over a locally-scanned batch, no new Rust/Tauri
surface at all, see "Filter" above), and the List panel (a naming-scheme-style
expression rendered per file via the same `render_expression` engine, with a Copy
List clipboard export and an Export to File… `.txt` writer behind one new
`export_text_list` command, see "List" above) — **all six of the original sidebar
panels are now real screens**, closing out the panel set FileBot itself shipped.
Align Up/Down is button-based
(swaps the selected file with its neighbor), not the original's
drag-and-drop-within-a-list interaction.
2026-08-29 03:44:38 +00:00
8. **Undo/history for renames** — done, as a flat JSON-backed undo stack rather than a
full port of `net.filebot.HistoryDialog`'s session/element log
(`crates/aurora-curator-core/src/history`, `src-tauri/src/history.rs`). Every successful
2026-08-29 03:44:38 +00:00
rename is recorded to a `history.json` in the OS app-config directory (same location
as `settings.json`); the History panel lists past batches and can revert one back to
its original paths, skipping (never overwriting) any file whose original path now
has something else at it. Capped at the 50 most recent batches.
9. **Packaging** — code signing, auto-update, installers per platform via
`npm run tauri build`.