Merge pull request #253 from euzu/feature/epg_logo_override

epg auto_epg removed and logo_override  added
This commit is contained in:
euzu
2025-05-15 16:43:50 +02:00
committed by GitHub
7 changed files with 55 additions and 33 deletions
+12 -4
View File
@@ -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`.
+2 -4
View File
@@ -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<Vec<EpgSource>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
+30 -19
View File
@@ -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);
}
}
}
+2
View File
@@ -60,6 +60,7 @@ impl XmlTag {
#[derive(Debug, Clone)]
pub struct Epg {
pub priority: i16,
pub logo_override: bool,
pub attributes: Option<HashMap<String, String>>,
pub children: Vec<XmlTag>,
}
@@ -82,6 +83,7 @@ impl Epg {
pub struct PersistedEpgSource {
pub file_path: PathBuf,
pub priority: i16,
pub logo_override: bool,
}
#[derive(Debug, Clone)]
+3
View File
@@ -96,6 +96,7 @@ impl TVGuide {
let first_epg_attributes = epgs.get_mut(0).unwrap().attributes.take();
let merged_children: Vec<XmlTag> = 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> {
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()),
+5 -5
View File
@@ -137,8 +137,8 @@ fn assign_channel_epg(new_epg: &mut Vec<Epg>, 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<Epg>, 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();
}
}
+1 -1
View File
@@ -28,7 +28,7 @@ pub async fn get_xmltv(client: Arc<reqwest::Client>, _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);