From cf48e471409c5d5376a1867b0c4554b35dae6fbe Mon Sep 17 00:00:00 2001 From: euzu Date: Thu, 24 Mar 2022 14:08:25 +0100 Subject: [PATCH 1/2] Changed configuration for multiple sources --- CHANGELOG.md | 2 + Cargo.lock | 2 +- Cargo.toml | 2 +- config.yml | 38 +++++-------- src/api.rs | 4 +- src/config.rs | 63 ++++++++++++++-------- src/m3u_processing.rs | 123 ++++++++++++++++++++++++------------------ src/main.rs | 29 +--------- 8 files changed, 133 insertions(+), 130 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f00621601..a79b40929 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ # Changelog +## v0.8.0(2022-03-24) +* Changed configuration. It is now possible to handle multiple sources. Each input has its own targets. ## v0.7.0(2022-01-20) * Updated frontend libraries diff --git a/Cargo.lock b/Cargo.lock index 81319da80..b2271a108 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1106,7 +1106,7 @@ dependencies = [ [[package]] name = "m3u-filter" -version = "0.5.0" +version = "0.8.0" dependencies = [ "actix-cors", "actix-files", diff --git a/Cargo.toml b/Cargo.toml index ec745a32d..53137fd31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "m3u-filter" -version = "0.5.0" +version = "0.8.0" edition = "2018" [profile.release] diff --git a/config.yml b/config.yml index 24328859b..5b26df691 100644 --- a/config.yml +++ b/config.yml @@ -1,25 +1,15 @@ +api: {host: 127.0.0.1, port: 8901, web_root: /home/m3u-filter/web} working_dir: /home/m3u-filter/data -api: - host: 127.0.0.1 - port: 8901 - web_root: /home/m3u-filter/web -input: - url: http://myserver.net/playlist.m3u - persist: playlist_{}.m3u -targets: - - filename: playlist_1.m3u - options: - ignore_logo: true - sort: - order: Asc - filter: - mode: Include - rules: - - field: Group - pattern: ^DE\s.* - - field: Group - pattern: ^AU\s.* - rename: - - field: Group - pattern: ^DE(.*) - new_name: 1. DE$1 +sources: + - input: {persist: 'playlist_1_{}.m3u', url: 'http://myserver.net/playlist.m3u'} + targets: + - filename: playlist_1.m3u + filter: + mode: Include + rules: + - {field: Group, pattern: ^DE\s.*} + - {field: Group, pattern: ^AU\s.*} + options: {ignore_logo: true} + rename: + - {field: Group, new_name: 1. DE$1, pattern: ^DE(.*)} + sort: {order: Asc} diff --git a/src/api.rs b/src/api.rs index adc2f349d..f841c14f5 100644 --- a/src/api.rs +++ b/src/api.rs @@ -21,14 +21,14 @@ pub(crate) async fn playlist( req: web::Json, _app_state: web::Data, ) -> HttpResponse { - let result = get_playlist(&_app_state.config.working_dir,&req.url.as_str(), None); + let result = get_playlist(&_app_state.config.working_dir, &req.url.as_str(), None); HttpResponse::Ok().json(result) } pub(crate) async fn config( _app_state: web::Data, ) -> HttpResponse { - let sources = vec![_app_state.config.input.url.clone()]; + let sources: Vec = _app_state.config.sources.iter().map(|t| t.input.url.clone()).collect(); let result = ServerConfig { sources }; diff --git a/src/config.rs b/src/config.rs index d4ca8ec6d..41c314f90 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ -use crate::utils::get_working_path; use path_absolutize::*; + use crate::utils; +use crate::utils::get_working_path; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub enum ItemField { @@ -44,7 +45,6 @@ impl ConfigFilter { } } - #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct ConfigOptions { pub ignore_logo: bool, @@ -102,6 +102,14 @@ pub struct ConfigTarget { pub rename: Vec, } + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct ConfigSources { + pub input: ConfigInput, + pub targets: Vec, + +} + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct ConfigInput { pub url: String, @@ -127,8 +135,7 @@ impl ConfigApi { #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct Config { pub api: ConfigApi, - pub input: ConfigInput, - pub targets: Vec, + pub sources: Vec, pub working_dir: String, } @@ -137,12 +144,14 @@ impl Config { self.working_dir = get_working_path(&self.working_dir); self.api.prepare(); self.prepare_api_web_root(); - for t in &mut self.targets { - for f in &mut t.filter.rules { - f.prepare(); - } - for r in &mut t.rename { - r.prepare(); + for source in &mut self.sources { + for target in &mut source.targets { + for f in &mut target.filter.rules { + f.prepare(); + } + for r in &mut target.rename { + r.prepare(); + } } } } @@ -178,10 +187,9 @@ impl Config { impl Clone for Config { fn clone(&self) -> Self { - Config{ + Config { api: self.api.clone(), - input: self.input.clone(), - targets: self.targets.clone(), + sources: self.sources.clone(), working_dir: self.working_dir.clone(), } } @@ -189,19 +197,28 @@ impl Clone for Config { impl Clone for ConfigTarget { fn clone(&self) -> Self { - ConfigTarget{ + ConfigTarget { filename: self.filename.clone(), - options: self.options.as_ref().map(|o| o.clone()), - sort: self.sort.as_ref().map(|s| s.clone()), + options: self.options.as_ref().map(|o| o.clone()), + sort: self.sort.as_ref().map(|s| s.clone()), filter: self.filter.clone(), - rename: self.rename.clone() + rename: self.rename.clone(), + } + } +} + +impl Clone for ConfigSources { + fn clone(&self) -> Self { + ConfigSources { + input: self.input.clone(), + targets: self.targets.clone(), } } } impl Clone for ConfigOptions { fn clone(&self) -> Self { - ConfigOptions{ + ConfigOptions { ignore_logo: self.ignore_logo, } } @@ -209,7 +226,7 @@ impl Clone for ConfigOptions { impl Clone for ConfigSort { fn clone(&self) -> Self { - ConfigSort{ + ConfigSort { order: self.order.clone(), } } @@ -217,20 +234,20 @@ impl Clone for ConfigSort { impl Clone for ConfigFilters { fn clone(&self) -> Self { - ConfigFilters{ + ConfigFilters { mode: self.mode.clone(), - rules: self.rules.clone() + rules: self.rules.clone(), } } } impl Clone for ConfigRename { fn clone(&self) -> Self { - ConfigRename{ + ConfigRename { field: self.field.clone(), pattern: self.pattern.clone(), new_name: self.new_name.clone(), - re: None + re: None, } } } diff --git a/src/m3u_processing.rs b/src/m3u_processing.rs index 4e722cee7..f4b8364fb 100644 --- a/src/m3u_processing.rs +++ b/src/m3u_processing.rs @@ -1,6 +1,6 @@ -use std::io::{ Write}; +use std::io::Write; -use crate::{config, m3u, utils}; +use crate::{config, Config, get_playlist, m3u, utils}; use crate::config::ItemField::Group; use crate::config::SortOrder::{Asc, Desc}; use crate::m3u::PlaylistItem; @@ -12,66 +12,64 @@ fn check_write(res: std::io::Result) -> Result<(), std::io::Error> { } } -pub(crate) fn write_m3u(playlist: &Vec, cfg: &config::Config) -> Result<(), std::io::Error> { - for t in &cfg.targets { - let mut new_playlist: Vec = Vec::new(); - for g in playlist { - let mut grp = g.clone(); - if t.rename.len() > 0 { - for r in &t.rename { - match r.field { - Group => { - let cap = r.re.as_ref().unwrap().replace_all(&grp.title, &r.new_name); - grp.title = cap.into_owned(); - }, - _ => {} +pub(crate) fn write_m3u(playlist: &Vec, target: &config::ConfigTarget, cfg: &config::Config) -> Result<(), std::io::Error> { + let mut new_playlist: Vec = Vec::new(); + for g in playlist { + let mut grp = g.clone(); + if target.rename.len() > 0 { + for r in &target.rename { + match r.field { + Group => { + let cap = r.re.as_ref().unwrap().replace_all(&grp.title, &r.new_name); + grp.title = cap.into_owned(); } + _ => {} } } - new_playlist.push(grp); } + new_playlist.push(grp); + } - if let Some(sort) = &t.sort { - new_playlist.sort_by(|a, b| { - let ordering = a.title.partial_cmp(&b.title).unwrap(); - match sort.order { - Asc => ordering, - Desc => ordering.reverse() + if let Some(sort) = &target.sort { + new_playlist.sort_by(|a, b| { + let ordering = a.title.partial_cmp(&b.title).unwrap(); + match sort.order { + Asc => ordering, + Desc => ordering.reverse() + } + }); + } + match utils::get_file_path(&cfg.working_dir, Some(std::path::PathBuf::from(&target.filename))) { + Some(path) => { + let mut file = match std::fs::File::create(&path) { + Ok(file) => file, + Err(e) => { + println!("cant open file: {:?}", &path); + return Err(e); } - }); - } - match utils::get_file_path(&cfg.working_dir, Some(std::path::PathBuf::from(&t.filename))) { - Some(path) => { - let mut file = match std::fs::File::create(&path) { - Ok(file) => file, - Err(e) => { - println!("cant open file: {:?}", &path); - return Err(e); - } - }; + }; - match check_write(file.write(b"#EXTM3U\n")) { - Ok(_) => (), - Err(e) => return Err(e), - } - for pg in &new_playlist { - for pli in &pg.channels { - if is_valid(&pli, &t.filter) { - let content = exec_rename(&pli, &t.rename).map_or_else(|| pli.to_m3u(&t.options), |p| p.to_m3u(&t.options)); - match check_write(file.write(content.as_bytes())) { - Ok(_) => (), - Err(e) => return Err(e), - } - match check_write(file.write(b"\n")) { - Ok(_) => (), - Err(e) => return Err(e), - } + match check_write(file.write(b"#EXTM3U\n")) { + Ok(_) => (), + Err(e) => return Err(e), + } + for pg in &new_playlist { + for pli in &pg.channels { + if is_valid(&pli, &target.filter) { + let content = exec_rename(&pli, &target.rename).map_or_else(|| pli.to_m3u(&target.options), |p| p.to_m3u(&target.options)); + match check_write(file.write(content.as_bytes())) { + Ok(_) => (), + Err(e) => return Err(e), + } + match check_write(file.write(b"\n")) { + Ok(_) => (), + Err(e) => return Err(e), } } } - }, - None => (), + } } + None => (), } Ok(()) } @@ -123,3 +121,26 @@ fn exec_rename(pli: &m3u::PlaylistItem, rename: &Vec) -> O } None } + +pub fn process_targets(cfg: &Config, verbose: bool) { + for source in cfg.sources.iter() { + let url_str = source.input.url.as_str(); + let persist_file: Option = + if source.input.persist.is_empty() { None } else { utils::prepare_persist_path(source.input.persist.as_str()) }; + let file_path = utils::get_file_path(&cfg.working_dir, persist_file); + if verbose { println!("persist file: {:?}", &file_path); } + + let result = get_playlist(&cfg.working_dir, url_str, file_path); + match &result { + Some(playlist) => { + for target in source.targets.iter() { + match write_m3u(playlist, target, &cfg) { + Ok(_) => (), + Err(e) => println!("Failed to write file: {}", e) + } + } + } + None => () + } + } +} diff --git a/src/main.rs b/src/main.rs index 2fddd4e1f..6bc62ba2f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,24 +29,7 @@ fn main() { Err(e) => panic!("cant start server: {}", e) }; } else { - let input_arg = args.value_of("input"); - let url_str = input_arg.unwrap_or(if cfg.input.url.is_empty() { "playlist.m3u" } else { cfg.input.url.as_str() }); - let input_perists = || -> Option { - if cfg.input.persist.is_empty() { None } else { utils::prepare_persist_path(cfg.input.persist.as_str()) } - }; - let persist_file = match input_arg { - Some(_) => if args.is_present("persist") { utils::prepare_persist_path("playlist_{}.m3u") } else { input_perists() }, - None => input_perists(), - }; - let file_path = utils::get_file_path(&cfg.working_dir, persist_file); - if verbose { println!("persist file: {:?}", &file_path); } - let result = get_playlist(&cfg.working_dir, url_str, file_path); - if result.is_some() { - match m3u_processing::write_m3u(&result.unwrap(), &cfg) { - Ok(_) => (), - Err(e) => println!("Failed to write file: {}", e) - } - } + m3u_processing::process_targets(&cfg, verbose) } } @@ -69,16 +52,6 @@ fn get_arguments<'a>() -> ArgMatches<'a> { .long("config") .takes_value(true) .help("The config file")) - .arg(clap::Arg::with_name("input") - .short("i") - .long("input") - .takes_value(true) - .help("Input filename, overrides config input")) - .arg(clap::Arg::with_name("persist") - .short("p") - .long("persist") - .takes_value(false) - .help("Persists the input file on disk, if the input parameter is missing it will be ignored!")) .arg(clap::Arg::with_name("server") .short("s") .long("server") From 150aa6dde3568d3c03cafe8d9a4de0d091498994 Mon Sep 17 00:00:00 2001 From: euzu <33094714+euzu@users.noreply.github.com> Date: Fri, 21 Jan 2022 11:34:33 +0100 Subject: [PATCH 2/2] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index fe06cf5f7..d729e6cd9 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,9 @@ If you have a playlist which contains unwanted entries, you can create filter wh based on the header information of the playlist entries. Currently filter and rename operations support group, name and title fields. +You can run m3u-filter as command line application to update your playlists (manually or as cron job), or you can +run it in server mode and open the web-ui to see the contents of the playlist, filter/search content and save the filtered groups as a new playlist. + You can define multiple targets for filtering if you want to create multiple playlists from a big playlist. The config.yml file contains the filter and rename definitions. It should be located next to the exe file or in the current working directory.