Add OMDb as a datasource: TV episodes (third option alongside TMDB/TVmaze) and movies (new second option alongside TMDB)

Movies get their own independent metadata-source setting for the first
time (previously hardcoded to TMDB) so the GUI's Fetch Data Settings now
has two separate pickers - TV episode source (TMDB/TVmaze/OMDb) and
movie source (TMDB/OMDb) - and a batch can freely mix sources across its
TV and movie halves. The -rename CLI's --db flag gained omdb alongside
tmdb/tvmaze, with movies now looked up online for both tmdb and omdb.
This commit is contained in:
Renamr Scaffold
2026-08-29 12:25:45 +00:00
parent 22a3da9f6f
commit c158befa83
11 changed files with 1234 additions and 174 deletions
+73 -17
View File
@@ -25,12 +25,13 @@ use aurora_curator_core::format::ExpressionFormat;
use aurora_curator_core::matching::{self, ReleaseInfo};
use aurora_curator_core::rename::{self, RenameOutcome, RenamePlanItem};
use aurora_curator_core::string_bindings;
use aurora_curator_core::web::omdb::OmdbClient;
use aurora_curator_core::web::tmdb::TmdbClient;
use aurora_curator_core::web::tvmaze::TvmazeClient;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use crate::metadata::AnyTvClient;
use crate::metadata::{AnyMovieClient, AnyTvClient, MovieLookup};
use crate::settings;
const USAGE: &str = "\
@@ -41,16 +42,16 @@ Usage:
Options:
--format <expr> Naming scheme, e.g. \"{show name} - {season number}x{episode number} - {episode name}\" (required)
--db <tmdb|tvmaze> Resolve TV episodes online against this source before rendering names (optional;
movies are only looked up online when this is tmdb - TVmaze has no movie catalog)
--db <tmdb|tvmaze|omdb> Resolve TV episodes online against this source before rendering names (optional;
movies are also looked up online when this is tmdb or omdb - TVmaze has no movie catalog)
--action <move|copy|symlink|test> What to do with each file (default: move; \"test\" prints the plan without touching anything)
--conflict <skip|auto-number|override> What to do when a computed target already exists (default: skip)
-non-strict Accepted for FileBot script compatibility; has no effect (local matching has no
confidence score to loosen - see the aurora_curator_core::cli module doc)
-h, --help Show this help and exit
A --db source needs a TMDB token already saved through the app's own Fetch Data Settings at least once -
the CLI never accepts a token as a command-line argument.
A --db tmdb/omdb source needs the matching API token/key already saved through the app's own Fetch Data
Settings at least once - the CLI never accepts a token or key as a command-line argument.
";
/// Entry point called from `main.rs`. Returns the process exit code.
@@ -93,8 +94,8 @@ fn run_rename(args: RenameCliArgs) -> i32 {
};
// Resolved once up front (not per-file) so a batch never ends up split across two
// different saved-token states, and so a missing token fails fast instead of after
// however many files were already processed.
// different saved-token states, and so a missing token/key fails fast instead of
// after however many files were already processed.
let tmdb_token = if args.db == Some(TvDbChoice::Tmdb) {
match settings::cli_tmdb_token() {
Some(token) => Some(token),
@@ -108,6 +109,19 @@ fn run_rename(args: RenameCliArgs) -> i32 {
} else {
None
};
let omdb_api_key = if args.db == Some(TvDbChoice::Omdb) {
match settings::cli_omdb_api_key() {
Some(key) => Some(key),
None => {
eprintln!(
"error: --db omdb needs an OMDb API key already saved through the app's own Fetch Data Settings"
);
return 2;
}
}
} else {
None
};
let tv_client = match args.db {
None => None,
@@ -115,10 +129,22 @@ fn run_rename(args: RenameCliArgs) -> i32 {
Some(TvDbChoice::Tmdb) => Some(AnyTvClient::Tmdb(TmdbClient::new(
tmdb_token.clone().expect("checked above"),
))),
Some(TvDbChoice::Omdb) => Some(AnyTvClient::Omdb(OmdbClient::new(
omdb_api_key.clone().expect("checked above"),
))),
};
// Movies get an online lookup whenever --db tmdb or --db omdb was explicitly
// passed - TVmaze has no movie catalog, and no --db at all means "local matching
// only" for both.
let movie_client = match args.db {
Some(TvDbChoice::Tmdb) => Some(AnyMovieClient::Tmdb(TmdbClient::new(
tmdb_token.clone().expect("checked above"),
))),
Some(TvDbChoice::Omdb) => Some(AnyMovieClient::Omdb(OmdbClient::new(
omdb_api_key.clone().expect("checked above"),
))),
Some(TvDbChoice::Tvmaze) | None => None,
};
// Movies only get an online lookup when --db tmdb was explicitly passed - TVmaze
// has no movie catalog, and no --db at all means "local matching only" for both.
let movie_client = tmdb_token.map(TmdbClient::new);
let items = tauri::async_runtime::block_on(build_plan(&files, &compiled, tv_client, movie_client));
if items.is_empty() {
@@ -219,7 +245,7 @@ async fn build_plan(
files: &[PathBuf],
compiled: &ExpressionFormat,
tv_client: Option<AnyTvClient>,
movie_client: Option<TmdbClient>,
movie_client: Option<AnyMovieClient>,
) -> Vec<RenamePlanItem> {
// Cache per (series_query, season), same as the GUI's own `run_fetch` - a whole
// season's worth of files only needs one online search + episode-list fetch.
@@ -277,16 +303,18 @@ async fn build_plan(
}
if let Some(client) = &movie_client {
if let Ok(info) = client.lookup_movie(&movie.title, movie.year).await {
bindings.insert("n".to_string(), info.title.clone());
bindings.insert("movie name".to_string(), info.title.clone());
if let Some(release_date) = &info.release_date {
if let Ok(MovieLookup::Found { title, release_date }) =
client.lookup_movie(&movie.title, movie.year).await
{
bindings.insert("n".to_string(), title.clone());
bindings.insert("movie name".to_string(), title);
if let Some(release_date) = &release_date {
bindings.insert("airdate".to_string(), release_date.clone());
// Only overwrite the locally-guessed year if Match didn't
// already find one in the filename itself - same rule
// RenamePanel.tsx's Fetch Data handler uses, since a
// filename-carried year is usually more precise than TMDB's
// release date for disambiguating remakes.
// filename-carried year is usually more precise than the
// online source's release date for disambiguating remakes.
if movie.year.is_none() {
let year: String = release_date.chars().take(4).collect();
bindings.insert("y".to_string(), year.clone());
@@ -492,4 +520,32 @@ mod tests {
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn run_fails_cleanly_when_db_omdb_is_requested_without_a_saved_key() {
// Mirrors `run_fails_cleanly_when_db_tmdb_is_requested_without_a_saved_token`
// above, for the OMDb credential instead.
if settings::cli_omdb_api_key().is_some() {
eprintln!("skipping: a real OMDb API key is saved in this environment's config dir");
return;
}
let dir = temp_dir();
let file = dir.join("Breaking.Bad.S05E14.mkv");
std::fs::write(&file, b"hello").unwrap();
let code = run(vec![
"-rename".to_string(),
"--format".to_string(),
"{n}".to_string(),
"--db".to_string(),
"omdb".to_string(),
file.to_string_lossy().to_string(),
]);
assert_eq!(code, 2);
assert!(file.exists(), "should fail before touching any file");
std::fs::remove_dir_all(&dir).ok();
}
}