mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-20 14:52:22 +02:00
Fix/local series episodes (#681)
* Local and provider series uuid generation fixed. * fixed some local media issues
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
use crate::library::{EpisodeMetadata, MediaMetadata, MetadataAsyncIter, MetadataCacheEntry};
|
||||
use crate::library::{EpisodeMetadata, MediaMetadata, MetadataAsyncIter, MetadataCacheEntry, TechnicalMetadata};
|
||||
use crate::library::resolve_metadata_storage_path;
|
||||
use crate::model::{AppConfig, ConfigInput};
|
||||
use shared::concat_string;
|
||||
use shared::error::TuliproxError;
|
||||
use shared::model::UUIDType;
|
||||
use shared::model::{EpisodeStreamProperties, PlaylistGroup, PlaylistItem, PlaylistItemHeader, PlaylistItemType, SeriesStreamDetailEpisodeProperties, SeriesStreamDetailProperties, SeriesStreamDetailSeasonProperties, SeriesStreamProperties, StreamProperties, VideoStreamDetailProperties, VideoStreamProperties, XtreamCluster, normalize_episode_title};
|
||||
use shared::utils::{concat_path_leading_slash, generate_playlist_uuid, Internable};
|
||||
use shared::utils::{concat_path_leading_slash, generate_local_playlist_uuid, Internable};
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
@@ -23,6 +23,24 @@ fn episode_thumbnail_url(episode: &EpisodeMetadata, api_base_path: &str) -> Opti
|
||||
.map(|id| concat_path_leading_slash(api_base_path, &format!("library/thumbnail/{id}")))
|
||||
}
|
||||
|
||||
fn duration_secs_to_xtream_duration(duration_secs: u32) -> Arc<str> {
|
||||
let hours = duration_secs / 3600;
|
||||
let minutes = (duration_secs % 3600) / 60;
|
||||
let seconds = duration_secs % 60;
|
||||
format!("{hours:02}:{minutes:02}:{seconds:02}").into()
|
||||
}
|
||||
|
||||
fn technical_duration_secs(technical: Option<&TechnicalMetadata>) -> Option<u32> {
|
||||
technical.and_then(|metadata| metadata.duration_secs).filter(|value| *value > 0)
|
||||
}
|
||||
|
||||
fn technical_bitrate(technical: Option<&TechnicalMetadata>) -> u32 {
|
||||
technical
|
||||
.and_then(|metadata| metadata.bitrate)
|
||||
.filter(|value| *value > 0)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub async fn download_library_playlist(_client: &reqwest::Client, app_config: &Arc<AppConfig>, input: &ConfigInput) -> (Vec<PlaylistGroup>, Vec<TuliproxError>) {
|
||||
let config = &*app_config.config.load();
|
||||
let Some(library_config) = config.library.as_ref() else { return (vec![], vec![]) };
|
||||
@@ -215,7 +233,7 @@ fn build_series_episode_playlist_item(
|
||||
header: PlaylistItemHeader {
|
||||
id: episode.id.to_string().into(),
|
||||
parent_code: entry.uuid.clone().into(),
|
||||
uuid: generate_playlist_uuid(input_name, &episode.id.to_string(), PlaylistItemType::LocalSeries, &episode.direct_source),
|
||||
uuid: generate_local_playlist_uuid(input_name, PlaylistItemType::LocalSeries, &episode.direct_source),
|
||||
logo: logo.clone(),
|
||||
name: episode.title.clone(),
|
||||
group: Arc::clone(group_name),
|
||||
@@ -262,6 +280,9 @@ pub fn metadata_cache_entry_to_xtream_movie_info(
|
||||
.map(ToString::to_string).unwrap_or_default();
|
||||
|
||||
let actor_names = movie.actors.as_ref().map(|a| a.iter().map(|a| a.name.clone()).collect::<Vec<_>>().join(", ").intern());
|
||||
let technical = movie.technical.as_ref();
|
||||
let duration_secs = technical_duration_secs(technical).or_else(|| movie.runtime.map(|runtime| runtime * 60));
|
||||
let duration = duration_secs.map(duration_secs_to_xtream_duration);
|
||||
|
||||
let properties = VideoStreamProperties {
|
||||
name: movie.title.clone().into(),
|
||||
@@ -302,17 +323,12 @@ pub fn metadata_cache_entry_to_xtream_movie_info(
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|f| vec![f.clone().into()])
|
||||
.or_else(|| poster.map(|p| vec![p.into()])),
|
||||
duration_secs: movie.runtime.map(|r| (r * 60).to_string().into()),
|
||||
duration: movie.runtime.map(|r| {
|
||||
let h = r / 60;
|
||||
let m = r % 60;
|
||||
format!("{h:02}:{m:02}:00").into()
|
||||
}),
|
||||
|
||||
video: None,
|
||||
audio: None,
|
||||
bitrate: 0,
|
||||
runtime: movie.runtime.map(|r| (r * 60).to_string().into()),
|
||||
duration_secs: duration_secs.map(|value| value.to_string().into()),
|
||||
duration: duration.clone(),
|
||||
video: technical.and_then(|metadata| metadata.video.as_ref()).map(|value| value.as_str().into()),
|
||||
audio: technical.and_then(|metadata| metadata.audio.as_ref()).map(|value| value.as_str().into()),
|
||||
bitrate: technical_bitrate(technical),
|
||||
runtime: duration_secs.map(|value| value.to_string().into()),
|
||||
status: Some("Released".intern()),
|
||||
}),
|
||||
};
|
||||
@@ -333,18 +349,21 @@ pub fn metadata_cache_entry_to_xtream_series_info(
|
||||
let actor_names: Arc<str> = series.actors.as_ref().map(|a| a.iter().map(|a| a.name.clone()).collect::<Vec<_>>().join(", ")).unwrap_or_default().into();
|
||||
let release_date = series.year.map(|y| format!("{y}-01-01"));
|
||||
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 series_thumbnail = thumbnail_url(entry, api_base_path);
|
||||
let series_art = series.poster.clone().or(series_thumbnail.clone());
|
||||
|
||||
let mut season_data = HashMap::new();
|
||||
series.seasons.as_ref().iter().for_each(|seasons| seasons.iter().for_each(|season_metadata| {
|
||||
let season_art = season_metadata.poster_path.clone().or_else(|| series_art.clone());
|
||||
season_data.insert(season_metadata.season_number, SeriesStreamDetailSeasonProperties {
|
||||
name: season_metadata.name.clone().into(),
|
||||
season_number: season_metadata.season_number,
|
||||
episode_count: 0,
|
||||
overview: season_metadata.overview.clone().map(Into::into),
|
||||
air_date: season_metadata.air_date.clone().map(Into::into),
|
||||
cover: season_metadata.poster_path.clone().map(Into::into),
|
||||
cover_tmdb: season_metadata.poster_path.clone().map(Into::into),
|
||||
cover_big: None,
|
||||
cover: season_art.clone().map(Into::into),
|
||||
cover_tmdb: season_art.clone().map(Into::into),
|
||||
cover_big: season_art.map(Into::into),
|
||||
duration: Some(String::from("0").into()),
|
||||
});
|
||||
}));
|
||||
@@ -360,17 +379,19 @@ pub fn metadata_cache_entry_to_xtream_series_info(
|
||||
let tmdb_id = (episode.tmdb_id > 0).then_some(episode.tmdb_id);
|
||||
let raw_episode_title: Arc<str> = episode.title.clone().into();
|
||||
let series_title: Arc<str> = series.title.clone().into();
|
||||
let technical = episode.technical.as_ref();
|
||||
let duration_secs = technical_duration_secs(technical).or_else(|| episode.runtime.map(|runtime| runtime * 60));
|
||||
|
||||
let season_entry = season_data.entry(episode.season).or_insert_with(|| {
|
||||
SeriesStreamDetailSeasonProperties {
|
||||
name: concat_string!(&series.title, " ", &episode.season.to_string()).into(),
|
||||
season_number: episode.season,
|
||||
episode_count: 0,
|
||||
overview: series.poster.clone().map(Into::into),
|
||||
overview: None,
|
||||
air_date: episode.aired.clone().map(Into::into),
|
||||
cover: series.poster.clone().map(Into::into),
|
||||
cover_tmdb: None,
|
||||
cover_big: None,
|
||||
cover: series_art.clone().map(Into::into),
|
||||
cover_tmdb: series_art.clone().map(Into::into),
|
||||
cover_big: series_art.clone().map(Into::into),
|
||||
duration: None,
|
||||
}
|
||||
});
|
||||
@@ -390,19 +411,18 @@ pub fn metadata_cache_entry_to_xtream_series_info(
|
||||
series_release_date: None,
|
||||
plot: episode.plot.clone().map(Into::into),
|
||||
crew: Some(Arc::clone(&actor_names)),
|
||||
duration_secs: episode.runtime.map_or(0, |r| r * 60),
|
||||
duration: episode.runtime
|
||||
.map(|r| format!("{:02}:{:02}:00", r / 60, r % 60))
|
||||
.unwrap_or_default().into(),
|
||||
duration_secs: duration_secs.unwrap_or_default(),
|
||||
duration: duration_secs.map(duration_secs_to_xtream_duration).unwrap_or_default(),
|
||||
movie_image: episode
|
||||
.thumb
|
||||
.clone()
|
||||
.or_else(|| episode_thumbnail_url(episode, api_base_path))
|
||||
.or_else(|| series_art.clone())
|
||||
.unwrap_or_default()
|
||||
.into(),
|
||||
audio: None,
|
||||
video: None,
|
||||
bitrate: 0,
|
||||
audio: technical.and_then(|metadata| metadata.audio.as_ref()).map(|value| value.as_str().into()),
|
||||
video: technical.and_then(|metadata| metadata.video.as_ref()).map(|value| value.as_str().into()),
|
||||
bitrate: technical_bitrate(technical),
|
||||
rating: None,
|
||||
}
|
||||
}).collect::<Vec<_>>()
|
||||
@@ -422,12 +442,12 @@ pub fn metadata_cache_entry_to_xtream_series_info(
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|f| vec![f.clone().into()])
|
||||
.or_else(|| {
|
||||
series.poster.as_ref()
|
||||
series_art.as_ref()
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|p| vec![p.clone().into()])
|
||||
}),
|
||||
cast: Arc::clone(&actor_names),
|
||||
cover: series.poster.clone().unwrap_or_default().into(),
|
||||
cover: series_art.unwrap_or_default().into(),
|
||||
director: series.directors.as_ref().map(|d| d.join(", ")).unwrap_or_default().into(),
|
||||
episode_run_time: None,
|
||||
genre: series.genres.as_ref().map(|d| d.join(", ").into()),
|
||||
@@ -451,7 +471,7 @@ pub fn metadata_cache_entry_to_xtream_series_info(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{metadata_cache_entry_to_xtream_series_info, thumbnail_url};
|
||||
use crate::library::{MediaMetadata, MetadataCacheEntry, MovieMetadata, SeriesMetadata, EpisodeMetadata};
|
||||
use crate::library::{EpisodeMetadata, MediaMetadata, MetadataCacheEntry, MovieMetadata, SeriesMetadata, TechnicalMetadata};
|
||||
use shared::model::StreamProperties;
|
||||
|
||||
#[test]
|
||||
@@ -502,4 +522,139 @@ mod tests {
|
||||
"/api/v1/library/thumbnail/31fedbc18dca3fa273fba98afda584486ad4f1d8e1ca06740435b97b14f2ec8b",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn series_info_uses_local_thumbnail_when_series_poster_missing() {
|
||||
let mut entry = MetadataCacheEntry::new(
|
||||
"/tmp/show/S01E01.mkv".to_string(),
|
||||
123,
|
||||
456,
|
||||
MediaMetadata::Series(SeriesMetadata {
|
||||
title: "Test Series".to_string(),
|
||||
episodes: Some(vec![EpisodeMetadata {
|
||||
title: "Episode 1".to_string(),
|
||||
season: 1,
|
||||
episode: 1,
|
||||
file_path: "/tmp/show/S01E01.mkv".to_string(),
|
||||
..EpisodeMetadata::default()
|
||||
}]),
|
||||
..SeriesMetadata::default()
|
||||
}),
|
||||
);
|
||||
entry.uuid = "series-thumb-uuid".to_string();
|
||||
entry.thumbnail_hash = Some("thumb-hash".to_string());
|
||||
|
||||
let Some(StreamProperties::Series(series)) = metadata_cache_entry_to_xtream_series_info(&entry, "/api/v1") else {
|
||||
panic!("expected series stream properties");
|
||||
};
|
||||
|
||||
assert_eq!(series.cover.as_ref(), "/api/v1/library/thumbnail/series-thumb-uuid");
|
||||
|
||||
let seasons = series.details.as_ref().and_then(|details| details.seasons.as_ref()).expect("seasons missing");
|
||||
let season = seasons.first().expect("season missing");
|
||||
assert_eq!(season.cover.as_deref().map(|v| v.as_ref()), Some("/api/v1/library/thumbnail/series-thumb-uuid"));
|
||||
assert_eq!(season.cover_tmdb.as_deref().map(|v| v.as_ref()), Some("/api/v1/library/thumbnail/series-thumb-uuid"));
|
||||
assert_eq!(season.cover_big.as_deref().map(|v| v.as_ref()), Some("/api/v1/library/thumbnail/series-thumb-uuid"));
|
||||
assert!(season.overview.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn series_episode_falls_back_to_series_thumbnail_when_episode_thumb_missing() {
|
||||
let mut entry = MetadataCacheEntry::new(
|
||||
"/tmp/show/S01E01.mkv".to_string(),
|
||||
123,
|
||||
456,
|
||||
MediaMetadata::Series(SeriesMetadata {
|
||||
title: "Test Series".to_string(),
|
||||
episodes: Some(vec![EpisodeMetadata {
|
||||
title: "Episode 1".to_string(),
|
||||
season: 1,
|
||||
episode: 1,
|
||||
file_path: "/tmp/show/S01E01.mkv".to_string(),
|
||||
..EpisodeMetadata::default()
|
||||
}]),
|
||||
..SeriesMetadata::default()
|
||||
}),
|
||||
);
|
||||
entry.uuid = "series-thumb-uuid".to_string();
|
||||
entry.thumbnail_hash = Some("thumb-hash".to_string());
|
||||
|
||||
let Some(StreamProperties::Series(series)) = metadata_cache_entry_to_xtream_series_info(&entry, "/api/v1") else {
|
||||
panic!("expected series stream properties");
|
||||
};
|
||||
let episodes = series.details.as_ref().and_then(|details| details.episodes.as_ref()).expect("episodes missing");
|
||||
let episode = episodes.first().expect("episode missing");
|
||||
|
||||
assert_eq!(episode.movie_image.as_ref(), "/api/v1/library/thumbnail/series-thumb-uuid");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn movie_info_prefers_local_technical_metadata_for_probe_fields() {
|
||||
let entry = MetadataCacheEntry::new(
|
||||
"/tmp/movie/Test.Movie.2024.mkv".to_string(),
|
||||
123,
|
||||
456,
|
||||
MediaMetadata::Movie(MovieMetadata {
|
||||
title: "Test Movie".to_string(),
|
||||
runtime: Some(90),
|
||||
technical: Some(TechnicalMetadata {
|
||||
video: Some("{\"codec_name\":\"hevc\"}".to_string()),
|
||||
audio: Some("{\"codec_name\":\"aac\"}".to_string()),
|
||||
duration_secs: Some(5_430),
|
||||
bitrate: Some(8_200_000),
|
||||
}),
|
||||
..MovieMetadata::default()
|
||||
}),
|
||||
);
|
||||
|
||||
let Some(StreamProperties::Video(video)) = super::metadata_cache_entry_to_xtream_movie_info(&entry, "/api/v1") else {
|
||||
panic!("expected video stream properties");
|
||||
};
|
||||
let details = video.details.as_ref().expect("details missing");
|
||||
|
||||
assert_eq!(details.video.as_deref().map(|value| value.as_ref()), Some("{\"codec_name\":\"hevc\"}"));
|
||||
assert_eq!(details.audio.as_deref().map(|value| value.as_ref()), Some("{\"codec_name\":\"aac\"}"));
|
||||
assert_eq!(details.duration_secs.as_deref().map(|value| value.as_ref()), Some("5430"));
|
||||
assert_eq!(details.duration.as_deref().map(|value| value.as_ref()), Some("01:30:30"));
|
||||
assert_eq!(details.runtime.as_deref().map(|value| value.as_ref()), Some("5430"));
|
||||
assert_eq!(details.bitrate, 8_200_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn series_episode_prefers_local_technical_metadata_for_probe_fields() {
|
||||
let entry = MetadataCacheEntry::new(
|
||||
"/tmp/show/S01E01.mkv".to_string(),
|
||||
123,
|
||||
456,
|
||||
MediaMetadata::Series(SeriesMetadata {
|
||||
title: "Test Series".to_string(),
|
||||
episodes: Some(vec![EpisodeMetadata {
|
||||
title: "Episode 1".to_string(),
|
||||
season: 1,
|
||||
episode: 1,
|
||||
file_path: "/tmp/show/S01E01.mkv".to_string(),
|
||||
technical: Some(TechnicalMetadata {
|
||||
video: Some("{\"codec_name\":\"h264\"}".to_string()),
|
||||
audio: Some("{\"codec_name\":\"ac3\"}".to_string()),
|
||||
duration_secs: Some(1_541),
|
||||
bitrate: Some(3_100_000),
|
||||
}),
|
||||
..EpisodeMetadata::default()
|
||||
}]),
|
||||
..SeriesMetadata::default()
|
||||
}),
|
||||
);
|
||||
|
||||
let Some(StreamProperties::Series(series)) = metadata_cache_entry_to_xtream_series_info(&entry, "/api/v1") else {
|
||||
panic!("expected series stream properties");
|
||||
};
|
||||
let episodes = series.details.as_ref().and_then(|details| details.episodes.as_ref()).expect("episodes missing");
|
||||
let episode = episodes.first().expect("episode missing");
|
||||
|
||||
assert_eq!(episode.video.as_deref().map(|value| value.as_ref()), Some("{\"codec_name\":\"h264\"}"));
|
||||
assert_eq!(episode.audio.as_deref().map(|value| value.as_ref()), Some("{\"codec_name\":\"ac3\"}"));
|
||||
assert_eq!(episode.duration_secs, 1_541);
|
||||
assert_eq!(episode.duration.as_ref(), "00:25:41");
|
||||
assert_eq!(episode.bitrate, 3_100_000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::model::{AppConfig};
|
||||
use crate::processing::processor::{select_cancel_token, ProbeHandleGuard};
|
||||
use crate::repository::{get_input_m3u_playlist_file_path, get_input_storage_path, get_input_local_library_playlist_file_path, xtream_get_file_path, BPlusTreeUpdate};
|
||||
use crate::utils::debug_if_enabled;
|
||||
use crate::utils::ffmpeg::{FfmpegExecutor, ProbeFailureKind, ProbeUrlOutcome};
|
||||
use crate::utils::ffmpeg::{FfmpegExecutor, ProbeFailureKind, ProbeStreamStats, ProbeUrlOutcome};
|
||||
use log::{info, warn};
|
||||
use shared::error::TuliproxError;
|
||||
use shared::model::{EpisodeStreamProperties, InputType, PlaylistItemType, StreamProperties, VideoStreamDetailProperties, VideoStreamProperties, LiveStreamProperties, M3uPlaylistItem, XtreamCluster, XtreamPlaylistItem};
|
||||
@@ -139,8 +139,8 @@ pub async fn update_generic_stream_metadata(
|
||||
handle.release().await;
|
||||
}
|
||||
|
||||
let (raw_video, raw_audio) = match probe_data {
|
||||
ProbeUrlOutcome::Success(_quality, raw_video, raw_audio) => (raw_video, raw_audio),
|
||||
let (raw_video, raw_audio, stats) = match probe_data {
|
||||
ProbeUrlOutcome::Success(_quality, raw_video, raw_audio, stats) => (raw_video, raw_audio, stats),
|
||||
ProbeUrlOutcome::Failed(ProbeFailureKind::NotFound) => {
|
||||
warn!("Probe target not found (404) for generic stream: {unique_id}");
|
||||
return Err(shared::error::info_err!("Probe target returned 404 Not Found for stream {unique_id}"));
|
||||
@@ -175,6 +175,7 @@ pub async fn update_generic_stream_metadata(
|
||||
item.virtual_id,
|
||||
raw_video,
|
||||
raw_audio,
|
||||
stats,
|
||||
);
|
||||
tree_update
|
||||
.update(&key, item)
|
||||
@@ -198,6 +199,7 @@ pub async fn update_generic_stream_metadata(
|
||||
item.virtual_id,
|
||||
raw_video,
|
||||
raw_audio,
|
||||
stats,
|
||||
);
|
||||
tree_update
|
||||
.update(&uuid, item)
|
||||
@@ -228,6 +230,7 @@ pub async fn update_generic_stream_metadata(
|
||||
item.virtual_id,
|
||||
raw_video,
|
||||
raw_audio,
|
||||
stats,
|
||||
);
|
||||
tree_update
|
||||
.update(&provider_id, item)
|
||||
@@ -260,7 +263,8 @@ fn update_properties(
|
||||
name: &str,
|
||||
virtual_id: u32,
|
||||
raw_video: Option<serde_json::Value>,
|
||||
raw_audio: Option<serde_json::Value>
|
||||
raw_audio: Option<serde_json::Value>,
|
||||
stats: ProbeStreamStats,
|
||||
) {
|
||||
if matches!(item_type, PlaylistItemType::Video | PlaylistItemType::LocalVideo) {
|
||||
let mut props = if let Some(StreamProperties::Video(p)) = props_opt {
|
||||
@@ -284,6 +288,12 @@ fn update_properties(
|
||||
if let Some(a) = raw_audio {
|
||||
details.audio = Some(a.to_string().into());
|
||||
}
|
||||
if let Some(duration_secs) = stats.duration_secs {
|
||||
details.duration_secs = Some(duration_secs.to_string().into());
|
||||
}
|
||||
if let Some(bitrate) = stats.bitrate {
|
||||
details.bitrate = bitrate;
|
||||
}
|
||||
}
|
||||
*props_opt = Some(StreamProperties::Video(Box::new(props)));
|
||||
}
|
||||
@@ -343,6 +353,7 @@ fn update_properties(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::utils::ffmpeg::ProbeStreamStats;
|
||||
|
||||
#[test]
|
||||
fn library_probe_does_not_require_provider_connection() {
|
||||
@@ -366,4 +377,29 @@ mod tests {
|
||||
InputType::XtreamBatch
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_properties_applies_probe_stats_to_video_details() {
|
||||
let mut props_opt = None;
|
||||
|
||||
update_properties(
|
||||
&mut props_opt,
|
||||
PlaylistItemType::Video,
|
||||
"Example",
|
||||
77,
|
||||
None,
|
||||
None,
|
||||
ProbeStreamStats {
|
||||
duration_secs: Some(1_541),
|
||||
bitrate: Some(3_100_000),
|
||||
},
|
||||
);
|
||||
|
||||
let Some(StreamProperties::Video(video)) = props_opt else {
|
||||
panic!("expected video properties");
|
||||
};
|
||||
let details = video.details.as_ref().unwrap_or_else(|| unreachable!());
|
||||
assert_eq!(details.duration_secs.as_deref().map(|value| value.as_ref()), Some("1541"));
|
||||
assert_eq!(details.bitrate, 3_100_000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ pub async fn update_live_stream_metadata(
|
||||
)
|
||||
.await
|
||||
{
|
||||
ProbeUrlOutcome::Success(_quality, raw_video, raw_audio) => {
|
||||
ProbeUrlOutcome::Success(_quality, raw_video, raw_audio, _stats) => {
|
||||
// 3. Update properties on success
|
||||
if let Some(v) = raw_video {
|
||||
properties.video = Some(v.to_string().into());
|
||||
|
||||
@@ -935,7 +935,7 @@ pub async fn update_series_metadata(
|
||||
)
|
||||
.await
|
||||
{
|
||||
ProbeUrlOutcome::Success(_quality, raw_video, raw_audio) => {
|
||||
ProbeUrlOutcome::Success(_quality, raw_video, raw_audio, _stats) => {
|
||||
if let Some(v) = raw_video {
|
||||
ep.video = Some(v.to_string().into());
|
||||
properties_updated = true;
|
||||
|
||||
@@ -852,7 +852,7 @@ pub async fn update_vod_metadata(
|
||||
)
|
||||
.await
|
||||
{
|
||||
ProbeUrlOutcome::Success(_quality, raw_video, raw_audio) => {
|
||||
ProbeUrlOutcome::Success(_quality, raw_video, raw_audio, stats) => {
|
||||
if let Some(details) = properties.details.as_mut() {
|
||||
if let Some(v) = raw_video {
|
||||
details.video = Some(v.to_string().into());
|
||||
@@ -862,6 +862,14 @@ pub async fn update_vod_metadata(
|
||||
details.audio = Some(a.to_string().into());
|
||||
properties_updated = true;
|
||||
}
|
||||
if let Some(duration_secs) = stats.duration_secs {
|
||||
details.duration_secs = Some(duration_secs.to_string().into());
|
||||
properties_updated = true;
|
||||
}
|
||||
if let Some(bitrate) = stats.bitrate {
|
||||
details.bitrate = bitrate;
|
||||
properties_updated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ProbeUrlOutcome::Failed(ProbeFailureKind::NotFound) => {
|
||||
|
||||
Reference in New Issue
Block a user