mirror of
https://github.com/euzu/tuliprox.git
synced 2026-09-27 11:32:11 +02:00
Issue #9 addedd suffix and prefix at input level.
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
- "time_shift"
|
||||
- "rec"
|
||||
- "source"
|
||||
* Added static suffix and prefix at inpupt source level
|
||||
|
||||
# v0.9.7(2023-02-15)
|
||||
* Breaking changes, mappings.yml refactored
|
||||
|
||||
Generated
+1
-1
@@ -1066,7 +1066,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "m3u-filter"
|
||||
version = "0.9.7"
|
||||
version = "0.9.8"
|
||||
dependencies = [
|
||||
"actix-cors",
|
||||
"actix-files",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "m3u-filter"
|
||||
version = "0.9.7"
|
||||
version = "0.9.8"
|
||||
edition = "2018"
|
||||
|
||||
[profile.release]
|
||||
|
||||
@@ -66,12 +66,19 @@ This will replace all occurrences of `!delimiter!` and `!quality!` in the regexp
|
||||
* `targets`
|
||||
|
||||
### 1.5.1 `input`
|
||||
Has two entries, `persist` and `url`.
|
||||
Has for entries, `persist`, `url`, `prefix`, `suffix`.
|
||||
|
||||
`input: { persist: ./playlist_{}.m3u, url: http://myserver.net/playlist.m3u }`
|
||||
`input: { persist: ./playlist_{}.m3u, url: http://myserver.net/playlist.m3u, prefix: {field: title, value: '#!# ' }, suffix: {field: title, value: ' +-+' } }`
|
||||
|
||||
- `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` is the download url or a local filename of the input-source.
|
||||
- `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.
|
||||
They have 2 fields:
|
||||
- `field` can be `name` , `group`, `title`
|
||||
- `value` a static text
|
||||
|
||||
### 1.5.2 `targets`
|
||||
Has the following top level entries:
|
||||
|
||||
+5
-3
@@ -12,12 +12,14 @@ fi
|
||||
#fi
|
||||
|
||||
cd ./frontend || (echo "cant find frontend directory" && exit)
|
||||
NEW_VERSION=$(yarn version --patch | grep "New version" | grep -Po "(\d+\.)+\d+")
|
||||
NEW_VERSION=$(yarn version --no-git-tag-version --patch | grep "New version" | grep -Po "(\d+\.)+\d+")
|
||||
cd ..
|
||||
cargo-set-version set-version ./Cargo.toml "$NEW_VERSION"
|
||||
|
||||
cargo set-version "$NEW_VERSION"
|
||||
|
||||
VERSION=v$NEW_VERSION
|
||||
|
||||
echo "building version $NEW_VERSION"
|
||||
|
||||
LIN_DIR=m3u-filter_${VERSION}_linux_x86_64
|
||||
WIN_DIR=m3u-filter_${VERSION}_windows_x86_64
|
||||
DARWIN_DIR=m3u-filter_${VERSION}_darwin_x86_64
|
||||
|
||||
+9
-4
@@ -3,13 +3,10 @@ use path_absolutize::*;
|
||||
use crate::filter::{Filter, get_filter, MockValueProcessor, PatternTemplate, prepare_templates, ValueProvider};
|
||||
use crate::mapping::Mappings;
|
||||
use crate::mapping::Mapping;
|
||||
use crate::model::{ItemField, ProcessingOrder, SortOrder, TargetType};
|
||||
use crate::model::{ItemField, ProcessingOrder, SortOrder, TargetType, default_as_zero, default_as_empty_str, default_as_false};
|
||||
use crate::utils;
|
||||
use crate::utils::get_working_path;
|
||||
|
||||
fn default_as_zero() -> u8 { 0 }
|
||||
fn default_as_false() -> bool { false }
|
||||
fn default_as_empty_str() -> String { String::from("") }
|
||||
fn default_as_frm() -> ProcessingOrder { ProcessingOrder::FRM }
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
@@ -95,11 +92,19 @@ impl ConfigSources {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct InputAffix {
|
||||
pub field: String,
|
||||
pub value: String
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ConfigInput {
|
||||
pub url: String,
|
||||
#[serde(default = "default_as_empty_str")]
|
||||
pub persist: String,
|
||||
pub prefix: Option<InputAffix>,
|
||||
pub suffix: Option<InputAffix>,
|
||||
}
|
||||
|
||||
impl ConfigInput {
|
||||
|
||||
+60
-9
@@ -6,12 +6,13 @@ use std::thread;
|
||||
use config::ConfigTarget;
|
||||
use chrono::Datelike;
|
||||
use unidecode::unidecode;
|
||||
use crate::{config, Config, get_playlist, utils};
|
||||
use crate::{config, Config, get_playlist, utils, valid_property};
|
||||
use crate::config::{ConfigInput, InputAffix};
|
||||
use crate::model::SortOrder::{Asc, Desc};
|
||||
use crate::filter::{ValueProvider};
|
||||
use crate::m3u::{PlaylistGroup, PlaylistItem};
|
||||
use crate::m3u::{FieldAccessor, PlaylistGroup, PlaylistItem, PlaylistItemHeader};
|
||||
use crate::mapping::{Mapping, MappingValueProcessor};
|
||||
use crate::model::{ItemField, ProcessingOrder, TargetType};
|
||||
use crate::model::{ItemField, AFFIX_FIELDS, ProcessingOrder, TargetType};
|
||||
|
||||
macro_rules! open_file {
|
||||
($path:expr) => {{
|
||||
@@ -61,7 +62,54 @@ fn filter_playlist(playlist: &Vec<PlaylistGroup>, target: &ConfigTarget, verbose
|
||||
Some(new_playlist)
|
||||
}
|
||||
|
||||
fn apply_affixes(playlist: &mut Vec<PlaylistGroup>, input: &ConfigInput, verbose: bool) {
|
||||
if input.suffix.is_some() || input.prefix.is_some() {
|
||||
let validate_affix = |a: &Option<InputAffix>| match a {
|
||||
Some(affix) => {
|
||||
valid_property!(&affix.field.as_str(), AFFIX_FIELDS) && affix.value.len() > 0
|
||||
}
|
||||
_ => false
|
||||
};
|
||||
|
||||
let apply_prefix = validate_affix(&input.prefix);
|
||||
let apply_suffix = validate_affix(&input.suffix);
|
||||
|
||||
if apply_prefix || apply_suffix {
|
||||
let get_affix_applied_value = |header: &mut PlaylistItemHeader, affix: &InputAffix, prefix: bool| {
|
||||
if let Some(field_value) = header.get_field(&affix.field.as_str()) {
|
||||
return if prefix {
|
||||
format!("{}{}", &affix.value, field_value.as_str())
|
||||
} else {
|
||||
format!("{}{}", field_value.as_str(), &affix.value)
|
||||
}
|
||||
}
|
||||
return String::from(&affix.value)
|
||||
};
|
||||
|
||||
for group in playlist {
|
||||
for channel in &mut group.channels {
|
||||
if apply_suffix {
|
||||
if let Some(suffix) = &input.suffix {
|
||||
let value = get_affix_applied_value(&mut channel.header, suffix, false);
|
||||
if verbose { println!("Applying input suffix: {}={}", &suffix.field, &value)}
|
||||
channel.header.set_field(&suffix.field, &value.as_str());
|
||||
}
|
||||
}
|
||||
if apply_prefix {
|
||||
if let Some(prefix) = &input.prefix {
|
||||
let value = get_affix_applied_value(&mut channel.header, prefix, true);
|
||||
if verbose { println!("Applying input prefix: {}={}", &prefix.field, &value)}
|
||||
channel.header.set_field(&prefix.field, &value.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn write_m3u(playlist: &Vec<PlaylistGroup>,
|
||||
input: &ConfigInput,
|
||||
target: &ConfigTarget, cfg: &Config,
|
||||
verbose: bool) -> Result<(), std::io::Error> {
|
||||
let pipe: Vec<fn(playlist: &Vec<PlaylistGroup>, target: &ConfigTarget, verbose: bool) -> Option<Vec<PlaylistGroup>>> =
|
||||
@@ -84,6 +132,8 @@ pub(crate) fn write_m3u(playlist: &Vec<PlaylistGroup>,
|
||||
}
|
||||
}
|
||||
|
||||
apply_affixes(&mut new_playlist, input, verbose);
|
||||
|
||||
sort_playlist(target, &mut new_playlist);
|
||||
match &target.output {
|
||||
Some(output_type) => {
|
||||
@@ -319,7 +369,7 @@ fn map_channel(channel: &mut PlaylistItem, mapping: &Mapping, verbose: bool) ->
|
||||
}
|
||||
|
||||
fn map_playlist(playlist: &Vec<PlaylistGroup>, target: &ConfigTarget, verbose: bool) -> Option<Vec<PlaylistGroup>> {
|
||||
if verbose { println!("Mapping")}
|
||||
if verbose { println!("Mapping") }
|
||||
if target._mapping.is_some() {
|
||||
let mut new_playlist: Vec<PlaylistGroup> = Vec::new();
|
||||
for playlist_group in playlist {
|
||||
@@ -360,7 +410,8 @@ fn set_field_value(pli: &mut PlaylistItem, field: &ItemField, value: String) ->
|
||||
|
||||
fn process_source(cfg: Arc<Config>, source_idx: usize, verbose: bool) {
|
||||
let source = cfg.sources.get(source_idx).unwrap();
|
||||
let url_str = source.input.url.as_str();
|
||||
let input = &source.input;
|
||||
let url_str = input.url.as_str();
|
||||
let persist_file: Option<std::path::PathBuf> =
|
||||
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);
|
||||
@@ -378,7 +429,7 @@ fn process_source(cfg: Arc<Config>, source_idx: usize, verbose: bool) {
|
||||
} else {
|
||||
if verbose { println!("Input file has {} groups", playlist.len()) }
|
||||
for target in source.targets.iter() {
|
||||
match write_m3u(playlist, target, &cfg, verbose) {
|
||||
match write_m3u(playlist, input, target, &cfg, verbose) {
|
||||
Ok(_) => (),
|
||||
Err(e) => println!("Failed to write file: {}", e)
|
||||
}
|
||||
@@ -390,10 +441,10 @@ fn process_source(cfg: Arc<Config>, source_idx: usize, verbose: bool) {
|
||||
}
|
||||
|
||||
pub fn process_targets(cfg: Arc<Config>, verbose: bool) {
|
||||
let mut handle_list = vec![];
|
||||
let mut handle_list = vec![];
|
||||
let thread_num = cfg.threads;
|
||||
let process_parallel = thread_num > 1 && cfg.sources.len() > 1;
|
||||
if verbose && process_parallel { println!("Using {} threads", thread_num)}
|
||||
if verbose && process_parallel { println!("Using {} threads", thread_num) }
|
||||
|
||||
for (index, _) in cfg.sources.iter().enumerate() {
|
||||
let config = cfg.clone();
|
||||
@@ -410,6 +461,6 @@ pub fn process_targets(cfg: Arc<Config>, verbose: bool) {
|
||||
}
|
||||
}
|
||||
for handle in handle_list {
|
||||
let _= handle.join();
|
||||
let _ = handle.join();
|
||||
}
|
||||
}
|
||||
|
||||
+5
-15
@@ -2,13 +2,9 @@ use std::collections::HashMap;
|
||||
use regex::Regex;
|
||||
use crate::filter::{Filter, get_filter, PatternTemplate, prepare_templates, RegexWithCaptures, ValueProcessor};
|
||||
use crate::m3u::{FieldAccessor, PlaylistItem};
|
||||
use crate::model::{ItemField, MAPPER_ATTRIBUTE_FIELDS, MAPPER_PREFIX_SUFFIX_FIELDS};
|
||||
|
||||
fn default_as_false() -> bool { false }
|
||||
|
||||
fn default_as_empty_str() -> String { String::from("") }
|
||||
|
||||
fn default_as_empty_map() -> HashMap<String, String> { HashMap::new() }
|
||||
use crate::model::{ItemField, MAPPER_ATTRIBUTE_FIELDS, AFFIX_FIELDS,
|
||||
default_as_empty_str, default_as_false, default_as_empty_map, };
|
||||
use crate::valid_property;
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct MappingTag {
|
||||
@@ -79,12 +75,6 @@ impl Clone for Mapper {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! valid_property {
|
||||
($key:expr, $array:expr) => {{
|
||||
$array.contains(&$key)
|
||||
}};
|
||||
}
|
||||
|
||||
pub struct MappingValueProcessor<'a> {
|
||||
pub(crate) pli: &'a mut PlaylistItem,
|
||||
pub(crate) mapper: &'a Mapper,
|
||||
@@ -154,7 +144,7 @@ impl MappingValueProcessor<'_> {
|
||||
|
||||
fn apply_suffix(&mut self, captures: &HashMap<&String, &str>, verbose: bool) {
|
||||
for (key, value) in &self.mapper.suffix {
|
||||
if valid_property!(key.as_str(), MAPPER_PREFIX_SUFFIX_FIELDS) {
|
||||
if valid_property!(key.as_str(), AFFIX_FIELDS) {
|
||||
match self.apply_tags(value, captures, verbose) {
|
||||
Some(suffix) => {
|
||||
match self.get_property(key) {
|
||||
@@ -173,7 +163,7 @@ impl MappingValueProcessor<'_> {
|
||||
|
||||
fn apply_prefix(&mut self, captures: &HashMap<&String, &str>, verbose: bool) {
|
||||
for (key, value) in &self.mapper.prefix {
|
||||
if valid_property!(key.as_str(), MAPPER_PREFIX_SUFFIX_FIELDS) {
|
||||
if valid_property!(key.as_str(), AFFIX_FIELDS) {
|
||||
match self.apply_tags(value, captures, verbose) {
|
||||
Some(prefix) => {
|
||||
match self.get_property(key) {
|
||||
|
||||
+19
-1
@@ -1,3 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use enum_iterator::Sequence;
|
||||
|
||||
pub const MAPPER_ATTRIBUTE_FIELDS: &[&str] = &[
|
||||
@@ -9,7 +10,24 @@ pub const MAPPER_ATTRIBUTE_FIELDS: &[&str] = &[
|
||||
"rec",
|
||||
"source",
|
||||
];
|
||||
pub const MAPPER_PREFIX_SUFFIX_FIELDS: &[&str] = &["name", "title", "group"];
|
||||
pub const AFFIX_FIELDS: &[&str] = &["name", "title", "group"];
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! valid_property {
|
||||
($key:expr, $array:expr) => {{
|
||||
$array.contains(&$key)
|
||||
}};
|
||||
}
|
||||
|
||||
|
||||
pub fn default_as_false() -> bool { false }
|
||||
|
||||
pub fn default_as_empty_str() -> String { String::from("") }
|
||||
|
||||
pub fn default_as_empty_map() -> HashMap<String, String> { HashMap::new() }
|
||||
|
||||
pub fn default_as_zero() -> u8 { 0 }
|
||||
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Sequence)]
|
||||
pub enum TargetType {
|
||||
|
||||
Reference in New Issue
Block a user