mirror of
https://github.com/euzu/tuliprox.git
synced 2026-09-28 12:02:09 +02:00
refactored, renamed processor to m3u-processing, moved function from main to utils
This commit is contained in:
+17
-21
@@ -1,16 +1,12 @@
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum ItemField {
|
||||
Group,
|
||||
Name,
|
||||
Title,
|
||||
}
|
||||
|
||||
impl fmt::Display for ItemField {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
impl std::fmt::Display for ItemField {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match *self {
|
||||
ItemField::Group => write!(f, "Group"),
|
||||
ItemField::Name => write!(f, "Name"),
|
||||
@@ -19,22 +15,22 @@ impl fmt::Display for ItemField {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum FilterMode {
|
||||
Discard,
|
||||
Include,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ConfigFilter {
|
||||
pub field: ItemField,
|
||||
pub pattern: String,
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub re: Option<Regex>,
|
||||
pub re: Option<regex::Regex>,
|
||||
}
|
||||
|
||||
impl ConfigFilter {
|
||||
pub fn prepare(&mut self) -> () {
|
||||
pub(crate) fn prepare(&mut self) -> () {
|
||||
let re = regex::Regex::new(&self.pattern);
|
||||
if re.is_err() {
|
||||
println!("cant parse regex: {}", &self.pattern);
|
||||
@@ -44,29 +40,29 @@ impl ConfigFilter {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ConfigFilters {
|
||||
pub mode: FilterMode,
|
||||
pub rules: Vec<ConfigFilter>,
|
||||
}
|
||||
|
||||
impl ConfigFilters {
|
||||
pub fn is_include(&self) -> bool {
|
||||
pub(crate) fn is_include(&self) -> bool {
|
||||
matches!(self.mode, FilterMode::Include)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ConfigRename {
|
||||
pub field: ItemField,
|
||||
pub pattern: String,
|
||||
pub new_name: String,
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub re: Option<Regex>,
|
||||
pub re: Option<regex::Regex>,
|
||||
}
|
||||
|
||||
impl ConfigRename {
|
||||
pub fn prepare(&mut self) -> () {
|
||||
pub(crate) fn prepare(&mut self) -> () {
|
||||
let re = regex::Regex::new(&self.pattern);
|
||||
if re.is_err() {
|
||||
println!("cant parse regex: {}", &self.pattern);
|
||||
@@ -77,33 +73,33 @@ impl ConfigRename {
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ConfigTarget {
|
||||
pub filename: String,
|
||||
pub filter: ConfigFilters,
|
||||
pub rename: Vec<ConfigRename>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ConfigInput {
|
||||
pub url: String,
|
||||
pub persist: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Config {
|
||||
pub input: ConfigInput,
|
||||
pub targets: Vec<ConfigTarget>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn prepare(&mut self) {
|
||||
pub(crate) fn prepare(&mut self) {
|
||||
for t in &mut self.targets {
|
||||
for f in &mut t.filter.rules {
|
||||
f.prepare();
|
||||
}
|
||||
for r in &mut t.rename {
|
||||
r.prepare();
|
||||
r.prepare();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-20
@@ -1,11 +1,6 @@
|
||||
// https://de.wikipedia.org/wiki/M3U
|
||||
// https://siptv.eu/howto/playlist.html
|
||||
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::str::Chars;
|
||||
|
||||
pub struct PlaylistItemHeader {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
@@ -38,8 +33,8 @@ impl Clone for PlaylistItemHeader {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PlaylistItemHeader {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
impl std::fmt::Display for PlaylistItemHeader {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
fmt.write_str("id:")?;
|
||||
fmt.write_str(&self.id)?;
|
||||
fmt.write_str(", name:")?;
|
||||
@@ -80,8 +75,8 @@ impl Clone for PlaylistItem {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PlaylistItem {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
impl std::fmt::Display for PlaylistItem {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
fmt.write_str("header:")?;
|
||||
fmt.write_str(&self.header.to_string())?;
|
||||
fmt.write_str(", url:")?;
|
||||
@@ -91,7 +86,7 @@ impl fmt::Display for PlaylistItem {
|
||||
}
|
||||
|
||||
impl PlaylistItem {
|
||||
pub fn to_m3u(&self) -> String {
|
||||
pub(crate) fn to_m3u(&self) -> String {
|
||||
let mut line = format!("#EXTINF:-1 tvg-id=\"{}\" tvg-name=\"{}\" tvg-logo=\"{}\" group-title=\"{}\"", self.header.id, self.header.name, self.header.logo, self.header.group);
|
||||
if !self.header.logo_small.is_empty() {
|
||||
line = format!("{} tvg-logo-small=\"!{}\"", line, self.header.logo_small);
|
||||
@@ -118,8 +113,8 @@ pub struct PlaylistGroup {
|
||||
pub channels: Vec<PlaylistItem>,
|
||||
}
|
||||
|
||||
impl fmt::Display for PlaylistGroup {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
impl std::fmt::Display for PlaylistGroup {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
fmt.write_str("title:")?;
|
||||
fmt.write_str(&self.title.to_string())?;
|
||||
fmt.write_str(", channels:")?;
|
||||
@@ -128,7 +123,7 @@ impl fmt::Display for PlaylistGroup {
|
||||
}
|
||||
}
|
||||
|
||||
fn token_value<'a>(it: &'a mut Chars) -> String {
|
||||
fn token_value<'a>(it: &'a mut std::str::Chars) -> String {
|
||||
let oc = it.next();
|
||||
if oc.is_some() && oc.unwrap() == '"' {
|
||||
return get_value(it);
|
||||
@@ -136,7 +131,7 @@ fn token_value<'a>(it: &'a mut Chars) -> String {
|
||||
return String::from("");
|
||||
}
|
||||
|
||||
fn get_value<'a>(it: &'a mut Chars) -> String {
|
||||
fn get_value<'a>(it: &'a mut std::str::Chars) -> String {
|
||||
let mut result: Vec<char> = vec![];
|
||||
let mut oc = it.next();
|
||||
while oc.is_some() && oc.unwrap() != '"' {
|
||||
@@ -146,7 +141,7 @@ fn get_value<'a>(it: &'a mut Chars) -> String {
|
||||
return String::from(result.iter().collect::<String>());
|
||||
}
|
||||
|
||||
fn token_till(it: &mut Chars, stop_char: char) -> Option<String> {
|
||||
fn token_till(it: &mut std::str::Chars, stop_char: char) -> Option<String> {
|
||||
let mut result: Vec<char> = vec![];
|
||||
loop {
|
||||
let c = it.next();
|
||||
@@ -161,7 +156,7 @@ fn token_till(it: &mut Chars, stop_char: char) -> Option<String> {
|
||||
if result.len() > 0 { Some(String::from(result.iter().collect::<String>())) } else { None }
|
||||
}
|
||||
|
||||
fn skip_digit(it: &mut Chars) -> Option<char> {
|
||||
fn skip_digit(it: &mut std::str::Chars) -> Option<char> {
|
||||
let mut c: Option<char>;
|
||||
loop {
|
||||
c = it.next();
|
||||
@@ -222,8 +217,8 @@ fn decode_header(content: &String) -> PlaylistItemHeader {
|
||||
plih
|
||||
}
|
||||
|
||||
pub fn decode(lines: &Vec<String>) -> Vec<PlaylistGroup> {
|
||||
let mut groups: HashMap<String, Vec<PlaylistItem>> = HashMap::new();
|
||||
pub(crate) fn decode(lines: &Vec<String>) -> Vec<PlaylistGroup> {
|
||||
let mut groups: std::collections::HashMap<String, Vec<PlaylistItem>> = std::collections::HashMap::new();
|
||||
let mut sort_order: Vec<String> = vec![];
|
||||
let mut header: Option<String> = None;
|
||||
let mut group: Option<String> = None;
|
||||
@@ -248,11 +243,11 @@ pub fn decode(lines: &Vec<String>) -> Vec<PlaylistGroup> {
|
||||
let key = String::from(&item.header.group);
|
||||
let key2 = String::from(&item.header.group);
|
||||
match groups.entry(key) {
|
||||
Entry::Vacant(e) => {
|
||||
std::collections::hash_map::Entry::Vacant(e) => {
|
||||
e.insert(vec![item]);
|
||||
sort_order.push(key2);
|
||||
}
|
||||
Entry::Occupied(mut e) => { e.get_mut().push(item); }
|
||||
std::collections::hash_map::Entry::Occupied(mut e) => { e.get_mut().push(item); }
|
||||
}
|
||||
}
|
||||
header = None;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
use crate::{config, m3u};
|
||||
use crate::m3u::PlaylistItem;
|
||||
|
||||
pub fn write_m3u(playlist: &Vec<m3u::PlaylistGroup>, cfg: &config::Config) {
|
||||
pub(crate) fn write_m3u(playlist: &Vec<m3u::PlaylistGroup>, cfg: &config::Config) {
|
||||
for t in &cfg.targets {
|
||||
let mut file = match File::create(&t.filename) {
|
||||
let mut file = match std::fs::File::create(&t.filename) {
|
||||
Ok(file) => file,
|
||||
Err(_) => {
|
||||
println!("cant open file: {}", t.filename);
|
||||
+4
-11
@@ -1,7 +1,6 @@
|
||||
use std::path::{PathBuf};
|
||||
mod m3u;
|
||||
mod config;
|
||||
mod processor;
|
||||
mod m3u_processing;
|
||||
mod utils;
|
||||
|
||||
fn main() {
|
||||
@@ -40,17 +39,11 @@ fn main() {
|
||||
let input_arg = matches.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 persist_file = match input_arg {
|
||||
Some(_) => matches.value_of("persist").map_or(None, |p|prepare_persist_path(p)),
|
||||
None => if cfg.input.persist.is_empty() {None} else { prepare_persist_path(cfg.input.persist.as_str())},
|
||||
Some(_) => matches.value_of("persist").map_or(None, |p| utils::prepare_persist_path(p)),
|
||||
None => if cfg.input.persist.is_empty() { None } else { utils::prepare_persist_path(cfg.input.persist.as_str()) },
|
||||
};
|
||||
let lines: Vec<String> = utils::get_input_content(url_str, persist_file);
|
||||
let result = m3u::decode(&lines);
|
||||
processor::write_m3u(&result, &cfg);
|
||||
}
|
||||
|
||||
fn prepare_persist_path(file_name: &str) -> Option<PathBuf> {
|
||||
let now = chrono::Local::now();
|
||||
let filename = file_name.replace("{}", now.format("%Y%m%d_%H%M%S").to_string().as_str());
|
||||
Some(PathBuf::from(filename))
|
||||
m3u_processing::write_m3u(&result, &cfg);
|
||||
}
|
||||
|
||||
|
||||
+30
-25
@@ -1,8 +1,6 @@
|
||||
use url::{Url};
|
||||
use std::io::{BufRead, Write};
|
||||
use std::path::{PathBuf};
|
||||
|
||||
pub fn get_default_config_path() -> String {
|
||||
pub(crate) fn get_default_config_path() -> String {
|
||||
let default_path = std::path::Path::new("./");
|
||||
let current_exe = std::env::current_exe();
|
||||
let path: &std::path::Path = match current_exe {
|
||||
@@ -17,7 +15,7 @@ pub fn get_default_config_path() -> String {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn open_file(file_name: &str) -> std::fs::File {
|
||||
pub(crate) fn open_file(file_name: &str) -> std::fs::File {
|
||||
let file = match std::fs::File::open(file_name) {
|
||||
Ok(file) => file,
|
||||
Err(_) => {
|
||||
@@ -28,8 +26,8 @@ pub fn open_file(file_name: &str) -> std::fs::File {
|
||||
file
|
||||
}
|
||||
|
||||
pub fn get_input_content(url_str: &str, persist_file: Option<PathBuf>) -> Vec<String> {
|
||||
match url_str.parse::<Url>() {
|
||||
pub(crate) fn get_input_content(url_str: &str, persist_file: Option<std::path::PathBuf>) -> Vec<String> {
|
||||
match url_str.parse::<url::Url>() {
|
||||
Ok(url) => match download_content(url, persist_file) {
|
||||
Ok(content) => content,
|
||||
Err(e) => {
|
||||
@@ -43,11 +41,11 @@ pub fn get_input_content(url_str: &str, persist_file: Option<PathBuf>) -> Vec<St
|
||||
if persist_file.is_some() {
|
||||
let to_file = &persist_file.unwrap();
|
||||
match std::fs::copy(file, to_file) {
|
||||
Ok(_) => {},
|
||||
Ok(_) => {}
|
||||
Err(e) => println!("cant persist to: {} => {}", to_file.to_str().unwrap_or("?"), e),
|
||||
}
|
||||
} ;
|
||||
std::io::BufReader::new(open_file(url_str)).lines(). map(|l| l.unwrap()).collect()
|
||||
};
|
||||
std::io::BufReader::new(open_file(url_str)).lines().map(|l| l.unwrap()).collect()
|
||||
} else {
|
||||
println!("cant read input url: {} => {}", url_str, e);
|
||||
std::process::exit(1);
|
||||
@@ -56,38 +54,45 @@ pub fn get_input_content(url_str: &str, persist_file: Option<PathBuf>) -> Vec<St
|
||||
}
|
||||
}
|
||||
|
||||
fn download_content(url: Url, persist_file: Option<PathBuf>) -> Result<Vec<String>, String> {
|
||||
fn download_content(url: url::Url, persist_file: Option<std::path::PathBuf>) -> Result<Vec<String>, String> {
|
||||
match reqwest::blocking::get(url) {
|
||||
Ok(response) => {
|
||||
if response.status().is_success() {
|
||||
match response.text_with_charset("utf8") {
|
||||
Ok(text) => {
|
||||
persists_playlist(persist_file, &text);
|
||||
let result= text.lines().map(|l| String::from(l)).collect();
|
||||
let result = text.lines().map(|l| String::from(l)).collect();
|
||||
Ok(result)
|
||||
},
|
||||
}
|
||||
Err(e) => Err(e.to_string())
|
||||
}
|
||||
} else {
|
||||
Err(format!("Request failed: {}", response.status()))
|
||||
}
|
||||
},
|
||||
}
|
||||
Err(e) => Err(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn persists_playlist(persist_file: Option<PathBuf>, text: &String) {
|
||||
match persist_file {
|
||||
Some(path_buf) => {
|
||||
let filename = &path_buf.to_str().unwrap_or("?");
|
||||
match std::fs::File::create(&path_buf) {
|
||||
Ok(mut file) => match file.write_all(text.as_bytes()) {
|
||||
Ok(_) => println!("persisted: {}", filename),
|
||||
Err(e) => println!("failed to persist file {}, {}", filename, e)
|
||||
},
|
||||
fn persists_playlist(persist_file: Option<std::path::PathBuf>, text: &String) {
|
||||
match persist_file {
|
||||
Some(path_buf) => {
|
||||
let filename = &path_buf.to_str().unwrap_or("?");
|
||||
match std::fs::File::create(&path_buf) {
|
||||
Ok(mut file) => match file.write_all(text.as_bytes()) {
|
||||
Ok(_) => println!("persisted: {}", filename),
|
||||
Err(e) => println!("failed to persist file {}, {}", filename, e)
|
||||
}
|
||||
},
|
||||
None => {}
|
||||
},
|
||||
Err(e) => println!("failed to persist file {}, {}", filename, e)
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn prepare_persist_path(file_name: &str) -> Option<std::path::PathBuf> {
|
||||
let now = chrono::Local::now();
|
||||
let filename = file_name.replace("{}", now.format("%Y%m%d_%H%M%S").to_string().as_str());
|
||||
Some(std::path::PathBuf::from(filename))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user