diff --git a/README.md b/README.md index 85d1948cb..9fcb8e44c 100644 --- a/README.md +++ b/README.md @@ -74,16 +74,40 @@ Has five entries: `enabled`, `persist`, `url`, `prefix`, `suffix`. - `enabled` is optional, default is true, if you disable the processing is skipped - `persist` is optional, you can skip or leave it blank to avoid persisting the input file. The `{}` in the filename is filled with the current timestamp. - `url` for type `m3u` is the download url or a local filename of the input-source. For type `xtream`it is `http://:` + - `headers` is optional, used only for type `xtream` - `username` only mandatory for type `xtream` - `pasword`only mandatory for type `xtream` - `prefix` is optional, it is applied to the given field with the given value - `suffix` is optional, it is applied to the given field with the given value -`prefix` and `suffix` is appended after all processing is done, but before sort. +`prefix` and `suffix` are appended after all processing is done, but before sort. They have 2 fields: - `field` can be `name` , `group`, `title` - `value` a static text +Example input config for `m3u` +``` +sources: + - input: + persist: 'playlist_1_{}.m3u' + url: 'http://localhost:8080/get.php?username=test&password=test&type=m3u' +``` + +Example input config for `xtream` +``` +sources: + - input: + type: xtream + headers: + User-Agent: "Mozilla/5.0 (Linux; Tizen 2.3) AppleWebKit/538.1 (KHTML, like Gecko)Version/2.3 TV Safari/538.1" + Accept: application/json + Accept-Encoding: gzip + url: 'http://localhost:8080' + username: test + password: test +``` + + ### 1.5.2 `targets` Has the following top level entries: * `enabled` _optional_ default is `true`, if you disable the processing is skipped diff --git a/config.yml b/config.yml index 48a9f7758..5f2a3f181 100644 --- a/config.yml +++ b/config.yml @@ -1,6 +1,18 @@ api: { host: 127.0.0.1, port: 8901, web_root: /home/m3u-filter/web } threads: 0 working_dir: /home/m3u-filter/data +templates: + - name: DE_CHAN + value: >- + Group ~ "(?i)^.DE.*Serien.*" OR + Group ~ "(?i)^.TR.*Filme.*" OR + Group ~ "(?i)^.TR.*Live.*" + - name: FR_CHAN + value: Group ~ "^.FR..*" + - name: ALL_CHAN + value: >- + !DE_CHAN! OR + !FR_CHAN! sources: - input: type: xtream @@ -8,19 +20,14 @@ sources: User-Agent: "Mozilla/5.0 (Linux; Tizen 2.3) AppleWebKit/538.1 (KHTML, like Gecko)Version/2.3 TV Safari/538.1" Accept: application/json Accept-Encoding: gzip - - m3u: - persist: 'playlist_1_{}.m3u' - url: 'http://localhost/playlist.m3u' - xtream: - url: 'http://localhost:3000' - username: test - password: test + url: 'http://localhost:8080' + username: test + password: test targets: - name: pl1 filename: playlist_1.m3u type: m3u - filter: 'Group ~ "^DE\s.*" OR Group ~ "TR\s.*" OR Group ~ "TF1.*"' + filter: '!ALL_CHAN!' options: { ignore_logo: true } rename: - { field: group, new_name: 1. DE$1, pattern: ^DE(.*) } diff --git a/src/m3u_processor.rs b/src/m3u_processor.rs index 84c35727f..feaedfd8d 100644 --- a/src/m3u_processor.rs +++ b/src/m3u_processor.rs @@ -52,7 +52,7 @@ fn skip_digit(it: &mut std::str::Chars) -> Option { } } -fn decode_header(content: &String) -> PlaylistItemHeader { +fn process_header(content: &String) -> PlaylistItemHeader { let mut plih = PlaylistItemHeader { id: String::from(""), name: String::from(""), @@ -103,7 +103,7 @@ fn decode_header(content: &String) -> PlaylistItemHeader { plih } -pub(crate) fn decode(lines: &Vec) -> Vec { +pub(crate) fn process(lines: &Vec) -> Vec { let mut groups: std::collections::HashMap> = std::collections::HashMap::new(); let mut sort_order: Vec = vec![]; let mut header: Option = None; @@ -122,7 +122,7 @@ pub(crate) fn decode(lines: &Vec) -> Vec { continue; } if header.is_some() { - let mut item = PlaylistItem { header: decode_header(&header.unwrap()), url: String::from(line) }; + let mut item = PlaylistItem { header: process_header(&header.unwrap()), url: String::from(line) }; if group.is_some() && item.header.group.is_empty() { item.header.group = String::from(group.unwrap()); } diff --git a/src/service.rs b/src/service.rs index 1d01b1ab4..b14ab8732 100644 --- a/src/service.rs +++ b/src/service.rs @@ -22,7 +22,7 @@ pub(crate) fn get_m3u_playlist(input: &ConfigInput, working_dir: &String, verbos let url = input.url.as_str(); let file_path = prepare_file_path(input, working_dir, "", verbose); let lines: Option> = utils::get_input_content(working_dir, url, file_path, verbose); - lines.map_or(None, |l| Some(m3u_processor::decode(&l))) + lines.map_or(None, |l| Some(m3u_processor::process(&l))) } @@ -41,9 +41,9 @@ pub(crate) fn get_xtream_playlist(input: &ConfigInput, working_dir: &String, ver let category_file_path = prepare_file_path(input, working_dir, format!("{}_", category).as_str(), verbose); let stream_file_path = prepare_file_path(input, working_dir, format!("{}_", stream).as_str(), verbose); - let category_content: Option = utils::get_input_json_content(input, working_dir, &category_url, category_file_path, verbose); - let stream_content: Option = utils::get_input_json_content(input, working_dir, &stream_url, stream_file_path, verbose); - let mut sub_playlist = xtream_processor::decode(category_content, stream_content, &stream_base_url); + let category_content: Option = utils::get_input_json_content(input, &category_url, category_file_path, verbose); + let stream_content: Option = utils::get_input_json_content(input, &stream_url, stream_file_path, verbose); + let mut sub_playlist = xtream_processor::process(category_content, stream_content, &stream_base_url); while let Some(group) = sub_playlist.pop() { playlist.push(group); } diff --git a/src/utils.rs b/src/utils.rs index 5b8c2b26c..9e01e33fa 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -188,7 +188,7 @@ pub(crate) fn get_file_path(wd: &String, path: Option) -> Option, verbose: bool) -> Result { +fn download_json_content(input: &ConfigInput, url: url::Url, persist_file: Option, verbose: bool) -> Result { let mut request = reqwest::blocking::Client::new().get(url); if input.headers.is_empty() { let mut headers = header::HeaderMap::new(); @@ -206,7 +206,7 @@ fn download_json_content(input: &ConfigInput, url: url::Url, _persist_file: Opti if response.status().is_success() { match response.json::() { Ok(content) => { - //persist_playlist(persist_file, , verbose); + persist_playlist(persist_file, &serde_json::to_string(&content).unwrap(), verbose); Ok(content) } Err(e) => Err(e.to_string()) @@ -219,7 +219,7 @@ fn download_json_content(input: &ConfigInput, url: url::Url, _persist_file: Opti } } -pub(crate) fn get_input_json_content(input: &ConfigInput, _working_dir: &String, url_str: &String, persist_file: Option, verbose: bool) -> Option { +pub(crate) fn get_input_json_content(input: &ConfigInput, url_str: &String, persist_file: Option, verbose: bool) -> Option { match url_str.parse::() { Ok(url) => match download_json_content(input, url, persist_file, verbose) { Ok(content) => Some(content), diff --git a/src/xtream_processor.rs b/src/xtream_processor.rs index 54da46f2e..e75768e0c 100644 --- a/src/xtream_processor.rs +++ b/src/xtream_processor.rs @@ -1,15 +1,27 @@ use std::cell::RefCell; use std::collections::HashMap; -use serde::{Deserialize}; +use serde::{Deserialize, Deserializer}; use crate::m3u::{PlaylistGroup, PlaylistItem, PlaylistItemHeader}; use crate::model::{default_as_empty_str}; +fn null_to_default<'de, D, T>(d: D) -> Result + where + D: Deserializer<'de>, + T: Default + Deserialize<'de>, +{ + let opt = Option::deserialize(d)?; + let val = opt.unwrap_or_else(T::default); + Ok(val) +} pub fn default_as_empty_list() -> Vec { vec![] } +pub fn default_as_zero() -> i32 { 0 } #[derive(Deserialize)] struct XtreamCategory { + #[serde(deserialize_with = "null_to_default")] pub category_id: String, + #[serde(deserialize_with = "null_to_default")] pub category_name: String, //pub parent_id: i32, #[serde(default = "default_as_empty_list")] @@ -24,22 +36,28 @@ impl XtreamCategory { #[derive(Deserialize)] struct XtreamStream { + #[serde(default = "default_as_zero", deserialize_with = "null_to_default")] pub num: i32, + #[serde(deserialize_with = "null_to_default")] pub name: String, //pub stream_type: String, + #[serde(deserialize_with = "null_to_default")] + pub category_id: String, + + #[serde(default = "default_as_zero", deserialize_with = "null_to_default")] pub stream_id: i32, + #[serde(default = "default_as_empty_str", deserialize_with = "null_to_default")] pub stream_icon: String, //pub epg_channel_id: String, // pub added: String, - pub category_id: String, //pub custom_sid: String, //pub tv_archive: i32, - #[serde(default = "default_as_empty_str")] + #[serde(default = "default_as_empty_str", deserialize_with = "null_to_default")] pub direct_source: String, //pub tv_archive_duration: i32, } -fn decode_category(category: Option) -> Vec { +fn process_category(category: Option) -> Vec { match category { Some(value) => { match serde_json::from_value::>(value) { @@ -55,7 +73,7 @@ fn decode_category(category: Option) -> Vec { } -fn decode_streams(streams: Option) -> Vec { +fn process_streams(streams: Option) -> Vec { match streams { Some(value) => { match serde_json::from_value::>(value) { @@ -70,10 +88,10 @@ fn decode_streams(streams: Option) -> Vec { } } -pub(crate) fn decode(category: Option, streams: Option, stream_base_url: &String) -> Vec { - let mut categories = decode_category(category); +pub(crate) fn process(category: Option, streams: Option, stream_base_url: &String) -> Vec { + let mut categories = process_category(category); if !categories.is_empty() { - let streams = decode_streams(streams); + let streams = process_streams(streams); if !streams.is_empty() { let mut group_map = HashMap::>::new(); while let Some(category) = categories.pop() {