refactor: extract source-neutral playlist curation (#860)

* refactor: extract source-neutral playlist curation

* fix: keep curation matches within media kind

---------

Co-authored-by: Juanjo Presa <juanjop@gmail.com>
This commit is contained in:
Juanjo Presa
2026-09-09 20:40:53 +02:00
committed by GitHub
co-authored by Juanjo Presa
parent 019fb7fde2
commit 145b082a4f
24 changed files with 1574 additions and 1200 deletions
@@ -9,7 +9,6 @@ use crate::{
processor::{
epg::{clear_invalid_live_epg_ids, process_playlist_epg, retain_epg_referenced_by_groups},
sort::sort_playlist,
trakt::process_trakt_categories_for_target,
xtream_series::playlist_resolve_series,
xtream_vod::playlist_resolve_vod,
StalkerRefreshMode,
@@ -17,7 +16,7 @@ use crate::{
};
use futures::{FutureExt, StreamExt};
use indexmap::IndexMap;
use log::{debug, error, info, log_enabled, warn, Level};
use log::{debug, error, info, log_enabled, trace, warn, Level};
use path_clean::PathClean;
use shared::{
concat_string,
@@ -52,6 +51,7 @@ use tuliprox_core::{
},
utils::{debug_if_enabled, log_memory_snapshot, trace_if_enabled, StepMeasure, StepMeasureCallback},
};
use tuliprox_curation::curate_trakt_categories;
use tuliprox_iptv::{
epg::{CountingEpgSink, EpgFetchRequest, EpgProvider},
error::ProviderErrorKind,
@@ -387,7 +387,7 @@ pub(crate) async fn finalize_prepared_target<E: EventSink + Clone + 'static, M:
(Ok(()), errors)
} else {
// Process Trakt categories
if trakt_playlist(&ctx.client, target, &mut errors, &mut new_playlist).await {
if trakt_playlist(&ctx.client, target, &mut new_playlist).await {
step.tick("trakt categories");
log_memory_snapshot(format!("target '{}' after_trakt", target.name).as_str());
}
@@ -767,23 +767,18 @@ pub fn process_favourites(playlist: &mut Vec<PlaylistGroup>, favourites_cfg: Opt
pub(crate) async fn trakt_playlist(
client: &reqwest::Client,
target: &ConfigTarget,
errors: &mut Vec<TuliproxError>,
playlist: &mut Vec<PlaylistGroup>,
) -> bool {
match process_trakt_categories_for_target(client, playlist, target).await {
Ok(Some(trakt_categories)) => {
if !trakt_categories.is_empty() {
info!("Adding {} Trakt categories to playlist", trakt_categories.len());
playlist.extend(trakt_categories);
}
}
Ok(None) => {
return false;
}
Err(trakt_errors) => {
warn!("Trakt processing failed with {} errors", trakt_errors.len());
errors.extend(trakt_errors);
}
let Some(trakt_config) = target.get_xtream_output().and_then(|output| output.trakt.as_ref()) else {
trace!("No Trakt configuration found for target {}", target.name);
return false;
};
let Some(trakt_categories) = curate_trakt_categories(client, playlist, &target.name, trakt_config).await else {
return false;
};
if !trakt_categories.is_empty() {
info!("Adding {} Trakt categories to playlist", trakt_categories.len());
playlist.extend(trakt_categories);
}
true
}
@@ -1483,6 +1483,17 @@ match {
}
}
#[tokio::test]
async fn trakt_finalization_stage_is_a_noop_without_xtream_configuration() {
let target = ConfigTarget::from(&ConfigTargetDto::default());
let mut playlist = Vec::new();
let applied = trakt_playlist(&reqwest::Client::new(), &target, &mut playlist).await;
assert!(!applied);
assert!(playlist.is_empty());
}
#[cfg(test)]
mod disk_epg_wireup_tests {
use super::spill_epg_to_disk;