diff --git a/CHANGELOG.md b/CHANGELOG.md index 8efa9bf35..ac3a9c91d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ # Changelog +# 2.0.10 (2024-12-xx) +- added Target Output Option `m3u_use_type_url`, default false. This adds `live`, `movie`, `series` to the url of the stream in reverse proxy mode. + # 2.0.9 (2024-12-01) - Fixed api proxy server url bug diff --git a/README.md b/README.md index ea3e5d275..b0b31e579 100644 --- a/README.md +++ b/README.md @@ -358,24 +358,6 @@ Each format has 2 properties `type` is _mandatory_ for `m3u`, `strm` and `xtream`. `filename` is _mandatory_ if type is `strm`. if type is `m3u` the plain m3u file is written but it is not used by `m3u-filter`. -`strm` output has additional options -- `underscore_whitespace` -- `cleanup` -- `kodi_style`. - -`xtream` output has additional options -- `xtream_skip_live_direct_source` if true the direct_source property from provider for live is ignored -- `xtream_skip_video_direct_source` if true the direct_source property from provider for movies is ignored -- `xtream_skip_series_direct_source` if true the direct_source property from provider for series is ignored - -`m3u` output has additional options -Because xtream api delivers only the metadata to series, we need to fetch the series and resolve them. But be aware, -each series info entry needs to be fetched one by one. -- `xtream_resolve_series` if is set to `true` and you have xtream input and m3u output, the series are fetched and resolved. -This can cause a lot of requests to the provider. Be cautious when using this option. -- `xtream_resolve_series_delay` to avoid a provider ban you can set the seconds between series_info_request's. Default is 2 seconds. -But be aware that the more series entries there are, the longer the process takes. - ```yaml output: - type: m3u @@ -387,18 +369,31 @@ The processing order (Filter, Rename and Map) can be configured for each target `processing_order: frm` (valid values are: frm, fmr, rfm, rmf, mfr, mrf. default is frm) ### 2.2.2.4 `options` -- ignore_logo `true` or `false` -- underscore_whitespace `true` or `false` -- cleanup `true` or `false` -- kodi_style `true` or `false` +Target options are: -`underscore_whitespace`, `cleanup` and `kodi_style` are only valid for `strm` output. +- `ingore_logo` logo attributes are ignored to avoid caching logo files on devices. -- `ingore_log` logo attributes are ignored to avoid caching logo files on devices. +`strm` output has additional options - `underscore_whitespace` replaces all whitespaces with `_` in the path. - `cleanup` deletes the directory given at `filename`. - `kodi_style` tries to rename `filename` with [kodi style](https://kodi.wiki/view/Naming_video_files/TV_shows). +`m3u` output has additional options +- `m3u_use_type_url`, default false, if true adds the stream type `live`, `movie`, `series` to the url of the stream in reverse proxy mode. + +`xtream` output has additional options +- `xtream_skip_live_direct_source` if true the direct_source property from provider for live is ignored +- `xtream_skip_video_direct_source` if true the direct_source property from provider for movies is ignored +- `xtream_skip_series_direct_source` if true the direct_source property from provider for series is ignored + +Because xtream api delivers only the metadata to series, we need to fetch the series and resolve them. But be aware, +each series info entry needs to be fetched one by one. +- `xtream_resolve_series` if is set to `true` and you have xtream input and m3u output, the series are fetched and resolved. + This can cause a lot of requests to the provider. Be cautious when using this option. +- `xtream_resolve_series_delay` to avoid a provider ban you can set the seconds between series_info_request's. Default is 2 seconds. + But be aware that the more series entries there are, the longer the process takes. + + ### 2.2.2.5 `filter` The filter is a string with a filter statement. The filter can have UnaryExpression `NOT`, BinaryExpression `AND OR`, Regexp Comparison `(Group|Title|Name|Url) ~ "regexp"` diff --git a/src/api/m3u_api.rs b/src/api/m3u_api.rs index 08862161b..0bd0ff804 100644 --- a/src/api/m3u_api.rs +++ b/src/api/m3u_api.rs @@ -83,5 +83,8 @@ pub fn m3u_api_register(cfg: &mut web::ServiceConfig) { .service(web::resource("/get.php").route(web::post().to(m3u_api_get)).route(web::post().to(m3u_api_post))) .service(web::resource("/apiget").route(web::get().to(m3u_api_get)).route(web::post().to(m3u_api_post))) .service(web::resource("/m3u").route(web::get().to(m3u_api_get)).route(web::post().to(m3u_api_post))) + .service(web::resource("/m3u-stream/live/{username}/{password}/{stream_id}").route(web::get().to(m3u_api_stream))) + .service(web::resource("/m3u-stream/movie/{username}/{password}/{stream_id}").route(web::get().to(m3u_api_stream))) + .service(web::resource("/m3u-stream/series/{username}/{password}/{stream_id}").route(web::get().to(m3u_api_stream))) .service(web::resource("/m3u-stream/{username}/{password}/{stream_id}").route(web::get().to(m3u_api_stream))); } \ No newline at end of file diff --git a/src/api/v1_api.rs b/src/api/v1_api.rs index d9bfdddf9..18a791b5d 100644 --- a/src/api/v1_api.rs +++ b/src/api/v1_api.rs @@ -16,7 +16,7 @@ use crate::processing::playlist_processor; use crate::utils::{config_reader, download}; use crate::utils::request_utils::mask_sensitive_info; -fn _save_config_api_proxy(backup_dir: &str, api_proxy: &ApiProxyConfig, file_path: &str) -> Option { +fn intern_save_config_api_proxy(backup_dir: &str, api_proxy: &ApiProxyConfig, file_path: &str) -> Option { match config_reader::save_api_proxy(file_path, backup_dir, api_proxy) { Ok(()) => {} Err(err) => { @@ -27,7 +27,7 @@ fn _save_config_api_proxy(backup_dir: &str, api_proxy: &ApiProxyConfig, file_pat None } -fn _save_config_main(file_path: &str, backup_dir: &str, cfg: &ConfigDto) -> Option { +fn intern_save_config_main(file_path: &str, backup_dir: &str, cfg: &ConfigDto) -> Option { match config_reader::save_main_config(file_path, backup_dir, cfg) { Ok(()) => {} Err(err) => { @@ -47,7 +47,7 @@ async fn save_config_api_proxy_user( if let Some(api_proxy) = app_state.config.t_api_proxy.write().unwrap().as_mut() { let backup_dir = app_state.config.backup_dir.as_ref().unwrap().as_str(); api_proxy.user = users; - if let Some(err) = _save_config_api_proxy(backup_dir, api_proxy, app_state.config.t_api_proxy_file_path.as_str()) { + if let Some(err) = intern_save_config_api_proxy(backup_dir, api_proxy, app_state.config.t_api_proxy_file_path.as_str()) { return HttpResponse::InternalServerError().json(json!({"error": err.to_string()})); } api_proxy.user.iter_mut().flat_map(|t| &mut t.credentials).for_each(|c| c.prepare(true)); @@ -63,7 +63,7 @@ async fn save_config_main( if cfg.is_valid() { let file_path = app_state.config.t_config_file_path.as_str(); let backup_dir = app_state.config.backup_dir.as_ref().unwrap().as_str(); - if let Some(err) = _save_config_main(file_path, backup_dir, &cfg) { + if let Some(err) = intern_save_config_main(file_path, backup_dir, &cfg) { return HttpResponse::InternalServerError().json(json!({"error": err.to_string()})); } HttpResponse::Ok().finish() @@ -85,7 +85,7 @@ async fn save_config_api_proxy_config( if let Some(api_proxy) = app_state.config.t_api_proxy.write().unwrap().as_mut() { api_proxy.server = req_api_proxy; let backup_dir = app_state.config.backup_dir.as_ref().unwrap().as_str(); - if let Some(err) = _save_config_api_proxy(backup_dir, api_proxy, app_state.config.t_api_proxy_file_path.as_str()) { + if let Some(err) = intern_save_config_api_proxy(backup_dir, api_proxy, app_state.config.t_api_proxy_file_path.as_str()) { return HttpResponse::InternalServerError().json(json!({"error": err.to_string()})); } } @@ -98,7 +98,7 @@ async fn playlist_update( ) -> HttpResponse { let targets = req.0; let user_targets = if targets.is_empty() { None } else { Some(targets) }; - let process_targets = validate_targets(&user_targets, &app_state.config.sources); + let process_targets = validate_targets(user_targets.as_ref(), &app_state.config.sources); match process_targets { Ok(valid_targets) => { actix_rt::spawn(playlist_processor::exec_processing(Arc::clone(&app_state.config), Arc::new(valid_targets))); diff --git a/src/api/xtream_api.rs b/src/api/xtream_api.rs index 419a0afff..7489f902a 100644 --- a/src/api/xtream_api.rs +++ b/src/api/xtream_api.rs @@ -208,13 +208,10 @@ async fn xtream_player_api_stream( let pli = try_result_bad_request!(xtream_repository::xtream_get_item_for_stream_id(virtual_id, &app_state.config, target, None), true, format!("Failed to read xtream item for stream id {}", virtual_id)); let input = try_option_bad_request!(app_state.config.get_input_by_id(pli.input_id), true, format!("Cant find input for target {target_name}, context {}, stream_id {virtual_id}", stream_req.context)); - match pli.item_type { - PlaylistItemType::LiveHls => { - let stream_url = pli.url.to_string(); - debug!("Redirecting stream request to {stream_url}"); - return HttpResponse::Found().insert_header(("Location", stream_url)).finish(); - } - _ => {} + if pli.item_type == PlaylistItemType::LiveHls { + let stream_url = pli.url.to_string(); + debug!("Redirecting stream request to {stream_url}"); + return HttpResponse::Found().insert_header(("Location", stream_url)).finish(); } let query_path = if stream_req.action_path.is_empty() { diff --git a/src/filter.rs b/src/filter.rs index d62f54758..7b584a20e 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -42,7 +42,7 @@ pub struct ValueProvider<'a> { pub pli: RefCell<&'a PlaylistItem>, } -impl<'a> ValueProvider<'a> { +impl ValueProvider<'_> { fn call(&self, field: &ItemField) -> Rc { let pli = *self.pli.borrow(); get_field_value(pli, field) @@ -154,7 +154,7 @@ impl Filter { } Self::TypeComparison(field, item_type) => { let value = provider.call(field); - get_filter_item_type(value.as_str()).map_or(false, |pli_type| { + get_filter_item_type(value.as_str()).is_some_and(|pli_type| { let is_match = pli_type.eq(item_type); if log_enabled!(Level::Trace) { if is_match { diff --git a/src/main.rs b/src/main.rs index 0aab2aaf1..4461b15b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -102,7 +102,7 @@ fn main() { create_directories(&cfg); - let targets = validate_targets(&args.target, &cfg.sources).unwrap_or_else(|err| exit!("{}", err)); + let targets = validate_targets(args.target.as_ref(), &cfg.sources).unwrap_or_else(|err| exit!("{}", err)); info!("Version: {}", VERSION); info!("Current time: {}", chrono::offset::Local::now().format("%Y-%m-%d %H:%M:%S").to_string()); diff --git a/src/messaging.rs b/src/messaging.rs index f41dc2059..1abd5d911 100644 --- a/src/messaging.rs +++ b/src/messaging.rs @@ -18,7 +18,7 @@ fn is_enabled(kind: &MsgKind, cfg: &MessagingConfig) -> bool { cfg.notify_on.contains(kind) } -pub fn send_message(kind: &MsgKind, cfg: &Option, msg: &str) { +pub fn send_message(kind: &MsgKind, cfg: Option<&MessagingConfig>, msg: &str) { if let Some(messaging) = cfg { if is_enabled(kind, messaging) { if let Some(telegram) = &messaging.telegram { diff --git a/src/model/api_proxy.rs b/src/model/api_proxy.rs index b70cdb319..0b8b61357 100644 --- a/src/model/api_proxy.rs +++ b/src/model/api_proxy.rs @@ -201,7 +201,7 @@ impl ApiProxyServerInfo { if port.is_empty() { base_url } else { - format!("{}:{}", base_url, port) + format!("{base_url}:{port}") } } } diff --git a/src/model/config.rs b/src/model/config.rs index 67d8f0893..e8581a263 100644 --- a/src/model/config.rs +++ b/src/model/config.rs @@ -283,7 +283,6 @@ impl ConfigRename { } } - #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)] pub struct ConfigTargetOptions { #[serde(default)] @@ -304,6 +303,8 @@ pub struct ConfigTargetOptions { pub xtream_resolve_series: bool, #[serde(default = "default_as_two_u16")] pub xtream_resolve_series_delay: u16, + #[serde(default)] + pub m3u_use_type_url: bool, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -412,7 +413,7 @@ impl ConfigTarget { pub fn filter(&self, provider: &ValueProvider) -> bool { let mut processor = MockValueProcessor {}; - return self.t_filter.as_ref().unwrap().filter(provider, &mut processor); + self.t_filter.as_ref().unwrap().filter(provider, &mut processor) } pub fn get_m3u_filename(&self) -> Option<&String> { @@ -858,7 +859,7 @@ impl Config { self.t_api_proxy = Arc::new(RwLock::new(api_proxy)); } - fn _get_target_for_user(&self, user_target: Option<(ProxyUserCredentials, String)>) -> Option<(ProxyUserCredentials, &ConfigTarget)> { + fn intern_get_target_for_user(&self, user_target: Option<(ProxyUserCredentials, String)>) -> Option<(ProxyUserCredentials, &ConfigTarget)> { match user_target { Some((user, target_name)) => { for source in &self.sources { @@ -884,11 +885,11 @@ impl Config { // } pub fn get_target_for_user(&self, username: &str, password: &str) -> Option<(ProxyUserCredentials, &ConfigTarget)> { - self.t_api_proxy.read().unwrap().as_ref().and_then(|api_proxy| self._get_target_for_user(api_proxy.get_target_name(username, password))) + self.t_api_proxy.read().unwrap().as_ref().and_then(|api_proxy| self.intern_get_target_for_user(api_proxy.get_target_name(username, password))) } pub fn get_target_for_user_by_token(&self, token: &str) -> Option<(ProxyUserCredentials, &ConfigTarget)> { - self.t_api_proxy.read().unwrap().as_ref().and_then(|api_proxy| self._get_target_for_user(api_proxy.get_target_name_by_token(token))) + self.t_api_proxy.read().unwrap().as_ref().and_then(|api_proxy| self.intern_get_target_for_user(api_proxy.get_target_name_by_token(token))) } pub fn get_input_by_id(&self, input_id: u16) -> Option<&ConfigInput> { @@ -1038,7 +1039,7 @@ impl Config { /// * `target_args` the program parameters given with `-target` parameter. /// * `sources` configured sources in config file /// -pub fn validate_targets(target_args: &Option>, sources: &Vec) -> Result { +pub fn validate_targets(target_args: Option<&Vec>, sources: &Vec) -> Result { let mut enabled = true; let mut inputs: Vec = vec![]; let mut targets: Vec = vec![]; diff --git a/src/model/playlist.rs b/src/model/playlist.rs index c4f31d109..e54fbb55e 100644 --- a/src/model/playlist.rs +++ b/src/model/playlist.rs @@ -242,9 +242,9 @@ pub struct M3uPlaylistItem { } impl M3uPlaylistItem { - pub fn to_m3u(&self, target_options: &Option, url: Option<&str>) -> String { + pub fn to_m3u(&self, target_options: Option<&ConfigTargetOptions>, url: Option<&str>) -> String { let options = target_options.as_ref(); - let ignore_logo = options.map_or(false, |o| o.ignore_logo); + let ignore_logo = options.is_some_and(|o| o.ignore_logo); let mut line = format!("#EXTINF:-1 tvg-id=\"{}\" tvg-name=\"{}\" group-title=\"{}\"", self.epg_channel_id.as_ref().map_or("", |o| o.as_ref()), self.name, self.group); @@ -260,7 +260,7 @@ impl M3uPlaylistItem { (time_shift, "timeshift"), (rec, "tvg-rec");); - format!("{},{}\n{}", line, self.title, if url.is_none() { self.url.as_str() } else { url.unwrap() }) + format!("{},{}\n{}", line, self.title, url.unwrap_or_else(|| self.url.as_str())) } } diff --git a/src/processing/playlist_processor.rs b/src/processing/playlist_processor.rs index 722d50ac2..1d4eed912 100644 --- a/src/processing/playlist_processor.rs +++ b/src/processing/playlist_processor.rs @@ -127,7 +127,7 @@ fn sort_playlist(target: &ConfigTarget, new_playlist: &mut [PlaylistGroup]) { } } -fn exec_rename(pli: &PlaylistItem, rename: &Option>) { +fn exec_rename(pli: &PlaylistItem, rename: Option<&Vec>) { if let Some(renames) = rename { if !renames.is_empty() { let result = pli; @@ -161,7 +161,7 @@ fn rename_playlist(playlist: &mut [PlaylistGroup], target: &ConfigTarget) -> Opt } } - grp.channels.iter_mut().for_each(|pli| exec_rename(pli, &target.rename)); + grp.channels.iter_mut().for_each(|pli| exec_rename(pli, target.rename.as_ref())); new_playlist.push(grp); } return Some(new_playlist); @@ -463,7 +463,7 @@ fn flatten_groups(playlistgroups: Vec) -> Vec { sort_order } -async fn process_playlist<'a>(playlists: &mut [FetchedPlaylist<'a>], +async fn process_playlist(playlists: &mut [FetchedPlaylist<'_>], target: &ConfigTarget, cfg: &Config, stats: &mut HashMap, @@ -546,7 +546,7 @@ pub async fn exec_processing(cfg: Arc, targets: Arc) { // print stats info!("{}", stats_msg); // send stats - send_message(&MsgKind::Stats, &cfg.messaging, stats_msg.as_str()); + send_message(&MsgKind::Stats, cfg.messaging.as_ref(), stats_msg.as_str()); // log errors for err in &errors { error!("{}", err.message); @@ -554,6 +554,6 @@ pub async fn exec_processing(cfg: Arc, targets: Arc) { // send errors if let Some(message) = get_errors_notify_message!(errors, 255) { let error_msg = format!("{{\"errors\": \"{}\"}}", message.as_str()); - send_message(&MsgKind::Error, &cfg.messaging, error_msg.as_str()); + send_message(&MsgKind::Error, cfg.messaging.as_ref(), error_msg.as_str()); } } \ No newline at end of file diff --git a/src/processing/playlist_watch.rs b/src/processing/playlist_watch.rs index 0ea178a82..3937c05d5 100644 --- a/src/processing/playlist_watch.rs +++ b/src/processing/playlist_watch.rs @@ -71,7 +71,7 @@ fn handle_watch_notification(cfg: &Config, added: &BTreeSet, removed: &B if !message.is_empty() { let msg = format!("Changes {}/{}\n{}", target_name, group_name, message.join("")); info!("{}", &msg); - send_message(&MsgKind::Watch, &cfg.messaging, &msg); + send_message(&MsgKind::Watch, cfg.messaging.as_ref(), &msg); } } diff --git a/src/processing/xmltv_parser.rs b/src/processing/xmltv_parser.rs index 5f3410b0f..487f87a72 100644 --- a/src/processing/xmltv_parser.rs +++ b/src/processing/xmltv_parser.rs @@ -19,10 +19,10 @@ impl TVGuide { let mut filter_tags = |tag: XmlTag| { if match tag.name.as_str() { EPG_TAG_CHANNEL => { - tag.get_attribute_value(EPG_ATTRIB_ID).map_or(false, |val| channel_ids.contains(val)) + tag.get_attribute_value(EPG_ATTRIB_ID).is_some_and(|val| channel_ids.contains(val)) } EPG_TAG_PROGRAMME => { - tag.get_attribute_value(EPG_ATTRIB_CHANNEL).map_or(false, |val| channel_ids.contains(val)) + tag.get_attribute_value(EPG_ATTRIB_CHANNEL).is_some_and(|val| channel_ids.contains(val)) }, EPG_TAG_TV => { tv_attributes.clone_from(&tag.attributes); diff --git a/src/processing/xtream_processor.rs b/src/processing/xtream_processor.rs index 039a648b8..aed662082 100644 --- a/src/processing/xtream_processor.rs +++ b/src/processing/xtream_processor.rs @@ -4,7 +4,7 @@ use crate::model::playlist::FetchedPlaylist; use crate::processing::playlist_processor::ProcessingPipe; use crate::utils::download; -pub async fn playlist_resolve_series<'a>(target: &ConfigTarget, errors: &mut Vec, +pub async fn playlist_resolve_series(target: &ConfigTarget, errors: &mut Vec, pipe: &ProcessingPipe, fpl: &mut FetchedPlaylist<'_>, new_fpl: &mut FetchedPlaylist<'_>) { diff --git a/src/repository/kodi_repository.rs b/src/repository/kodi_repository.rs index d64d3906b..04f403464 100644 --- a/src/repository/kodi_repository.rs +++ b/src/repository/kodi_repository.rs @@ -1,5 +1,6 @@ use std::fs::File; use std::io::Write; +use std::sync::LazyLock; use chrono::Datelike; use log::error; use crate::create_m3u_filter_error_result; @@ -16,9 +17,9 @@ struct KodiStyle { } fn sanitize_for_filename(text: &str, underscore_whitespace: bool) -> String { - return text.chars().filter(|c| c.is_alphanumeric() || c.is_whitespace()) + text.chars().filter(|c| c.is_alphanumeric() || c.is_whitespace()) .map(|c| if underscore_whitespace { if c.is_whitespace() { '_' } else { c } } else { c }) - .collect::(); + .collect::() } fn kodi_style_rename_year(name: &String, style: &KodiStyle) -> (String, Option) { @@ -67,15 +68,22 @@ fn kodi_style_rename(name: &String, style: &KodiStyle) -> String { String::from(name) } +static KODY_STYLE: LazyLock = LazyLock::new(|| KodiStyle { + season: regex::Regex::new(r"[Ss]\d\d").unwrap(), + episode: regex::Regex::new(r"[Ee]\d\d").unwrap(), + year: regex::Regex::new(r"\d\d\d\d").unwrap(), + whitespace: regex::Regex::new(r"\s+").unwrap(), +}); -pub fn kodi_write_strm_playlist(target: &ConfigTarget, cfg: &Config, new_playlist: &[PlaylistGroup], filename: &Option) -> Result<(), M3uFilterError> { + +pub fn kodi_write_strm_playlist(target: &ConfigTarget, cfg: &Config, new_playlist: &[PlaylistGroup], filename: Option<&String>) -> Result<(), M3uFilterError> { if !new_playlist.is_empty() { if filename.is_none() { return Err(M3uFilterError::new(M3uFilterErrorKind::Notify, "write strm playlist failed: ".to_string())); } - let underscore_whitespace = target.options.as_ref().map_or(false, |o| o.underscore_whitespace); - let cleanup = target.options.as_ref().map_or(false, |o| o.cleanup); - let kodi_style = target.options.as_ref().map_or(false, |o| o.kodi_style); + let underscore_whitespace = target.options.as_ref().is_some_and(|o| o.underscore_whitespace); + let cleanup = target.options.as_ref().is_some_and(|o| o.cleanup); + let kodi_style = target.options.as_ref().is_some_and(|o| o.kodi_style); if let Some(path) = file_utils::get_file_path(&cfg.working_dir, Some(std::path::PathBuf::from(&filename.as_ref().unwrap()))) { if cleanup { @@ -95,13 +103,7 @@ pub fn kodi_write_strm_playlist(target: &ConfigTarget, cfg: &Config, new_playlis }; let mut kodi_file_name = sanitize_for_filename(&header.title, underscore_whitespace); if kodi_style { - let style = KodiStyle { - season: regex::Regex::new(r"[Ss]\d\d").unwrap(), - episode: regex::Regex::new(r"[Ee]\d\d").unwrap(), - year: regex::Regex::new(r"\d\d\d\d").unwrap(), - whitespace: regex::Regex::new(r"\s+").unwrap(), - }; - kodi_file_name = kodi_style_rename(&kodi_file_name, &style); + kodi_file_name = kodi_style_rename(&kodi_file_name, &KODY_STYLE); } let file_path = dir_path.join(format!("{kodi_file_name}.strm")); match File::create(&file_path) { diff --git a/src/repository/m3u_playlist_iterator.rs b/src/repository/m3u_playlist_iterator.rs index 449c052c2..5279a26c8 100644 --- a/src/repository/m3u_playlist_iterator.rs +++ b/src/repository/m3u_playlist_iterator.rs @@ -11,6 +11,8 @@ use crate::utils::file_lock_manager::FileReadGuard; pub struct M3uPlaylistIterator { reader: IndexedDocumentReader, base_url: String, + username: String, + password: String, target_options: Option, proxy_type: ProxyType, _file_lock: FileReadGuard, @@ -26,28 +28,63 @@ impl M3uPlaylistIterator { let target_path = ensure_target_storage_path(cfg, target.name.as_str())?; let (m3u_path, idx_path) = m3u_get_file_paths(&target_path); - let file_lock = cfg.file_locks.read_lock(&m3u_path).map_err(|err| - M3uFilterError::new(M3uFilterErrorKind::Info, format!("Could not lock document {m3u_path:?}: {err}")) - )?; + let file_lock = cfg.file_locks.read_lock(&m3u_path).map_err(|err| { + M3uFilterError::new( + M3uFilterErrorKind::Info, + format!("Could not lock document {m3u_path:?}: {err}"), + ) + })?; - let reader = IndexedDocumentReader::::new(&m3u_path, &idx_path).map_err(|err| - M3uFilterError::new(M3uFilterErrorKind::Info, format!("Could not deserialize file {m3u_path:?} - {err}")))?; + let reader = + IndexedDocumentReader::::new(&m3u_path, &idx_path).map_err(|err| { + M3uFilterError::new( + M3uFilterErrorKind::Info, + format!("Could not deserialize file {m3u_path:?} - {err}"), + ) + })?; let server_info = get_user_server_info(cfg, user); - let base_url = format!( - "{}/m3u-stream/{}/{}", - server_info.get_base_url(), user.username, user.password - ); - + // let base_url = format!( + // "{}/m3u-stream/{}/{}", + // server_info.get_base_url(), user.username, user.password + // ); + // Ok(Self { reader, - base_url, + base_url: server_info.get_base_url(), + username: user.username.to_string(), + password: user.password.to_string(), target_options: target.options.clone(), proxy_type: user.proxy.clone(), - _file_lock: file_lock, // Speichern des Locks in der Struct + _file_lock: file_lock, // Save lock inside struct started: false, }) } + + fn get_stream_url(&self, m3u_pli: &M3uPlaylistItem, typed: bool) -> String { + if typed { + let stream_type = match m3u_pli.item_type { + PlaylistItemType::Live + | PlaylistItemType::Catchup + | PlaylistItemType::LiveUnknown + | PlaylistItemType::LiveHls => "live", + PlaylistItemType::Video => "movie", + PlaylistItemType::Series + | PlaylistItemType::SeriesInfo + | PlaylistItemType::SeriesEpisode => "series", + }; + format!("{}/{stream_type}/{}/{}/{}", + &self.base_url, + &self.username, + &self.password, + m3u_pli.virtual_id + ) + } else { + format!("{}/{}/{}/{}", + &self.base_url, &self.username, &self.password, m3u_pli.virtual_id + ) + } + } } impl Iterator for M3uPlaylistIterator { @@ -62,18 +99,21 @@ impl Iterator for M3uPlaylistIterator { self.reader.next().map(|m3u_pli| { // TODO hls and unknown reverse proxy match m3u_pli.item_type { - PlaylistItemType::LiveHls => { - m3u_pli.to_m3u(&self.target_options, None) - } + PlaylistItemType::LiveHls => m3u_pli.to_m3u(self.target_options.as_ref(), None), _ => match self.proxy_type { - ProxyType::Reverse => { - m3u_pli.to_m3u(&self.target_options, Some(format!("{}/{}", &self.base_url, m3u_pli.virtual_id).as_str())) - } - ProxyType::Redirect => { - m3u_pli.to_m3u(&self.target_options, None) - } - } + ProxyType::Reverse => m3u_pli.to_m3u( + self.target_options.as_ref(), + Some( + self.get_stream_url( + &m3u_pli, + self.target_options.as_ref().unwrap().m3u_use_type_url, + ) + .as_str(), + ), + ), + ProxyType::Redirect => m3u_pli.to_m3u(self.target_options.as_ref(), None), + }, } }) } -} \ No newline at end of file +} diff --git a/src/repository/m3u_repository.rs b/src/repository/m3u_repository.rs index 9782fa834..ada21ebf5 100644 --- a/src/repository/m3u_repository.rs +++ b/src/repository/m3u_repository.rs @@ -39,7 +39,7 @@ fn persist_m3u_playlist_as_text(target: &ConfigTarget, cfg: &Config, m3u_playlis let mut buf_writer = BufWriter::new(file); let _ = buf_writer.write(b"#EXTM3U\n"); for m3u in m3u_playlist { - let _ = buf_writer.write(m3u.to_m3u(&target.options, None).as_bytes()); + let _ = buf_writer.write(m3u.to_m3u(target.options.as_ref(), None).as_bytes()); let _ = buf_writer.write(b"\n"); } } diff --git a/src/repository/playlist_repository.rs b/src/repository/playlist_repository.rs index 371df87fc..9efae09fc 100644 --- a/src/repository/playlist_repository.rs +++ b/src/repository/playlist_repository.rs @@ -47,7 +47,7 @@ pub fn persist_playlist(playlist: &mut [PlaylistGroup], epg: Option<&Epg>, let result = match output.target { TargetType::M3u => m3u_write_playlist(target, cfg, &target_path, playlist), TargetType::Xtream => xtream_write_playlist(target, cfg, playlist), - TargetType::Strm => kodi_write_strm_playlist(target, cfg, playlist, &output.filename), + TargetType::Strm => kodi_write_strm_playlist(target, cfg, playlist, output.filename.as_ref()), }; if let Err(err) = result { diff --git a/src/utils/download.rs b/src/utils/download.rs index fa1cc3b87..584326d3b 100644 --- a/src/utils/download.rs +++ b/src/utils/download.rs @@ -33,7 +33,7 @@ pub async fn get_m3u_playlist(cfg: &Config, input: &ConfigInput, working_dir: &S } } -pub async fn get_xtream_playlist_series<'a>(fpl: &mut FetchedPlaylist<'a>, errors: &mut Vec, resolve_delay: u16) -> Vec { +pub async fn get_xtream_playlist_series(fpl: &mut FetchedPlaylist<'_>, errors: &mut Vec, resolve_delay: u16) -> Vec { let input = fpl.input; let mut result: Vec = vec![]; for plg in &mut fpl.playlistgroups { diff --git a/src/utils/file_utils.rs b/src/utils/file_utils.rs index 974d9bbc0..8cb61ca75 100644 --- a/src/utils/file_utils.rs +++ b/src/utils/file_utils.rs @@ -28,7 +28,7 @@ pub fn get_exe_path() -> PathBuf { Ok(exe) => { match fs::read_link(&exe) { Ok(f) => f.parent().map_or(default_path, std::path::Path::to_path_buf), - Err(_) => return exe.parent().map_or(default_path, std::path::Path::to_path_buf) + Err(_) => exe.parent().map_or(default_path, std::path::Path::to_path_buf) } } Err(_) => default_path diff --git a/src/utils/json_utils.rs b/src/utils/json_utils.rs index abb9af433..3448a62cd 100644 --- a/src/utils/json_utils.rs +++ b/src/utils/json_utils.rs @@ -72,7 +72,7 @@ pub fn json_filter_file(file_path: &Path, filter: &HashMap<&str, &str>) -> Vec>(reader).flatten() { if let Some(item) = entry.as_object() { if filter.iter().all(|(&key, &value)| { - item.get(key).map_or(false, |field_value| match field_value { + item.get(key).is_some_and(|field_value| match field_value { Value::String(s) => s == value, Value::Number(n) => value.parse::().ok() == n.as_i64(), _ => false, diff --git a/test/rest-api.http b/test/rest-api.http index 78486c076..fa0eb9adf 100644 --- a/test/rest-api.http +++ b/test/rest-api.http @@ -19,12 +19,16 @@ Content-Type: application/json ### m3u playlist GET http://localhost:8901/m3u?username=xt&password=xt -### m3u playlist +### m3u playlist - form auth POST http://localhost:8901/player_api.php Content-Type: application/x-www-form-urlencoded username=xt&password=xt +### m3u playlist reverse +GET http://localhost:8901/m3u?username=xtream&password=xtream.secret + + ### xtream live_categories GET http://localhost:8901/player_api.php?action=get_live_categories&username=xt&password=xt