diff --git a/src/model/xmltv.rs b/src/model/xmltv.rs index 454b913e2..5a9caaa00 100644 --- a/src/model/xmltv.rs +++ b/src/model/xmltv.rs @@ -1,7 +1,7 @@ -use std::collections::{HashMap}; -use std::path::PathBuf; -use quick_xml::{Error, Writer}; use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; +use quick_xml::{Error, Writer}; +use std::collections::HashMap; +use std::path::PathBuf; pub const EPG_TAG_TV: &str = "tv"; pub const EPG_TAG_PROGRAMME: &str = "programme"; @@ -24,7 +24,6 @@ pub struct XmlTag { } impl XmlTag { - pub(crate) fn new(name: String, attribs: Option>) -> Self { Self { name, diff --git a/src/processing/parser/xmltv.rs b/src/processing/parser/xmltv.rs index 2f21c5961..fb8b106bb 100644 --- a/src/processing/parser/xmltv.rs +++ b/src/processing/parser/xmltv.rs @@ -1,5 +1,5 @@ -use crate::model::{EpgNamePrefix, EpgSmartMatchConfig, PersistedEpgSource}; use crate::model::{Epg, TVGuide, XmlTag, EPG_ATTRIB_CHANNEL, EPG_ATTRIB_ID, EPG_TAG_CHANNEL, EPG_TAG_DISPLAY_NAME, EPG_TAG_ICON, EPG_TAG_PROGRAMME, EPG_TAG_TV}; +use crate::model::{EpgNamePrefix, EpgSmartMatchConfig, PersistedEpgSource}; use crate::processing::processor::epg::EpgIdCache; use crate::utils::compressed_file_reader::CompressedFileReader; use crate::utils::CONSTANTS; @@ -9,8 +9,8 @@ use quick_xml::Reader; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use std::borrow::Cow; use std::cmp::min; -use std::collections::{HashMap}; use std::collections::hash_map::Entry; +use std::collections::HashMap; use std::mem; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex, RwLock}; @@ -89,18 +89,19 @@ pub fn normalize_channel_name(name: &str, normalize_config: &EpgSmartMatchConfig } impl TVGuide { - fn merge(mut epgs: Vec) -> Option { - if epgs.is_empty() { - return None; + pub fn merge(mut epgs: Vec) -> Option { + if let Some(first_epg) = epgs.get_mut(0) { + let first_epg_attributes = first_epg.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, + }) + } else { + None } - 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, - }) } fn prepare_tag(id_cache: &mut EpgIdCache, tag: &mut XmlTag, smart_match: bool) { @@ -272,18 +273,15 @@ impl TVGuide { } } - pub fn filter(&self, id_cache: &mut EpgIdCache) -> Option { + pub fn filter(&self, id_cache: &mut EpgIdCache) -> Option> { if id_cache.channel_epg_id.is_empty() && id_cache.normalized.is_empty() { return None; } - let epgs: Vec = self.get_epg_sources().iter() + let mut epg_sources: Vec = self.get_epg_sources().iter() .filter_map(|epg_source| Self::process_epg_file(id_cache, epg_source)) .collect(); - if epgs.len() == 1 { - epgs.into_iter().next() - } else { - Self::merge(epgs) - } + epg_sources.sort_by(|a, b| a.priority.cmp(&b.priority)); + Some(epg_sources) } } diff --git a/src/processing/processor/epg.rs b/src/processing/processor/epg.rs index c7ac18fda..52c3908fe 100644 --- a/src/processing/processor/epg.rs +++ b/src/processing/processor/epg.rs @@ -1,6 +1,6 @@ +use crate::model::{Epg, TVGuide, XmlTag, EPG_ATTRIB_ID}; use crate::model::{EpgConfig, EpgSmartMatchConfig}; use crate::model::{FetchedPlaylist, PlaylistItem, XtreamCluster}; -use crate::model::{Epg, XmlTag, EPG_ATTRIB_ID}; use crate::processing::parser::xmltv::normalize_channel_name; use log::debug; use rphonetic::{DoubleMetaphone, Encoder}; @@ -134,46 +134,57 @@ impl EpgIdCache<'_> { /// ``` fn assign_channel_epg(new_epg: &mut Vec, fp: &mut FetchedPlaylist, id_cache: &mut EpgIdCache) { if let Some(tv_guide) = &fp.epg { - if let Some(epg) = tv_guide.filter(id_cache) { - // // icon tags - let icon_tags: HashMap<&String, &XmlTag> = epg.children.iter() + let mut processed_epgs = vec![]; + if let Some(epg_sources) = tv_guide.filter(id_cache) { + let mut icon_assigned = HashSet::new(); + for epg_source in epg_sources { + // icon tags + let icon_tags: HashMap<&String, &XmlTag> = epg_source.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(); - let filter_live = |c: &&mut PlaylistItem| c.header.xtream_cluster == XtreamCluster::Live; - - let assign_values = |chan: &mut PlaylistItem| { - if id_cache.smart_match_enabled && chan.header.epg_channel_id.is_none() { - // if the channel has no epg_id or the epg_id is not present in xmltv/tvguide then we need to match one from existing tvguide - let not_processed = match &chan.header.epg_channel_id { - None => true, - Some(epg_id) => !id_cache.processed.contains(epg_id), - }; - if not_processed { - let normalized = id_cache.normalize(&chan.header.name); - if let Some(epg_id) = id_cache.normalized.get(&normalized) { - chan.header.epg_channel_id.clone_from(epg_id); - } - } - } - 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 epg.logo_override || chan.header.logo.is_empty() { - chan.header.logo = (*icon).to_string(); - } - if epg.logo_override || chan.header.logo_small.is_empty() { - chan.header.logo_small = (*icon).to_string(); + let assign_values = |chan: &mut PlaylistItem| { + if id_cache.smart_match_enabled && chan.header.epg_channel_id.is_none() { + // if the channel has no epg_id or the epg_id is not present in xmltv/tvguide then we need to match one from existing tvguide + let not_processed = match &chan.header.epg_channel_id { + None => true, + Some(epg_id) => !id_cache.processed.contains(epg_id), + }; + if not_processed { + let normalized = id_cache.normalize(&chan.header.name); + if let Some(epg_id) = id_cache.normalized.get(&normalized) { + chan.header.epg_channel_id.clone_from(epg_id); } } } - } - }; + if let Some(epg_channel_id) = chan.header.epg_channel_id.as_ref() { + if !icon_assigned.contains(epg_channel_id) && + (epg_source.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()) { + icon_assigned.insert(epg_channel_id.to_string()); + if let Some(icon) = icon_tag.icon.as_ref() { + if epg_source.logo_override || chan.header.logo.is_empty() { + chan.header.logo = (*icon).to_string(); + } + if epg_source.logo_override || chan.header.logo_small.is_empty() { + chan.header.logo_small = (*icon).to_string(); + } + } + } + } + } + }; - fp.playlistgroups.iter_mut() - .flat_map(|g| &mut g.channels) - .filter(filter_live) - .for_each(assign_values); + let filter_live = |c: &&mut PlaylistItem| c.header.xtream_cluster == XtreamCluster::Live; + fp.playlistgroups.iter_mut() + .flat_map(|g| &mut g.channels) + .filter(filter_live) + .for_each(assign_values); + processed_epgs.push(epg_source); + } + } + + if let Some(epg) = TVGuide::merge(processed_epgs) { new_epg.push(epg); } }