diff --git a/CHANGELOG.md b/CHANGELOG.md index a96e7efbc..9fae6458e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +1,27 @@ # Changelog # 3.1.0 (2025-05-xx) -- !BREAKING_CHANGE! epg config. Added priority, url config is now renamed to sources, priority is `optional` -The `priority` value determines the importance or order of processing. Lower numbers mean higher priority. That is: +- !BREAKING_CHANGE! epg refactored + - url config is now renamed to sources + - Added `priority`, priority is `optional` + - `auto_epg` is now removed, use `url: auto` instead. + - Added `logo_override` to overwrite logo from epg. + +**Npte:** The `priority` value determines the importance or order of processing. Lower numbers mean higher priority. That is: A `priority` of `0` is higher than `1`. **Negative numbers** are allowed and represent even higher priority + ```yaml epg: sources: + - url: "auto" + priority: -2 + logo_override: true - url: "http://localhost:3001/xmltv.php?epg_id=1" priority: -1 - url: "http://localhost:3001/xmltv.php?epg_id=2" priority: 3 - url: "http://localhost:3001/xmltv.php?epg_id=3" priority: 0 - - url: "http://localhost:3001/xmltv.php?epg_id=4" - priority: 1 smart_match: enabled: true fuzzy_matching: true @@ -60,6 +67,7 @@ The template can now be used for sequence - '(?i)\bSD\b' ``` + # 3.0.0 (2025-05-12) - !BREAKING_CHANGE! user has now the attribute `ui_enabled` to disable/enable web_ui for user. You need to migrate the user db if you have used `use_user_db:true`. diff --git a/src/model/config_epg.rs b/src/model/config_epg.rs index ea51b218b..d03cd7f08 100644 --- a/src/model/config_epg.rs +++ b/src/model/config_epg.rs @@ -9,6 +9,8 @@ pub struct EpgSource { pub(crate) url: String, #[serde(default)] pub priority: i16, + #[serde(default)] + pub logo_override: bool, } impl EpgSource { @@ -146,10 +148,6 @@ impl Default for EpgSmartMatchConfig { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct EpgConfig { - #[serde(default)] - pub auto_epg: bool, - #[serde(default)] - pub auto_epg_priority: i16, #[serde(default, skip_serializing_if = "Option::is_none")] pub sources: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] diff --git a/src/model/config_input.rs b/src/model/config_input.rs index 2dbb97d57..6a011bae8 100644 --- a/src/model/config_input.rs +++ b/src/model/config_input.rs @@ -1,11 +1,11 @@ use crate::tuliprox_error::{create_tuliprox_error_result, handle_tuliprox_error_result_list, info_err, TuliproxError, TuliproxErrorKind}; -use crate::model::{EpgConfig, EpgSource}; +use crate::model::{EpgConfig}; use crate::utils::default_as_true; use crate::utils::get_trimmed_string; use crate::utils::request::{get_base_url_from_str, get_credentials_from_url, get_credentials_from_url_str}; use enum_iterator::Sequence; use log::{debug, warn}; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fmt::Display; use std::str::FromStr; use url::Url; @@ -295,26 +295,37 @@ impl ConfigInput { if let Some(epg) = self.epg.as_mut() { let _ = epg.prepare(include_computed); - if include_computed && epg.auto_epg { - let (username, password) = if self.username.is_none() || self.password.is_none() { - get_credentials_from_url_str(&self.url) - } else { - (self.username.clone(), self.password.clone()) - }; + if include_computed { + let has_auto_epg = epg.t_sources.iter().any(|e| e.url.trim() == "auto"); + if has_auto_epg { + let (username, password) = if self.username.is_none() || self.password.is_none() { + get_credentials_from_url_str(&self.url) + } else { + (self.username.clone(), self.password.clone()) + }; - if username.is_none() || password.is_none() { - warn!("auto_epg is enabled for input {}, but no credentials could be extracted", self.name); - } else if !self.t_base_url.is_empty() { - let provider_epg_url = format!("{}/xmltv.php?username={}&password={}", self.t_base_url, username.unwrap_or_default(), password.unwrap_or_default()); - if ! epg.t_sources.iter().any(|e| e.url == provider_epg_url) { - debug!("Added provider epg url {provider_epg_url} for input {}", self.name); - epg.t_sources.push(EpgSource { - url: provider_epg_url, - priority: epg.auto_epg_priority, + if username.is_none() || password.is_none() { + warn!("auto_epg is enabled for input {}, but no credentials could be extracted", self.name); + } else if !self.t_base_url.is_empty() { + let provider_epg_url = format!("{}/xmltv.php?username={}&password={}", self.t_base_url, username.unwrap_or_default(), password.unwrap_or_default()); + + epg.t_sources.iter_mut().for_each(|epg_source| { + if epg_source.url.trim() == "auto" { + debug!("Added provider epg url {provider_epg_url} for input {}", self.name); + epg_source.url.clone_from(&provider_epg_url); + } }); + + epg.t_sources = { + let mut seen_urls = HashSet::new(); + epg.t_sources + .drain(..) + .filter(|src| seen_urls.insert(src.url.clone())) + .collect() + }; + } else { + warn!("auto_epg is enabled for input {}, but url could not be parsed {}", self.name, self.url); } - } else { - warn!("auto_epg is enabled for input {}, but url could not be parsed {}", self.name, self.url); } } } diff --git a/src/model/xmltv.rs b/src/model/xmltv.rs index f57abb1a7..454b913e2 100644 --- a/src/model/xmltv.rs +++ b/src/model/xmltv.rs @@ -60,6 +60,7 @@ impl XmlTag { #[derive(Debug, Clone)] pub struct Epg { pub priority: i16, + pub logo_override: bool, pub attributes: Option>, pub children: Vec, } @@ -82,6 +83,7 @@ impl Epg { pub struct PersistedEpgSource { pub file_path: PathBuf, pub priority: i16, + pub logo_override: bool, } #[derive(Debug, Clone)] diff --git a/src/processing/parser/xmltv.rs b/src/processing/parser/xmltv.rs index 6f989b717..2f21c5961 100644 --- a/src/processing/parser/xmltv.rs +++ b/src/processing/parser/xmltv.rs @@ -96,6 +96,7 @@ impl TVGuide { let first_epg_attributes = epgs.get_mut(0).unwrap().attributes.take(); let merged_children: Vec = epgs.into_iter().flat_map(|epg| epg.children).collect(); Some(Epg { + logo_override: false, priority: 0, attributes: first_epg_attributes, children: merged_children, @@ -261,6 +262,7 @@ impl TVGuide { } Some(Epg { + logo_override: epg_source.logo_override, priority: epg_source.priority, attributes: tv_attributes, children, @@ -456,6 +458,7 @@ pub fn flatten_tvguide(tv_guides: &[Epg]) -> Option { epg_children.lock().unwrap().extend(children); }); let epg = Epg { + logo_override: false, priority: 0, attributes: epg_attributes, children: mem::take(&mut *epg_children.lock().unwrap()), diff --git a/src/processing/processor/epg.rs b/src/processing/processor/epg.rs index 77bb6728d..43981cecd 100644 --- a/src/processing/processor/epg.rs +++ b/src/processing/processor/epg.rs @@ -137,8 +137,8 @@ fn assign_channel_epg(new_epg: &mut Vec, fp: &mut FetchedPlaylist, id_cache if let Some(epg) = tv_guide.filter(id_cache) { // // icon tags let icon_tags: HashMap<&String, &XmlTag> = epg.children.iter() - .filter(|tag| tag.icon.is_some() && tag.get_attribute_value(EPG_ATTRIB_ID).is_some()) - .map(|t| (t.get_attribute_value(EPG_ATTRIB_ID).unwrap(), t)).collect(); + .filter(|tag| tag.icon.is_some() && tag.get_attribute_value(EPG_ATTRIB_ID).is_some()) + .map(|t| (t.get_attribute_value(EPG_ATTRIB_ID).unwrap(), t)).collect(); let filter_live = |c: &&mut PlaylistItem| c.header.xtream_cluster == XtreamCluster::Live; // let filter_missing_epg_id = |chan: &mut PlaylistItem| chan.header.epg_channel_id.is_none() || chan.header.logo.is_empty() || chan.header.logo_small.is_empty(); @@ -158,13 +158,13 @@ fn assign_channel_epg(new_epg: &mut Vec, fp: &mut FetchedPlaylist, id_cache } } } - if chan.header.epg_channel_id.is_some() && (chan.header.logo.is_empty() || chan.header.logo_small.is_empty()) { + if chan.header.epg_channel_id.is_some() && (epg.logo_override || chan.header.logo.is_empty() || chan.header.logo_small.is_empty()) { if let Some(icon_tag) = icon_tags.get(chan.header.epg_channel_id.as_ref().unwrap()) { if let Some(icon) = icon_tag.icon.as_ref() { - if chan.header.logo.is_empty() { + if epg.logo_override || chan.header.logo.is_empty() { chan.header.logo = (*icon).to_string(); } - if chan.header.logo_small.is_empty() { + if epg.logo_override || chan.header.logo_small.is_empty() { chan.header.logo_small = (*icon).to_string(); } } diff --git a/src/utils/network/epg.rs b/src/utils/network/epg.rs index db09f1a48..b49566f02 100644 --- a/src/utils/network/epg.rs +++ b/src/utils/network/epg.rs @@ -28,7 +28,7 @@ pub async fn get_xmltv(client: Arc, _cfg: &Config, input: &Conf for epg_source in &epg_config.t_sources { match download_epg_file(&epg_source.url, &client, input, working_dir).await { Ok(file_path) => { - file_paths.push(PersistedEpgSource {file_path, priority: epg_source.priority}); + file_paths.push(PersistedEpgSource {file_path, priority: epg_source.priority, logo_override: epg_source.logo_override}); } Err(err) => { errors.push(err);