use crate::utils::{deserialize_as_option_string, deserialize_as_string_array, deserialize_number_from_string, deserialize_number_from_string_or_zero, deserialize_as_string, string_default_on_null, string_or_number_u32, deserialize_json_as_opt_string, serialize_json_as_opt_string}; use serde::ser::SerializeMap; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Value; use std::collections::BTreeMap; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct XtreamVideoInfoMovieData { pub name: String, #[serde(default, deserialize_with = "deserialize_number_from_string_or_zero")] pub category_id: u32, #[serde(default, deserialize_with = "deserialize_number_from_string_or_zero")] pub stream_id: u32, #[serde(default)] pub direct_source: String, #[serde(default, deserialize_with = "deserialize_as_option_string")] pub custom_sid: Option, #[serde(default, deserialize_with = "deserialize_as_string")] pub added: String, #[serde(default)] pub container_extension: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct XtreamVideoInfoInfo { #[serde(default)] pub kinopoisk_url: Option, #[serde(default, deserialize_with = "deserialize_as_string")] pub tmdb_id: String, // is in get_vod_streams #[serde(default)] pub name: String, // is in get_vod_streams pub o_name: Option, pub cover_big: Option, pub movie_image: Option, #[serde(default, deserialize_with = "deserialize_as_option_string")] pub releasedate: Option, #[serde(default, deserialize_with = "deserialize_number_from_string")] pub episode_run_time: Option, pub youtube_trailer: Option, pub director: Option, pub actors: Option, pub cast: Option, pub description: Option, pub plot: Option, #[serde(default, deserialize_with = "deserialize_as_option_string")] pub age: Option, #[serde(default, deserialize_with = "deserialize_as_option_string")] pub mpaa_rating: Option, #[serde(default)] pub rating_count_kinopoisk: u32, pub country: Option, pub genre: Option, #[serde(default, deserialize_with = "deserialize_as_string_array")] pub backdrop_path: Option>, #[serde(default, deserialize_with = "deserialize_as_option_string")] pub duration_secs: Option, #[serde(default, deserialize_with = "deserialize_as_option_string")] pub duration: Option, #[serde(default, serialize_with = "serialize_json_as_opt_string", deserialize_with = "deserialize_json_as_opt_string")] pub video: Option, #[serde(default, serialize_with = "serialize_json_as_opt_string", deserialize_with = "deserialize_json_as_opt_string")] pub audio: Option, #[serde(default)] pub bitrate: u32, #[serde(default, deserialize_with = "deserialize_as_option_string")] pub runtime: Option, #[serde(default, deserialize_with = "deserialize_as_option_string")] pub status: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct XtreamVideoInfo { pub info: XtreamVideoInfoInfo, pub movie_data: XtreamVideoInfoMovieData, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct XtreamSeriesInfoSeason { #[serde(default, deserialize_with = "string_default_on_null")] pub air_date: String, #[serde(default, deserialize_with = "string_or_number_u32")] pub episode_count: u32, #[serde(default, deserialize_with = "string_or_number_u32")] pub id: u32, #[serde(default, deserialize_with = "string_default_on_null")] pub name: String, #[serde(default, deserialize_with = "string_default_on_null")] pub overview: String, #[serde(default, deserialize_with = "string_or_number_u32")] pub season_number: u32, #[serde(default, deserialize_with = "deserialize_number_from_string_or_zero")] pub vote_average: f64, #[serde(default, deserialize_with = "string_default_on_null")] pub cover: String, #[serde(default, deserialize_with = "string_default_on_null")] pub cover_big: String, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] #[allow(non_snake_case)] pub struct XtreamSeriesInfoInfo { #[serde(default, deserialize_with = "string_default_on_null")] pub(crate) name: String, #[serde(default, deserialize_with = "string_default_on_null")] pub cover: String, #[serde(default, deserialize_with = "string_default_on_null")] pub plot: String, #[serde(default, deserialize_with = "string_default_on_null")] pub cast: String, #[serde(default, deserialize_with = "string_default_on_null")] pub director: String, #[serde(default, deserialize_with = "string_default_on_null")] pub genre: String, #[serde(default, deserialize_with = "string_default_on_null")] pub release_date: String, #[serde(default, deserialize_with = "string_default_on_null")] pub last_modified: String, #[serde(default, deserialize_with = "deserialize_number_from_string_or_zero")] pub rating: f64, #[serde(default, deserialize_with = "deserialize_number_from_string_or_zero")] pub rating_5based: f64, #[serde(default, deserialize_with = "deserialize_as_string_array")] pub backdrop_path: Option>, #[serde(default, deserialize_with = "deserialize_number_from_string")] pub tmdb: Option, #[serde(default, deserialize_with = "string_default_on_null")] pub youtube_trailer: String, #[serde(default, deserialize_with = "string_default_on_null")] pub episode_run_time: String, #[serde(default, deserialize_with = "string_or_number_u32")] pub category_id: u32, } #[allow(non_snake_case)] #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct XtreamSeriesInfoEpisodeInfo { #[serde(default, deserialize_with = "string_default_on_null")] pub air_date: String, #[serde(default, deserialize_with = "string_default_on_null")] pub crew: String, #[serde(default, deserialize_with = "deserialize_number_from_string_or_zero")] pub rating: f64, #[serde(default, deserialize_with = "deserialize_number_from_string")] pub id: Option, #[serde(default, deserialize_with = "string_or_number_u32")] pub duration_secs: u32, #[serde(default, deserialize_with = "string_default_on_null")] pub duration: String, #[serde(default, deserialize_with = "string_default_on_null")] pub movie_image: String, #[serde(default, deserialize_with = "deserialize_json_as_opt_string")] pub video: Option, #[serde(default, deserialize_with = "deserialize_json_as_opt_string")] pub audio: Option, #[serde(default, deserialize_with = "string_or_number_u32")] pub bitrate: u32, } // Used for serde_json deserialization, cannot be used with bincode #[derive(Debug, Clone, Serialize, Deserialize)] pub struct XtreamSeriesInfoEpisode { #[serde(default, deserialize_with = "string_or_number_u32")] pub id: u32, #[serde(default, deserialize_with = "string_or_number_u32")] pub episode_num: u32, #[serde(default, deserialize_with = "string_default_on_null")] pub title: String, #[serde(default, deserialize_with = "string_default_on_null")] pub container_extension: String, #[serde(default)] pub info: Option, #[serde(default)] pub custom_sid: Option, #[serde(default, deserialize_with = "string_default_on_null")] pub added: String, #[serde(default, deserialize_with = "string_or_number_u32")] pub season: u32, #[serde(default, deserialize_with = "string_default_on_null")] pub direct_source: String, } impl XtreamSeriesInfoEpisode { pub fn get_id(&self) -> u32 { self.id } } // sometimes episodes are a map with season as key, sometimes an array fn deserialize_episodes<'de, D>(deserializer: D) -> Result>, D::Error> where D: Deserializer<'de>, { // read as generic value let value = Value::deserialize(deserializer)?; match value { Value::Null => Ok(None), Value::Array(array) => { if array.is_empty() { Ok(None) } else { let mut result = Vec::new(); for inner in array { if let Some(inner_array) = inner.as_array() { // Nested array case: [[ep1, ep2], [ep3]] for item in inner_array { let ep: XtreamSeriesInfoEpisode = serde_json::from_value(item.clone()) .map_err(serde::de::Error::custom)?; result.push(ep); } } else if inner.is_object() { // Flat array case: [ep1, ep2, ep3] let ep: XtreamSeriesInfoEpisode = serde_json::from_value(inner.clone()) .map_err(serde::de::Error::custom)?; result.push(ep); } } Ok(if result.is_empty() { None } else { Some(result) }) } } Value::Object(object) => { if object.is_empty() { Ok(None) } else { let mut result = Vec::new(); for (_key, val) in object { if let Some(inner_array) = val.as_array() { for item in inner_array { let ep: XtreamSeriesInfoEpisode = serde_json::from_value(item.clone()) .map_err(serde::de::Error::custom)?; result.push(ep); } } } Ok(Some(result)) } } _ => Err(serde::de::Error::custom("Invalid format for episodes")), } } #[allow(clippy::ref_option)] fn serialize_episodes( episodes: &Option>, serializer: S, ) -> Result where S: Serializer, { match episodes { None => { let map = serializer.serialize_map(Some(0))?; map.end() } Some(list) => { if list.is_empty() { let map = serializer.serialize_map(Some(0))?; return map.end(); } let mut seasons: BTreeMap> = BTreeMap::new(); for ep in list { seasons.entry(ep.season.to_string()).or_default().push(ep); } seasons.serialize(serializer) } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct XtreamSeriesInfo { #[serde(default)] pub seasons: Option>, #[serde(default)] pub info: XtreamSeriesInfoInfo, #[serde( default, serialize_with = "serialize_episodes", deserialize_with = "deserialize_episodes" )] pub episodes: Option>, }