Files
tuliprox/src/processing/m3u_parser.rs
T

199 lines
6.7 KiB
Rust
Raw Normal View History

2023-10-25 09:57:44 +02:00
use std::borrow::{BorrowMut};
use std::cell::RefCell;
use std::rc::Rc;
use crate::model::config::Config;
use crate::model::model_config::default_as_empty_rc_str;
use crate::model::model_playlist::{default_playlist_item_type, default_stream_cluster, PlaylistGroup, PlaylistItem, PlaylistItemHeader, PlaylistItemType, XtreamCluster};
2023-03-19 17:16:05 +01:00
fn token_value(it: &mut std::str::Chars) -> String {
if let Some(oc) = it.next() {
if oc == '"' {
return get_value(it);
}
}
String::from("")
}
fn get_value(it: &mut std::str::Chars) -> String {
let mut result: Vec<char> = vec![];
2023-05-03 17:46:39 +02:00
for oc in it.by_ref() {
2023-03-19 17:16:05 +01:00
if oc == '"' {
break;
}
result.push(oc);
}
2023-05-03 17:46:39 +02:00
result.iter().collect::<String>()
2023-03-19 17:16:05 +01:00
}
fn token_till(it: &mut std::str::Chars, stop_char: char) -> Option<String> {
let mut result: Vec<char> = vec![];
2023-05-03 17:46:39 +02:00
for ch in it.by_ref() {
if ch == stop_char {
break;
} else if ch.is_whitespace() && result.is_empty() {
continue;
} else {
result.push(ch);
2023-03-19 17:16:05 +01:00
}
}
2023-05-03 17:46:39 +02:00
if !result.is_empty() { Some(result.iter().collect::<String>()) } else { None }
2023-03-19 17:16:05 +01:00
}
fn skip_digit(it: &mut std::str::Chars) -> Option<char> {
loop {
match it.next() {
Some(c) => {
2023-05-03 17:46:39 +02:00
if !(c == '-' || c == '+' || c.is_ascii_digit()) {
2023-03-19 17:16:05 +01:00
return Some(c);
}
}
None => return None,
}
}
}
2023-10-23 18:12:24 +02:00
fn create_empty_playlistitem_header(content: &String, url: String) -> PlaylistItemHeader {
2023-09-29 16:03:20 +02:00
PlaylistItemHeader {
id: default_as_empty_rc_str(),
name: default_as_empty_rc_str(),
logo: default_as_empty_rc_str(),
logo_small: default_as_empty_rc_str(),
group: default_as_empty_rc_str(),
title: default_as_empty_rc_str(),
parent_code: default_as_empty_rc_str(),
audio_track: default_as_empty_rc_str(),
time_shift: default_as_empty_rc_str(),
rec: default_as_empty_rc_str(),
source: Rc::new(content.to_owned()),
url: Rc::new(url),
2023-11-03 09:45:22 +01:00
epg_channel_id: None,
item_type: default_playlist_item_type(),
xtream_cluster: default_stream_cluster(),
additional_properties: None,
series_fetched: false,
2023-09-29 16:03:20 +02:00
}
2023-09-26 19:41:02 +02:00
}
2023-03-19 17:16:05 +01:00
macro_rules! process_header_fields {
($header:expr, $token:expr, $(($prop:ident, $field:expr)),*; $val:expr) => {
match $token {
$(
$field => $header.$prop = Rc::new($val),
)*
_ => {}
}
};
}
2023-10-25 09:57:44 +02:00
fn process_header(video_suffixes: &Vec<&str>, content: &String, url: String) -> PlaylistItemHeader {
2023-10-23 18:12:24 +02:00
let mut plih = create_empty_playlistitem_header(content, url.clone());
2023-03-19 17:16:05 +01:00
let mut it = content.chars();
let line_token = token_till(&mut it, ':');
if line_token == Some(String::from("#EXTINF")) {
let mut c = skip_digit(&mut it);
loop {
2023-05-03 17:46:39 +02:00
if c.is_none() {
2023-03-19 17:16:05 +01:00
break;
}
match c.unwrap() {
',' => plih.title = Rc::new(get_value(&mut it)),
2023-03-19 17:16:05 +01:00
_ => {
let token = token_till(&mut it, '=');
2023-05-03 17:46:39 +02:00
if let Some(t) = token {
2023-03-19 17:16:05 +01:00
let value = token_value(&mut it);
process_header_fields!(plih, t.as_str(),
(id, "tvg-id"),
(group, "group-title"),
(name, "tvg-name"),
(parent_code, "parent-code"),
(audio_track, "audio-track"),
(logo, "tvg-logo"),
(logo_small, "tvg-logo-small"),
(time_shift, "timeshift"),
(rec, "tvg-rec"); value)
2023-03-19 17:16:05 +01:00
}
}
}
c = it.next();
}
if plih.group.is_empty() {
plih.group = Rc::new(String::from("Unknown"));
}
plih.epg_channel_id = Some(Rc::clone(&plih.id));
2023-03-19 17:16:05 +01:00
}
2023-09-26 19:41:02 +02:00
for suffix in video_suffixes {
2023-10-25 09:57:44 +02:00
if url.ends_with(suffix) {
// TODO find Series based on group or configured names
plih.xtream_cluster = XtreamCluster::Video;
plih.item_type = PlaylistItemType::Movie;
2023-10-25 09:57:44 +02:00
break;
}
}
let header = plih.borrow_mut();
if header.name.is_empty() {
if !header.title.is_empty() {
header.name = header.title.clone();
} else if !header.id.is_empty() {
header.name = header.id.clone();
header.title = header.id.clone();
}
2023-09-26 19:41:02 +02:00
}
2023-03-19 17:16:05 +01:00
plih
}
2023-09-26 19:41:02 +02:00
2023-09-29 16:03:20 +02:00
pub(crate) fn parse_m3u(cfg: &Config, lines: &Vec<String>) -> Vec<PlaylistGroup> {
let mut groups: std::collections::HashMap<Rc<String>, Vec<PlaylistItem>> = std::collections::HashMap::new();
let mut sort_order: Vec<Rc<String>> = vec![];
2023-03-19 17:16:05 +01:00
let mut header: Option<String> = None;
let mut group: Option<String> = None;
2023-10-25 09:57:44 +02:00
let video_suffixes = cfg.video.as_ref().unwrap().extensions.iter().map(|ext| ext.as_str()).collect();
2023-03-19 17:16:05 +01:00
for line in lines {
if line.starts_with("#EXTINF") {
header = Some(String::from(line));
continue;
}
if line.starts_with("#EXTGRP") {
group = Some(String::from(&line[8..]));
continue;
}
2023-05-03 17:46:39 +02:00
if line.starts_with('#') {
2023-03-19 17:16:05 +01:00
continue;
}
2023-10-11 14:33:17 +02:00
if let Some(header_value) = header {
2023-10-23 18:12:24 +02:00
let item = PlaylistItem { header: RefCell::new(process_header(&video_suffixes, &header_value, String::from(line))) };
2023-10-11 14:33:17 +02:00
if let Some(group_value) = group {
if item.header.borrow().group.is_empty() {
item.header.borrow_mut().group = Rc::new(group_value);
2023-10-11 14:33:17 +02:00
}
2023-03-19 17:16:05 +01:00
}
let key = Rc::clone(&item.header.borrow().group);
2023-03-19 17:16:05 +01:00
// let key2 = String::from(&item.header.group);
match groups.entry(Rc::clone(&key)) {
2023-03-19 17:16:05 +01:00
std::collections::hash_map::Entry::Vacant(e) => {
e.insert(vec![item]);
sort_order.push(Rc::clone(&key));
2023-03-19 17:16:05 +01:00
}
std::collections::hash_map::Entry::Occupied(mut e) => { e.get_mut().push(item); }
}
}
header = None;
group = None;
}
let mut result: Vec<PlaylistGroup> = vec![];
for (grp_id, (key, channels)) in (1_u32..).zip(groups.into_iter()) {
let cluster = channels.first().map(|pli| pli.header.borrow().xtream_cluster.clone());
result.push(PlaylistGroup { id: grp_id, xtream_cluster: cluster.unwrap(), title: Rc::clone(&key), channels });
2023-03-19 17:16:05 +01:00
}
result.sort_by(|f, s| {
let i1 = sort_order.iter().position(|r| **r == *f.title).unwrap();
let i2 = sort_order.iter().position(|r| **r == *s.title).unwrap();
2023-05-03 17:46:39 +02:00
i1.cmp(&i2)
2023-03-19 17:16:05 +01:00
});
result
}