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:
+73
-17
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user