TMDB retry fixed and added cooldown. (#613)

New Features

Background probing for Live streams with configurable interval; new input options to trigger probing and toggle background resolution.
Configuration Changes

metadata_update reorganized into grouped sections: log, resolve, probe, ffprobe, tmdb.
FFprobe controls moved under ffprobe.* and now require explicit time/size units; new live ffprobe settings.
Added metadata_update.tmdb.cooldown (default 7d) and grouped TMDB settings.
Bug Fixes

TMDB no-match cooldown to prevent endless requeues.
Documentation

README and UI help updated to reflect grouped metadata_update fields and explanations.
This commit is contained in:
euzu
2026-02-26 14:41:02 +01:00
committed by GitHub
parent e54cb63634
commit 96a5512b65
27 changed files with 2563 additions and 1124 deletions
@@ -1,6 +1,6 @@
use crate::api::model::UpdateTask;
use crate::api::model::{ActiveProviderManager, ProviderHandle, ProviderIdType, ResolveReason, ResolveReasonSet};
use crate::library::MetadataResolver;
use crate::library::{MetadataResolver, MetadataStorage};
use crate::model::FetchedPlaylist;
use crate::model::{AppConfig, ConfigTarget, MetadataUpdateConfig};
use crate::model::{ConfigInput, ConfigInputFlags, InputSource};
@@ -18,6 +18,7 @@ use crate::repository::{
get_input_storage_path, persist_input_series_info_batch, MemoryPlaylistSource, PlaylistSource,
};
use crate::repository::{xtream_get_file_path, BPlusTreeQuery};
use crate::utils::ffmpeg::{ProbeFailureKind, ProbeUrlOutcome};
use crate::utils::{debug_if_enabled, xtream};
use log::{debug, error, info, log_enabled, trace, warn, Level};
use parking_lot::Mutex;
@@ -45,11 +46,11 @@ impl SeriesProbeSettings {
pub fn from_metadata_update(metadata_update: Option<&MetadataUpdateConfig>) -> Self {
let defaults = MetadataUpdateConfig::default();
Self {
timeout_secs: metadata_update.and_then(|cfg| cfg.ffprobe_timeout).unwrap_or(60),
timeout_secs: metadata_update.and_then(|cfg| cfg.ffprobe.timeout).unwrap_or(60),
analyze_duration_micros: metadata_update
.map_or(defaults.ffprobe_analyze_duration_micros, |cfg| cfg.ffprobe_analyze_duration_micros),
.map_or(defaults.ffprobe.analyze_duration_micros, |cfg| cfg.ffprobe.analyze_duration_micros),
probe_size_bytes: metadata_update
.map_or(defaults.ffprobe_probe_size_bytes, |cfg| cfg.ffprobe_probe_size_bytes),
.map_or(defaults.ffprobe.probe_size_bytes, |cfg| cfg.ffprobe.probe_size_bytes),
}
}
}
@@ -695,6 +696,7 @@ pub async fn update_series_metadata(
let mut fetched_new = false;
let mut properties_updated = false;
let mut probe_failure: Option<ProbeFailureKind> = None;
let display_id = series_id_opt.map_or_else(|| "StringID".to_string(), |v| v.to_string());
@@ -759,7 +761,12 @@ pub async fn update_series_metadata(
{
let config = app_config.config.load();
let library_config = config.library.as_ref();
let meta_resolver = MetadataResolver::new(library_config, client.clone());
let metadata_update_config = config.metadata_update.as_ref();
let tmdb_storage = metadata_update_config
.filter(|cfg| cfg.tmdb.enabled)
.map(|_| MetadataStorage::new(storage_path.clone()));
let meta_resolver =
MetadataResolver::from_config(library_config, metadata_update_config, client.clone(), tmdb_storage);
let mut meta = None;
let mut tried_title = false;
@@ -856,7 +863,7 @@ pub async fn update_series_metadata(
ep.title, ep.season, ep.episode_num, missing_reason
);
if let Some((_quality, raw_video, raw_audio)) = crate::utils::ffmpeg::probe_url(
match crate::utils::ffmpeg::probe_url(
&episode_url,
user_agent.as_deref(),
probe_settings.analyze_duration_micros,
@@ -865,15 +872,25 @@ pub async fn update_series_metadata(
)
.await
{
if let Some(v) = raw_video {
ep.video = Some(v.to_string().into());
properties_updated = true;
ProbeUrlOutcome::Success(_quality, raw_video, raw_audio) => {
if let Some(v) = raw_video {
ep.video = Some(v.to_string().into());
properties_updated = true;
}
if let Some(a) = raw_audio {
ep.audio = Some(a.to_string().into());
properties_updated = true;
}
probed_count += 1;
}
if let Some(a) = raw_audio {
ep.audio = Some(a.to_string().into());
properties_updated = true;
ProbeUrlOutcome::Failed(ProbeFailureKind::NotFound) => {
probe_failure = Some(ProbeFailureKind::NotFound);
}
ProbeUrlOutcome::Failed(ProbeFailureKind::Other) => {
if probe_failure.is_none() {
probe_failure = Some(ProbeFailureKind::Other);
}
}
probed_count += 1;
}
if let Some(h) = temp_handle {
@@ -897,6 +914,19 @@ pub async fn update_series_metadata(
}
}
let probe_only_unresolved = do_probe && !fetch_info && !resolve_tmdb && !properties_updated && !fetched_new;
if probe_only_unresolved {
if let Some(kind) = probe_failure {
let err = match kind {
ProbeFailureKind::NotFound => {
shared::error::info_err!("Probe failed with 404 Not Found for Series {display_id}")
}
ProbeFailureKind::Other => shared::error::info_err!("Probe failed for Series {display_id}"),
};
return Err(err);
}
}
// 4. Persist
if properties_updated || fetched_new {
if save {