feat(meta,app,android,ios,cli): film backdrops and logos from TMDB by id

TVDB has no films, so ani.zip has no fanart or logo for one, and most of
the titles in a real library with nothing wide to draw were films. ani.zip
does name the TMDB entry of every AniList match. With a TMDB key stored
and AniList chosen, an entry left without a backdrop or a logo is asked
for /{movie,tv}/{id}/images: one request, by id, so no title name goes to
TMDB. A bare backdrop and a PNG logo in the viewer's language are
preferred, and a failure there only costs the art.

The TMDB key field is now offered with either provider, labelled optional
with AniList. TMDB as the provider fetches its logo the same way, and
backdrops at w1280 rather than w780. A forced run re-enriches hand-picked
titles too, so saving a key and matching everything again reaches them.
This commit is contained in:
nvrl committed 2026-10-29 16:50:01 +01:00
1 parent 70952fa7ca
commit a7b87cdc79
15 files changed
+413 -89

No files matched your search

+16 -16
View File
@@ -546,15 +546,16 @@ async fn metadata(dirs: &AppDirs, command: MetadataCommand) -> Result<()> {
settings.provider.label(),
settings.provider.description()
);
if settings.provider.needs_api_key() {
println!(
"api key: {}",
match pstr_meta::settings::api_key(settings.provider) {
Some(_) => "stored in the credential store",
None => "not set — run `pstr metadata set --api-key`",
}
);
}
let stored_key = pstr_meta::settings::api_key(ProviderId::Tmdb).is_some();
println!(
"tmdb key: {}",
match (stored_key, settings.provider.needs_api_key()) {
(true, true) => "stored in the credential store",
(true, false) => "stored — used for the art of films TVDB has none for",
(false, true) => "not set — run `pstr metadata set --api-key`",
(false, false) => "not set (optional, for film art)",
}
);
let catalog = Catalog::open(&dirs.catalog_db()).context("open catalog")?;
let stored = catalog.all_metadata()?;
@@ -594,12 +595,11 @@ async fn metadata(dirs: &AppDirs, command: MetadataCommand) -> Result<()> {
}
if api_key {
let key = rpassword::prompt_password(format!(
"{} API key (empty to forget it): ",
settings.provider.label()
))
.context("read the API key")?;
pstr_meta::settings::set_api_key(settings.provider, &key)
// TMDB's is the only key there is: the provider with TMDB
// chosen, and film art with AniList.
let key = rpassword::prompt_password("TMDB API key (empty to forget it): ")
.context("read the API key")?;
pstr_meta::settings::set_api_key(ProviderId::Tmdb, &key)
.context("store the API key")?;
}
@@ -638,7 +638,7 @@ async fn metadata(dirs: &AppDirs, command: MetadataCommand) -> Result<()> {
let service = pstr_meta::MetadataService::new(
&settings,
pstr_meta::settings::api_key(settings.provider),
pstr_meta::settings::api_key(ProviderId::Tmdb),
)
.context("build the metadata provider")?;