diff --git a/backend/src/api/api_utils.rs b/backend/src/api/api_utils.rs index 82b95b48e..b32b0d116 100644 --- a/backend/src/api/api_utils.rs +++ b/backend/src/api/api_utils.rs @@ -1051,9 +1051,12 @@ pub async fn local_stream_response( .and_then(|v| v.to_str().ok()) .and_then(parse_range); - let (start, end) = if let Some((start, end)) = range { - let end = end.unwrap_or(file_size - 1); - (start, end.min(file_size - 1)) + let (start, end) = if let Some((req_start, req_end)) = range { + if file_size == 0 || req_start >= file_size { + return StatusCode::RANGE_NOT_SATISFIABLE.into_response(); + } + let end = req_end.unwrap_or(file_size - 1).min(file_size - 1); + (req_start, end) } else { if file_size == 0 { // Serve empty file diff --git a/backend/src/api/endpoints/xtream_api.rs b/backend/src/api/endpoints/xtream_api.rs index fbb8a3f4e..c43fc56fc 100644 --- a/backend/src/api/endpoints/xtream_api.rs +++ b/backend/src/api/endpoints/xtream_api.rs @@ -1005,7 +1005,7 @@ async fn xtream_get_stream_info_response( axum::http::header::CONTENT_TYPE, mime::APPLICATION_JSON.to_string() ) - .body(axum::body::Body::from(additional_properties.clone()))); + .body(axum::body::Body::from(additional_properties.to_owned()))); } } diff --git a/backend/src/library/scanner.rs b/backend/src/library/scanner.rs index c2b0ba23e..f768cf8c6 100644 --- a/backend/src/library/scanner.rs +++ b/backend/src/library/scanner.rs @@ -17,7 +17,7 @@ pub struct ScannedMediaFile { impl ScannedMediaFile { /// Creates a new `ScannedMediaFile` from a path and metadata - pub async fn from_path(path: PathBuf) -> io::Result { + pub async fn from_path(path: &Path) -> io::Result { let metadata = fs::metadata(&path).await?; let file_name = path .file_name() @@ -38,7 +38,7 @@ impl ScannedMediaFile { Ok(Self { file_path: path.display().to_string(), - path, + path: path.to_path_buf(), file_name, extension, size_bytes: metadata.len(), @@ -138,7 +138,7 @@ impl LibraryScanner { if let Some(ext) = entry_path.extension().and_then(|e| e.to_str()) { let ext_lower = ext.to_lowercase(); if self.config.supported_extensions.contains(&ext_lower) { - match ScannedMediaFile::from_path(entry_path.clone()).await { + match ScannedMediaFile::from_path(&entry_path).await { Ok(video_file) => { trace!("Found video file: {}", video_file.file_path); files.push(video_file); diff --git a/backend/src/model/config/source.rs b/backend/src/model/config/source.rs index b1b78b325..e341a4e00 100644 --- a/backend/src/model/config/source.rs +++ b/backend/src/model/config/source.rs @@ -26,6 +26,13 @@ impl ConfigSource { None } + // Determines whether this source should be processed for the given user targets. + // + // Returns `true` if: + // - `user_targets.targets` is empty (process all sources), OR + // - At least one target in this source matches an ID in `user_targets.targets` + // + // Returns `false` otherwise. pub fn should_process_for_user_targets(&self, user_targets: &ProcessTargets) -> bool { user_targets.targets.is_empty() || self.targets.iter().any(|t| user_targets.targets.contains(&t.id)) diff --git a/backend/src/model/xtream.rs b/backend/src/model/xtream.rs index 1b88e2af9..7063ab6fe 100644 --- a/backend/src/model/xtream.rs +++ b/backend/src/model/xtream.rs @@ -224,6 +224,7 @@ pub struct XtreamMovieInfoDetails { pub plot: Option, pub age: Option, pub mpaa_rating: Option, + #[serde(default)] pub rating_count_kinopoisk: u32, pub country: Option, pub genre: Option, diff --git a/backend/src/processing/processor/xtream_vod.rs b/backend/src/processing/processor/xtream_vod.rs index badfbc28f..18fdee3de 100644 --- a/backend/src/processing/processor/xtream_vod.rs +++ b/backend/src/processing/processor/xtream_vod.rs @@ -1,22 +1,22 @@ -use shared::model::InputType; -use shared::error::{TuliproxError}; use crate::model::{AppConfig, ConfigTarget}; -use crate::model::{FetchedPlaylist}; -use shared::model::{PlaylistItem, PlaylistItemType, XtreamCluster}; +use crate::model::FetchedPlaylist; +use crate::processing::processor::xtream::normalize_json_content; use crate::processing::processor::xtream::{create_resolve_info_wal_files, playlist_resolve_download_playlist_item, read_processed_info_ids, should_update_info}; +use crate::processing::processor::{create_resolve_options_function_for_xtream_target, handle_error, handle_error_and_return}; use crate::repository::xtream_repository::{write_vod_info_to_wal_file, xtream_update_input_info_file, xtream_update_input_vod_record_from_wal_file, InputVodInfoRecord}; -use shared::error::{notify_err}; -use crate::processing::processor::{handle_error, handle_error_and_return, create_resolve_options_function_for_xtream_target}; -use shared::utils::{get_u32_from_serde_value, get_u64_from_serde_value, get_string_from_serde_value}; -use serde_json::{Map, Value}; -use std::collections::{HashMap, HashSet}; -use std::io::{Write}; -use std::time::Instant; +use crate::utils; +use crate::utils::IO_BUFFER_SIZE; use log::{info, log_enabled, Level}; use serde_json::value::RawValue; -use crate::utils; -use crate::processing::processor::xtream::normalize_json_content; -use crate::utils::IO_BUFFER_SIZE; +use serde_json::{Map, Value}; +use shared::error::notify_err; +use shared::error::TuliproxError; +use shared::model::{InputType, PlaylistEntry}; +use shared::model::{PlaylistItem, PlaylistItemType, XtreamCluster}; +use shared::utils::{get_string_from_serde_value, get_u32_from_serde_value, get_u64_from_serde_value}; +use std::collections::{HashMap, HashSet}; +use std::io::Write; +use std::time::Instant; create_resolve_options_function_for_xtream_target!(vod); @@ -101,7 +101,11 @@ pub async fn playlist_resolve_vod(app_config: &AppConfig, client: &reqwest::Clie } let (should_update, provider_id, _ts) = should_update_vod_info(pli, &processed_info_ids); if should_update && provider_id != 0 && fetched_in_run.insert(provider_id) { - if let Some(content) = playlist_resolve_download_playlist_item(client, pli, fpl.input, errors, resolve_delay, XtreamCluster::Video).await { + if let Some(content) = if pli.get_item_type().is_local() { + pli.header.additional_properties.as_ref().map(ToString::to_string) + } else { + playlist_resolve_download_playlist_item(client, pli, fpl.input, errors, resolve_delay, XtreamCluster::Video).await + } { let normalized_content = normalize_json_content(content); let normalized_str: &str = &normalized_content; if let Some((provider_id, info_record)) = extract_info_record_from_vod_info(normalized_str) { diff --git a/backend/src/repository/strm_repository.rs b/backend/src/repository/strm_repository.rs index b2d62d089..16d2762c4 100644 --- a/backend/src/repository/strm_repository.rs +++ b/backend/src/repository/strm_repository.rs @@ -533,7 +533,7 @@ fn format_for_plex( (dir_path, final_filename) } PlaylistItemType::Series - | PlaylistItemType::LocalSeries=> { + | PlaylistItemType::LocalSeries => { let id_string = if tmdb_id > 0 { format!("{separator}{{tmdb-{tmdb_id}}}") } else { String::new() }; let series_name_raw = strm_item_info.series_name.as_ref().unwrap_or(&strm_item_info.title); let (name, year) = style_rename_year(series_name_raw, &CONSTANTS.export_style_config, strm_item_info.release_date.as_ref());