diff --git a/Cargo.lock b/Cargo.lock index 96ae70e8..90257b8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -941,7 +941,7 @@ dependencies = [ [[package]] name = "ffplayout" -version = "0.15.2" +version = "0.16.0" dependencies = [ "chrono", "clap", @@ -960,7 +960,7 @@ dependencies = [ [[package]] name = "ffplayout-api" -version = "0.6.2" +version = "0.6.3" dependencies = [ "actix-files", "actix-multipart", @@ -990,7 +990,7 @@ dependencies = [ [[package]] name = "ffplayout-lib" -version = "0.15.2" +version = "0.16.0" dependencies = [ "chrono", "crossbeam-channel", diff --git a/ffplayout-api/Cargo.toml b/ffplayout-api/Cargo.toml index 4b770827..28d4e881 100644 --- a/ffplayout-api/Cargo.toml +++ b/ffplayout-api/Cargo.toml @@ -4,7 +4,7 @@ description = "Rest API for ffplayout" license = "GPL-3.0" authors = ["Jonathan Baecker jonbae77@gmail.com"] readme = "README.md" -version = "0.6.2" +version = "0.6.3" edition = "2021" [dependencies] diff --git a/ffplayout-api/src/utils/playlist.rs b/ffplayout-api/src/utils/playlist.rs index 33c38d7b..b78be418 100644 --- a/ffplayout-api/src/utils/playlist.rs +++ b/ffplayout-api/src/utils/playlist.rs @@ -1,31 +1,11 @@ -use std::{ - fs::{self, File}, - io::Error, - path::PathBuf, -}; +use std::{fs, path::PathBuf}; use simplelog::*; use crate::utils::{errors::ServiceError, playout_config}; -use ffplayout_lib::utils::{generate_playlist as playlist_generator, JsonPlaylist}; - -fn json_reader(path: &PathBuf) -> Result { - let f = File::options().read(true).write(false).open(&path)?; - let p = serde_json::from_reader(f)?; - - Ok(p) -} - -fn json_writer(path: &PathBuf, data: JsonPlaylist) -> Result<(), Error> { - let f = File::options() - .write(true) - .truncate(true) - .create(true) - .open(&path)?; - serde_json::to_writer_pretty(f, &data)?; - - Ok(()) -} +use ffplayout_lib::utils::{ + generate_playlist as playlist_generator, json_reader, json_writer, JsonPlaylist, +}; pub async fn read_playlist(id: i64, date: String) -> Result { let (config, _) = playout_config(&id).await?; diff --git a/ffplayout-engine/Cargo.toml b/ffplayout-engine/Cargo.toml index baa45227..30323a61 100644 --- a/ffplayout-engine/Cargo.toml +++ b/ffplayout-engine/Cargo.toml @@ -4,8 +4,9 @@ description = "24/7 playout based on rust and ffmpeg" license = "GPL-3.0" authors = ["Jonathan Baecker jonbae77@gmail.com"] readme = "README.md" -version = "0.15.2" +version = "0.16.0" edition = "2021" +default-run = "ffplayout" [dependencies] ffplayout-lib = { path = "../lib" } diff --git a/ffplayout-engine/src/main.rs b/ffplayout-engine/src/main.rs index 4cf40b91..febb3a80 100644 --- a/ffplayout-engine/src/main.rs +++ b/ffplayout-engine/src/main.rs @@ -35,7 +35,7 @@ use ffplayout_lib::utils::{ use utils::Args; #[cfg(debug_assertions)] -use ffplayout_lib::utils::{mock_time, time_now}; +use ffplayout_lib::utils::{import::import_file, mock_time, time_now}; #[derive(Serialize, Deserialize)] struct StatusData { @@ -93,7 +93,7 @@ fn main() { #[cfg(debug_assertions)] fake_time(&args); - let config = get_config(args); + let config = get_config(args.clone()); let config_clone = config.clone(); let play_control = PlayerControl::new(); let playout_stat = PlayoutStatus::new(); @@ -122,6 +122,26 @@ fn main() { exit(0); } + if let Some(path) = args.import { + if args.date.is_none() { + error!("Import needs date parameter!"); + + exit(1); + } + + // convert text/m3u file to playlist + match import_file(&config, &args.date.unwrap(), None, &path) { + Ok(m) => { + info!("{m}"); + exit(0); + } + Err(e) => { + error!("{e}"); + exit(1); + } + } + } + if config.rpc_server.enable { // If RPC server is enable we also fire up a JSON RPC server. thread::spawn(move || json_rpc_server(config_clone, play_ctl, play_stat, proc_ctl2)); diff --git a/ffplayout-engine/src/utils/arg_parse.rs b/ffplayout-engine/src/utils/arg_parse.rs index 41c51c8f..99ffc007 100644 --- a/ffplayout-engine/src/utils/arg_parse.rs +++ b/ffplayout-engine/src/utils/arg_parse.rs @@ -31,6 +31,19 @@ pub struct Args { #[clap(short, long, help = "Play folder content")] pub folder: Option, + #[clap( + short, + long, + help = "Target date (YYYY-MM-DD) for text/m3u to playlist import" + )] + pub date: Option, + + #[clap( + long, + help = "Import a given text/m3u file and create a playlist from it" + )] + pub import: Option, + #[clap(short, long, help = "Path from playlist")] pub playlist: Option, diff --git a/lib/Cargo.toml b/lib/Cargo.toml index a8ae099c..2a99e6d9 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -4,7 +4,7 @@ description = "Library for ffplayout" license = "GPL-3.0" authors = ["Jonathan Baecker jonbae77@gmail.com"] readme = "README.md" -version = "0.15.2" +version = "0.16.0" edition = "2021" [dependencies] diff --git a/lib/src/utils/import.rs b/lib/src/utils/import.rs new file mode 100644 index 00000000..4af4991f --- /dev/null +++ b/lib/src/utils/import.rs @@ -0,0 +1,78 @@ +/// Import text/m3u file and create a playlist out of it +use std::{ + //error::Error, + fs::{create_dir_all, File}, + io::{BufRead, BufReader, Error, ErrorKind}, + path::Path, +}; + +use crate::utils::{json_reader, json_serializer::JsonPlaylist, json_writer, Media, PlayoutConfig}; + +pub fn import_file( + config: &PlayoutConfig, + date: &str, + channel_name: Option, + path: &str, +) -> Result { + let file = File::open(path)?; + let reader = BufReader::new(file); + let mut playlist = JsonPlaylist { + channel: channel_name.unwrap_or_else(|| "Channel 1".to_string()), + date: date.to_string(), + current_file: None, + start_sec: None, + modified: None, + program: vec![], + }; + + let playlist_root = Path::new(&config.playlist.path); + if !playlist_root.is_dir() { + return Err(Error::new( + ErrorKind::Other, + format!( + "Playlist folder {} not exists!", + &config.playlist.path, + ), + )); + } + + let d: Vec<&str> = date.split('-').collect(); + let year = d[0]; + let month = d[1]; + let playlist_path = playlist_root.join(year).join(month); + let playlist_file = &playlist_path.join(format!("{date}.json")); + + create_dir_all(playlist_path)?; + + for line in reader.lines() { + let line = line?; + + if !line.starts_with('#') { + let item = Media::new(0, line, true); + + playlist.program.push(item); + } + } + + let mut file_exists = false; + + if playlist_file.is_file() { + file_exists = true; + let existing_data = json_reader(playlist_file)?; + + if playlist == existing_data { + return Ok(format!("Playlist from {date}, already exists!")); + } + }; + + let mut msg = format!("Write playlist from {date} success!"); + + if file_exists { + msg = format!("Update playlist from {date} success!"); + } + + match json_writer(playlist_file, playlist) { + Ok(_) => Ok(msg), + Err(e) => Err(Error::new(ErrorKind::Other, e)), + } +} diff --git a/lib/src/utils/mod.rs b/lib/src/utils/mod.rs index af5a71a8..3fb47760 100644 --- a/lib/src/utils/mod.rs +++ b/lib/src/utils/mod.rs @@ -1,6 +1,6 @@ use std::{ ffi::OsStr, - fs::{self, metadata}, + fs::{self, metadata, File}, io::{BufRead, BufReader, Error}, net::TcpListener, path::{Path, PathBuf}, @@ -26,6 +26,7 @@ pub mod config; pub mod controller; pub mod folder; mod generator; +pub mod import; pub mod json_serializer; mod json_validate; mod logging; @@ -247,6 +248,24 @@ pub fn fps_calc(r_frame_rate: &str, default: f64) -> f64 { fps } +pub fn json_reader(path: &PathBuf) -> Result { + let f = File::options().read(true).write(false).open(&path)?; + let p = serde_json::from_reader(f)?; + + Ok(p) +} + +pub fn json_writer(path: &PathBuf, data: JsonPlaylist) -> Result<(), Error> { + let f = File::options() + .write(true) + .truncate(true) + .create(true) + .open(&path)?; + serde_json::to_writer_pretty(f, &data)?; + + Ok(()) +} + /// Covert JSON string to ffmpeg filter command. pub fn get_filter_from_json(raw_text: String) -> String { let re1 = Regex::new(r#""|}|\{"#).unwrap();