fetching tmdb data for series

This commit is contained in:
euzu
2025-12-19 15:57:46 +01:00
parent 475201d640
commit aa4463b632
20 changed files with 479 additions and 161 deletions
+183 -61
View File
@@ -1,12 +1,12 @@
use crate::library::{MediaMetadata, MetadataAsyncIter, MetadataCacheEntry};
use crate::model::{AppConfig, ConfigInput, XtreamSeriesInfo, XtreamSeriesInfoEpisode, XtreamSeriesInfoEpisodeInfo, XtreamSeriesInfoInfo};
use crate::model::{XtreamMovieData, XtreamMovieInfo, XtreamMovieInfoDetails};
use crate::model::{AppConfig, ConfigInput};
use serde_json::value::RawValue;
use shared::error::TuliproxError;
use shared::model::{PlaylistGroup, PlaylistItem, PlaylistItemHeader, PlaylistItemType, XtreamCluster};
use shared::utils::string_to_uuid_type;
use std::sync::Arc;
use shared::utils::{generate_playlist_uuid, string_to_uuid_type};
use std::path::Path;
use std::sync::Arc;
pub async fn get_library_playlist(_client: &reqwest::Client, app_config: &Arc<AppConfig>, input: &Arc<ConfigInput>) -> (Vec<PlaylistGroup>, Vec<TuliproxError>) {
let config = &*app_config.config.load();
@@ -31,12 +31,12 @@ pub async fn get_library_playlist(_client: &reqwest::Client, app_config: &Arc<Ap
match entry.metadata {
MediaMetadata::Movie(_) => {
let pli = to_playlist_item(&entry, &input.name, &library_config.playlist.movie_category);
group_movies.channels.push(pli);
},
group_movies.channels.extend(pli);
}
MediaMetadata::Series(_) => {
let pli = to_playlist_item(&entry, &input.name, &library_config.playlist.series_category);
group_series.channels.push(pli);
},
group_series.channels.extend(pli);
}
}
}
@@ -51,47 +51,78 @@ pub async fn get_library_playlist(_client: &reqwest::Client, app_config: &Arc<Ap
(groups, vec![])
}
fn to_playlist_item(entry: &MetadataCacheEntry, input_name: &str, group_name: &str) -> PlaylistItem {
fn to_playlist_item(entry: &MetadataCacheEntry, input_name: &str, group_name: &str) -> Vec<PlaylistItem> {
let metadata = &entry.metadata;
let (xtream_cluster, item_type, additional_props): (XtreamCluster, PlaylistItemType, Option<Box<RawValue>>) = {
match metadata {
MediaMetadata::Movie(_) => {
let add_props = metadata_cache_entry_to_xtream_movie_info(entry).and_then(|info| {
let raw = serde_json::to_string(&info).ok()?;
RawValue::from_string(raw).ok()
});
(XtreamCluster::Video, PlaylistItemType::LocalVideo, add_props)
}
MediaMetadata::Series(_s) => {
(XtreamCluster::Series, PlaylistItemType::LocalSeriesInfo, None)
}
match metadata {
MediaMetadata::Movie(_) => {
let additional_properties = metadata_cache_entry_to_xtream_movie_info(entry).and_then(|info| {
let raw = serde_json::to_string(&info).ok()?;
RawValue::from_string(raw).ok()
});
vec![PlaylistItem {
header: PlaylistItemHeader {
uuid: string_to_uuid_type(&entry.uuid),
name: metadata.title().to_string(),
group: group_name.to_string(),
title: metadata.title().to_string(),
url: format!("file://{}", entry.file_path),
xtream_cluster: XtreamCluster::Video,
additional_properties,
item_type: PlaylistItemType::LocalVideo,
input_name: input_name.to_string(),
..PlaylistItemHeader::default()
}
}]
}
};
MediaMetadata::Series(_series) => {
let mut items = vec![];
if let Some(xtream_series_info) = metadata_cache_entry_to_xtream_series_info(entry) {
let additional_properties = serde_json::to_string(&xtream_series_info).ok().and_then(|r| RawValue::from_string(r).ok());
PlaylistItem {
header: PlaylistItemHeader {
uuid: string_to_uuid_type(&entry.uuid),
id: String::new(),
virtual_id: 0,
name: metadata.title().to_string(),
chno: String::new(),
logo: String::new(),
logo_small: String::new(),
group: group_name.to_string(),
title: metadata.title().to_string(),
parent_code: String::new(),
audio_track: String::new(),
time_shift: String::new(),
rec: String::new(),
url: format!("file://{}", entry.file_path),
epg_channel_id: None,
xtream_cluster,
additional_properties: additional_props,
item_type,
category_id: 0,
input_name: input_name.to_string(),
},
let series_info = PlaylistItem {
header: PlaylistItemHeader {
uuid: string_to_uuid_type(&entry.uuid),
id: entry.uuid.clone(),
name: metadata.title().to_string(),
group: group_name.to_string(),
title: metadata.title().to_string(),
url: format!("file://{}", entry.file_path),
xtream_cluster: XtreamCluster::Series,
item_type: PlaylistItemType::LocalSeriesInfo,
input_name: input_name.to_string(),
additional_properties,
..PlaylistItemHeader::default()
}
};
items.push(series_info);
if let Some(episodes) = xtream_series_info.episodes.as_ref() {
for episode in episodes {
items.push(PlaylistItem {
header: PlaylistItemHeader {
id: episode.id.to_string(),
// we use parent_code for local series to find the parent series info and straighten the virtual_ids
parent_code: entry.uuid.clone(),
uuid: generate_playlist_uuid(&input_name, &episode.id.to_string(), PlaylistItemType::LocalSeries, &episode.direct_source),
name: episode.title.clone(),
group: group_name.to_string(),
title: episode.title.clone(),
url: episode.direct_source.clone(),
xtream_cluster: XtreamCluster::Series,
item_type: PlaylistItemType::LocalSeries,
category_id: 0,
input_name: input_name.to_string(),
additional_properties: episode.get_additional_properties(&xtream_series_info),
..Default::default()
}
});
}
}
}
items
}
}
}
@@ -112,33 +143,24 @@ pub fn metadata_cache_entry_to_xtream_movie_info(
let info = XtreamMovieInfoDetails {
kinopoisk_url: movie.tmdb_id.map(|id| format!("https://www.themoviedb.org/movie/{id}")),
tmdb_id: movie.tmdb_id.map(|id|id.to_string()),
tmdb_id: movie.tmdb_id.map(|id| id.to_string()),
name: Some(movie.title.clone()),
o_name: movie.original_title.clone(),
cover_big: movie.poster.clone(),
movie_image: movie.poster.clone(),
releasedate: movie.year.map(|y| format!("{y}-01-01")),
episode_run_time: movie.runtime,
youtube_trailer: None,
director: movie.directors.as_ref().map(|d| d.join(", ")),
youtube_trailer: movie.videos.as_ref().and_then(|v| v.iter().find(|video| video.site.eq_ignore_ascii_case("youtube")).map(|video| video.key.clone())),
actors: actor_names.clone(),
cast: actor_names,
genre: movie.genres.as_ref().map(|g| g.join(", ")),
description: movie.plot.clone(),
plot: movie.plot.clone(),
age: None,
mpaa_rating: movie.mpaa.clone(),
rating_count_kinopoisk: 0,
country: None,
backdrop_path: movie
.fanart
.as_ref()
@@ -151,7 +173,6 @@ pub fn metadata_cache_entry_to_xtream_movie_info(
.map(|p| vec![p.clone()])
})
.unwrap_or_default(),
duration_secs: movie.runtime.map(|r| (r * 60).to_string()),
duration: movie.runtime.map(|r| {
let h = r / 60;
@@ -161,9 +182,7 @@ pub fn metadata_cache_entry_to_xtream_movie_info(
video: Vec::new(),
audio: Vec::new(),
bitrate: 0,
rating: movie.rating.map(|r| format!("{r:.2}")),
runtime: movie.runtime.map(|r| (r * 60).to_string()),
status: Some("Released".to_string()),
@@ -172,12 +191,9 @@ pub fn metadata_cache_entry_to_xtream_movie_info(
let movie_data = XtreamMovieData {
stream_id: 0,
name: movie.title.clone(),
added: Some(entry.file_modified.to_string()),
category_id: 0,
category_ids: vec![0],
container_extension,
custom_sid: None,
direct_source: String::new(),
@@ -185,3 +201,109 @@ pub fn metadata_cache_entry_to_xtream_movie_info(
Some(XtreamMovieInfo { info, movie_data })
}
pub fn metadata_cache_entry_to_xtream_series_info(
entry: &MetadataCacheEntry,
) -> Option<XtreamSeriesInfo> {
let series = match &entry.metadata {
MediaMetadata::Movie(_) => return None,
MediaMetadata::Series(m) => m,
};
let actor_names = series.actors.as_ref().map(|a| a.iter().map(|a| a.name.clone()).collect::<Vec<_>>().join(", ")).unwrap_or_default();
let release_date = series.year.map(|y| format!("{y}-01-01")).unwrap_or_default();
let youtube_trailer = series.videos.as_ref().and_then(|v| v.iter().find(|video| video.site.eq_ignore_ascii_case("youtube")).map(|video| video.key.clone())).unwrap_or_default();
let info = XtreamSeriesInfoInfo {
name: series.title.clone(),
cover: series.poster.clone().unwrap_or_default(),
plot: series.plot.clone().unwrap_or_default(),
cast: actor_names,
director: series.directors.as_ref().map(|d| d.join(", ")).unwrap_or_default(),
genre: series.genres.as_ref().map(|d| d.join(", ")).unwrap_or_default(),
release_date: release_date.clone(),
releaseDate: release_date.clone(),
releasedate: release_date.clone(),
last_modified: series.last_updated.to_string(),
rating: series.rating.unwrap_or(0f64),
rating_5based: 0.0,
backdrop_path: series
.fanart
.as_ref()
.filter(|s| !s.is_empty())
.map(|f| vec![f.clone()])
.or_else(|| {
series.poster.as_ref()
.filter(|s| !s.is_empty())
.map(|p| vec![p.clone()])
}),
trailer: youtube_trailer.clone(),
youtube_trailer,
episode_run_time: String::from("0"),
category_id: 0,
tmdb_id: series.tmdb_id.clone(),
tmdb: series.tmdb_id.clone(),
year: series.year.clone(),
};
// seasons are not delivered through xtream get_series_info.
let seasons = Some(vec![]); /*series.seasons.as_ref()
.map(|seasons| seasons.iter().map(|season| XtreamSeriesInfoSeason {
air_date: season.air_date.clone().unwrap_or_default(),
episode_count: season.episode_count,
id: season.id,
name: season.name.clone(),
overview: season.overview.clone().unwrap_or_default(),
season_number: season.season_number,
vote_average: season.vote_average,
cover: season.poster_path.as_ref().cloned().unwrap_or_default(),
cover_big: season.poster_path.as_ref().cloned().unwrap_or_default(),
}).collect());
*/
let episodes = series.episodes.as_ref().map(|episodes| {
episodes.iter().filter(|episode| !episode.file_path.is_empty()).map(|episode| {
let container_extension = Path::new(&episode.file_path)
.extension()
.and_then(|s| s.to_str())
.map(ToString::to_string)
.unwrap_or_default();
let episode_release_date = episode.aired.as_ref().map(ToString::to_string).unwrap_or_default();
let tmdb_id = (episode.tmdb_id > 0).then_some(episode.tmdb_id);
XtreamSeriesInfoEpisode {
id: episode.id,
episode_num: episode.episode,
season: episode.season,
title: episode.title.clone(),
container_extension,
info: Some(XtreamSeriesInfoEpisodeInfo {
id: tmdb_id,
tmdb_id,
tmdb: tmdb_id,
season: episode.season,
release_date: episode_release_date.clone(),
releaseDate: episode_release_date.clone(),
releasedate: episode_release_date,
plot: episode.plot.clone().unwrap_or_default(),
duration_secs: episode.runtime.map(|r| r * 60).unwrap_or(0),
duration: episode.runtime
.map(|r| format!("{:02}:{:02}:00", r / 60, r % 60))
.unwrap_or_default(),
movie_image: episode.thumb.clone().unwrap_or_default(),
video: None,
audio: None,
bitrate: 0,
rating: episode.rating.unwrap_or(0.0),
}),
custom_sid: String::new(),
added: episode.file_modified.to_string(),
direct_source: episode.file_path.clone(),
}
}).collect::<Vec<_>>()
});
Some(XtreamSeriesInfo { seasons, info: Some(info), episodes })
}
@@ -334,7 +334,6 @@ async fn process_source(client: &reqwest::Client, app_config: Arc<AppConfig>, so
let (mut playlistgroups, mut error_list) = playlist_download_from_input(client, &app_config, input).await;
let (tvguide, mut tvguide_errors) = if error_list.is_empty() {
let working_dir = &app_config.config.load().working_dir;
epg::get_xmltv(client, input, working_dir).await
} else {
(None, vec![])