initial checkin

This commit is contained in:
E Uzu
2021-09-22 10:55:12 +02:00
committed by euzu
commit 9482a448a9
10 changed files with 890 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
/target
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
*.m3u
.idea
Generated
+260
View File
@@ -0,0 +1,260 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [
"winapi",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "clap"
version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "dtoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "indexmap"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "libc"
version = "0.2.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103"
[[package]]
name = "linked-hash-map"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]]
name = "m3u-filter"
version = "0.1.0"
dependencies = [
"clap",
"regex",
"serde",
"serde_yaml",
]
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "proc-macro2"
version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "serde"
version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_yaml"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8c608a35705a5d3cdc9fbe403147647ff34b921f8e833e49306df898f9b20af"
dependencies = [
"dtoa",
"indexmap",
"serde",
"yaml-rust",
]
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "syn"
version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "m3u-filter"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.8"
regex = "1"
clap = "2"
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 euzu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+62
View File
@@ -0,0 +1,62 @@
#m3u-filter
m3u-filter is a simple application which can filter entries out of a playlist in EXTM3U format.
If you have a playlist which contains unwanted entries, you can create filter which Include or Discard entries
based on the header information of the playlist entries.
Currently filter and rename operations support group, name and title fields.
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.
You can override this behaviour with the -c argument.
The input file can be defined inside the config.yml file or can be given as -i argument.
If given as argument, it overrides the config file entry.
the config.yml looks like:
```yaml
***REMOVED***
filename: playlist.m3u
***REMOVED***
***REMOVED***
***REMOVED***
***REMOVED***
***REMOVED***
***REMOVED***
***REMOVED***
***REMOVED***
***REMOVED***
***REMOVED***
***REMOVED***
***REMOVED***
***REMOVED***
```
The filter is either Include mode or Discard mode.
The regular expression syntax is similar to Perl-style regular expressions,
but lacks a few features like look around and backreferences.
The rename supports capture groups. Each group can be adressed with $1, $2 .. in the new_name attribute.
This is needed for players which do not observe the order and sort themselves. In the above example each entry starting
with DE will be prefixed with "1.".
If you dont care about sorting, you dont need the rename block.
##The EXTM3U format is an extension of the M3U format.
m3u has become almost a standard for the formation of playlists of media players and media devices.
A file in the EXTM3U format is a text file with the extension m3u or m3u8.
An example of the contents of the file in the EXTM3U format
```
#EXTM3U
#PLAYLIST:Playlist1
#EXTINF:-1 tvg-name="Channel 1" tvg-logo="http://site.domain / channel1_logo.png", Channel 1
#EXTGRP:Group1
http://site.domain/channel1
#EXTINF:-1 tvg-name="Channel 2" tvg-logo="http://site.domain / channel2_logo.png", Channel 2
#EXTGRP:Group1
http://site.domain/channel2
#EXTINF:-1 tvg-name="Channel 3" tvg-logo="http://site.domain / channel3_logo.png", Channel 3
#EXTGRP:Group2
http://site.domain/channel3 -- reports and targets
```
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+109
View File
@@ -0,0 +1,109 @@
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Serialize, Deserialize)]
pub enum ItemField {
Group,
Name,
Title,
}
impl fmt::Display for ItemField {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ItemField::Group => write!(f, "Group"),
ItemField::Name => write!(f, "Name"),
ItemField::Title => write!(f, "Title"),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub enum FilterMode {
Discard,
Include,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ConfigFilter {
pub field: ItemField,
pub pattern: String,
#[serde(skip_serializing, skip_deserializing)]
pub re: Option<Regex>,
}
impl ConfigFilter {
pub fn prepare(&mut self) -> () {
let re = regex::Regex::new(&self.pattern);
if re.is_err() {
println!("cant parse regex: {}", &self.pattern);
std::process::exit(1);
}
self.re = Some(re.unwrap());
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ConfigFilters {
pub mode: FilterMode,
pub rules: Vec<ConfigFilter>,
}
impl ConfigFilters {
pub fn is_include(&self) -> bool {
matches!(self.mode, FilterMode::Include)
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ConfigRename {
pub field: ItemField,
pub pattern: String,
pub new_name: String,
#[serde(skip_serializing, skip_deserializing)]
pub re: Option<Regex>,
}
impl ConfigRename {
pub fn prepare(&mut self) -> () {
let re = regex::Regex::new(&self.pattern);
if re.is_err() {
println!("cant parse regex: {}", &self.pattern);
std::process::exit(1);
}
self.re = Some(re.unwrap());
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ConfigTarget {
pub filename: String,
pub filter: ConfigFilters,
pub rename: Vec<ConfigRename>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ConfigInput {
pub filename: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
pub ***REMOVED*** ConfigInput,
pub ***REMOVED*** Vec<ConfigTarget>,
}
impl Config {
pub 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();
}
}
}
}
+259
View File
@@ -0,0 +1,259 @@
// 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,
pub logo: String,
pub logo_small: String,
pub group: String,
pub title: String,
pub parent_code: String,
pub audio_track: String,
pub time_shift: String,
pub rec: String,
pub source: String,
}
impl Clone for PlaylistItemHeader {
fn clone(&self) -> PlaylistItemHeader {
PlaylistItemHeader {
id: String::from(&self.id),
name: String::from(&self.name),
logo: String::from(&self.logo),
logo_small: String::from(&self.logo_small),
group: String::from(&self.group),
title: String::from(&self.title),
parent_code: String::from(&self.parent_code),
audio_track: String::from(&self.audio_track),
time_shift: String::from(&self.time_shift),
rec: String::from(&self.rec),
source: String::from(&self.source),
}
}
}
impl fmt::Display for PlaylistItemHeader {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("id:")?;
fmt.write_str(&self.id)?;
fmt.write_str(", name:")?;
fmt.write_str(&self.name)?;
fmt.write_str(", logo:")?;
fmt.write_str(&self.logo)?;
fmt.write_str(", logo-small:")?;
fmt.write_str(&self.logo_small)?;
fmt.write_str(", group:")?;
fmt.write_str(&self.group)?;
fmt.write_str(", title:")?;
fmt.write_str(&self.title)?;
fmt.write_str(", parent-code:")?;
fmt.write_str(&self.parent_code)?;
fmt.write_str(", audio-track:")?;
fmt.write_str(&self.audio_track)?;
fmt.write_str(", timeshift:")?;
fmt.write_str(&self.time_shift)?;
fmt.write_str(", rec:")?;
fmt.write_str(&self.rec)?;
fmt.write_str(", source:")?;
fmt.write_str(&self.source)?;
Ok(())
}
}
pub struct PlaylistItem {
pub header: PlaylistItemHeader,
pub url: String,
}
impl Clone for PlaylistItem {
fn clone(&self) -> PlaylistItem {
PlaylistItem {
header: self.header.clone(),
url: String::from(&self.url),
}
}
}
impl fmt::Display for PlaylistItem {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("header:")?;
fmt.write_str(&self.header.to_string())?;
fmt.write_str(", url:")?;
fmt.write_str(&self.url)?;
Ok(())
}
}
impl PlaylistItem {
pub 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);
}
if !self.header.parent_code.is_empty() {
line = format!("{} parent-code=\"!{}\"", line, self.header.parent_code);
}
if !self.header.audio_track.is_empty() {
line = format!("{} audio-track=\"!{}\"", line, self.header.audio_track);
}
if !self.header.time_shift.is_empty() {
line = format!("{} timeschift=\"!{}\"", line, self.header.time_shift);
}
if !self.header.rec.is_empty() {
line = format!("{} rec=\"!{}\"", line, self.header.rec);
}
line = format!("{},{}\n{}", line, self.header.title, self.url);
line
}
}
pub struct PlaylistGroup {
pub title: String,
pub channels: Vec<PlaylistItem>,
}
impl fmt::Display for PlaylistGroup {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("title:")?;
fmt.write_str(&self.title.to_string())?;
fmt.write_str(", channels:")?;
fmt.write_str(&self.channels.len().to_string())?;
Ok(())
}
}
fn token_value<'a>(it: &'a mut Chars) -> String {
let oc = it.next();
if oc.is_some() && oc.unwrap() == '"' {
return get_value(it);
}
return String::from("");
}
fn get_value<'a>(it: &'a mut Chars) -> String {
let mut result: Vec<char> = vec![];
let mut oc = it.next();
while oc.is_some() && oc.unwrap() != '"' {
result.push(oc.unwrap());
oc = it.next();
}
return String::from(result.iter().collect::<String>());
}
fn token_till(it: &mut Chars, stop_char: char) -> Option<String> {
let mut result: Vec<char> = vec![];
loop {
let c = it.next();
if !c.is_some() || c.unwrap() == stop_char {
break;
}
if result.len() == 0 && c.unwrap().is_whitespace() {
continue;
}
result.push(c.unwrap())
}
if result.len() > 0 { Some(String::from(result.iter().collect::<String>())) } else { None }
}
fn skip_digit(it: &mut Chars) -> Option<char> {
let mut c: Option<char>;
loop {
c = it.next();
if !c.is_some() || !(c.unwrap() == '-' || c.unwrap() == '+' || c.unwrap().is_digit(10)) {
break;
}
}
c
}
fn decode_header(content: &String) -> PlaylistItemHeader {
let mut plih = PlaylistItemHeader {
id: String::from(""),
name: String::from(""),
logo: String::from(""),
logo_small: String::from(""),
group: String::from("Unknown"),
title: String::from(""),
parent_code: String::from(""),
audio_track: String::from(""),
time_shift: String::from(""),
rec: String::from(""),
source: String::from(content),
};
let mut it = content.chars();
if token_till(&mut it, ':') == Some(String::from("#EXTINF")) {
let mut c = skip_digit(&mut it);
loop {
if !c.is_some() {
break;
}
match c.unwrap() {
',' => plih.title = get_value(&mut it),
_ => {
let token = token_till(&mut it, '=');
if token != None {
let value = token_value(&mut it);
match token.unwrap().as_str() {
"tvg-id" => plih.id = value,
"tvg-name" => plih.name = value,
"group-title" => if !value.is_empty() { plih.group = value },
"parent-code" => plih.parent_code = value,
"audio-track" => plih.audio_track = value,
"tvg-logo" => plih.logo = value,
"tvg-logo-small" => plih.logo_small = value,
"timeshift" => plih.time_shift = value,
"tvg-rec" => plih.rec = value,
_ => {}
}
}
}
}
c = it.next();
}
}
plih
}
pub fn decode(lines: &Vec<String>) -> Vec<PlaylistGroup> {
let mut groups: HashMap<String, Vec<PlaylistItem>> = HashMap::new();
let mut sort_order: Vec<String> = vec![];
let mut header: Option<String> = None;
for line in lines {
if line.starts_with("#EXTINF") {
header = Some(String::from(line));
continue;
}
if header.is_some() {
let item = PlaylistItem { header: decode_header(&header.unwrap()), url: String::from(line) };
let key = String::from(&item.header.group);
let key2 = String::from(&item.header.group);
match groups.entry(key) {
Entry::Vacant(e) => {
e.insert(vec![item]);
sort_order.push(key2);
}
Entry::Occupied(mut e) => { e.get_mut().push(item); }
}
}
header = None;
}
let mut result: Vec<PlaylistGroup> = vec![];
for (key, value) in groups {
result.push(PlaylistGroup { title: key, channels: value });
}
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();
return i1.cmp(&i2);
});
result
}
+67
View File
@@ -0,0 +1,67 @@
use std::io::BufRead;
mod m3u;
mod config;
mod processor;
fn open_file(file_name: &str) -> std::fs::File {
let file = match std::fs::File::open(file_name) {
Ok(file) => file,
Err(_) => {
println!("cant open file: {}", file_name);
std::process::exit(1);
}
};
file
}
fn main() {
let matches = clap::App::new("m3u-filter")
.version("0.1.0")
.author("euzu")
.about("Extended M3U playlist filter")
.arg(clap::Arg::with_name("config")
.short("c")
.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"))
.get_matches();
let default_path = get_default_config_path();
let config_file = matches.value_of("config").unwrap_or(default_path.as_str());
let mut cfg: config::Config = match serde_yaml::from_reader(open_file(config_file)) {
Ok(result) => result,
Err(e) => {
println!("cant read config file: {}", e);
std::process::exit(1);
}
};
cfg.prepare();
let file_name = matches.value_of("input").unwrap_or(if cfg.input.filename.is_empty() { "playlist.m3u" } else { cfg.input.filename.as_str() });
let reader: Vec<String> = std::io::BufReader::new(open_file(file_name)).lines().map(|l| l.expect("Could not parse line")).collect();
let result = m3u::decode(&reader);
processor::write_m3u(&result, &cfg);
}
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 {
Ok(ref exe) => exe.parent().unwrap_or(default_path),
Err(_) => default_path
};
let config_path = path.join("config.yml");
String::from(if config_path.exists() {
config_path.to_str().unwrap_or("./config.yml")
} else {
"./config.yml"
})
}
+75
View File
@@ -0,0 +1,75 @@
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) {
for t in &cfg.targets {
let mut file = match File::create(&t.filename) {
Ok(file) => file,
Err(_) => {
println!("cant open file: {}", t.filename);
std::process::exit(1);
}
};
file.write(b"#EXTM3U\n").expect("Unable to write file");
for pg in 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(), |p| p.to_m3u());
file.write(content.as_bytes()).expect("Unable to write file");
file.write(b"\n").expect("Unable to write file");
}
}
}
}
}
fn get_field_value<'a>(pli: &'a m3u::PlaylistItem, field: &config::ItemField) -> &'a str {
let value = match field {
config::ItemField::Group => pli.header.group.as_str(),
config::ItemField::Name => pli.header.name.as_str(),
config::ItemField::Title => pli.header.title.as_str(),
};
value
}
fn set_field_value(pli: &mut m3u::PlaylistItem, field: &config::ItemField, value: String) -> () {
let header = &mut pli.header;
match field {
config::ItemField::Group => header.group = value,
config::ItemField::Name => header.name = value,
config::ItemField::Title => header.title = value,
};
}
fn is_valid(pli: &m3u::PlaylistItem, filter: &config::ConfigFilters) -> bool {
let mut matched = false;
for r in &filter.rules {
let value = get_field_value(pli, &r.field);
matched = r.re.as_ref().unwrap().is_match(value);
if matched {
break;
}
}
return if filter.is_include() {
matched
} else {
!matched
};
}
fn exec_rename(pli: &m3u::PlaylistItem, rename: &Vec<config::ConfigRename>) -> Option<PlaylistItem> {
if rename.len() > 0 {
let mut result = pli.clone();
for r in rename {
let value = get_field_value(pli, &r.field);
let cap = r.re.as_ref().unwrap().replace_all(value, &r.new_name);
let value = cap.into_owned();
set_field_value(&mut result, &r.field, value);
}
return Some(result);
}
None
}