diff --git a/Cargo.lock b/Cargo.lock index 1440ded7..a3f24df9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,9 +79,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.1.6" +version = "3.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8c93436c21e4698bacadf42917db28b23017027a4deccb35dbe47a7e7840123" +checksum = "71c47df61d9e16dc010b55dba1952a57d8c215dbb533fd13cdd13369aac73b1c" dependencies = [ "atty", "bitflags", @@ -96,9 +96,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.1.4" +version = "3.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16" +checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1" dependencies = [ "heck", "proc-macro-error", @@ -132,6 +132,15 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "email-encoding" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6690291166824e467790ac08ba42f241791567e8337bbf00c5a6e87889629f98" +dependencies = [ + "base64", +] + [[package]] name = "fastrand" version = "1.7.0" @@ -143,7 +152,7 @@ dependencies = [ [[package]] name = "ffplayout-rs" -version = "0.8.0" +version = "0.8.1" dependencies = [ "chrono", "clap", @@ -355,9 +364,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" dependencies = [ "autocfg", "hashbrown", @@ -431,11 +440,12 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "lettre" -version = "0.10.0-rc.4" +version = "0.10.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d8da8f34d086b081c9cc3b57d3bb3b51d16fc06b5c848a188e2f14d58ac2a5" +checksum = "5144148f337be14dabfc0f0d85b691a68ac6c77ef22a5c47c5504b70a7c9fcf3" dependencies = [ "base64", + "email-encoding", "fastrand", "futures-util", "hostname", @@ -555,9 +565,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09bf6f32a3afefd0b587ee42ed19acd945c6d1f3b5424040f50b2f24ab16be77" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" dependencies = [ "lazy_static", "libc", @@ -717,9 +727,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "ppv-lite86" @@ -817,9 +827,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae183fc1b06c149f0c1793e1eb447c8b04bfe46d48e9e48bfb8d2d7ed64ecf0" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" dependencies = [ "bitflags", ] @@ -961,9 +971,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" [[package]] name = "strsim" @@ -973,9 +983,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.89" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea297be220d52398dcc07ce15a209fce436d361735ac1db700cab3b6cdfb9f54" +checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 4c9143e9..8faa94b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ffplayout-rs" -version = "0.8.0" +version = "0.8.1" edition = "2021" [dependencies] @@ -8,7 +8,7 @@ chrono = "0.4" clap = { version = "3.1", features = ["derive"] } ffprobe = "0.3" file-rotate = "0.6" -lettre = "0.10.0-rc.4" +lettre = "0.10.0-rc.5" log = "0.4" notify = "4.0" once_cell = "1.10" diff --git a/README.md b/README.md index 77e38116..3762fe3c 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ The main purpose of ffplayout is to provide a 24/7 broadcasting solution that pl - output: - **stream** - **desktop** + - **HLS** Requirements ----- @@ -105,6 +106,26 @@ But be careful with it, better test it multiple times! More informations in [Wiki](https://github.com/ffplayout/ffplayout_engine/wiki/Remote-URL-Source) +HLS output +----- + +For outputting to HLS, output parameters should look like: + +```yaml +out: + ... + + output_param: >- + ... + + -flags +cgop + -f hls + -hls_time 6 + -hls_list_size 600 + -hls_flags append_list+delete_segments+omit_endlist+program_date_time + -hls_segment_filename /var/www/html/live/stream-%09d.ts /var/www/html/live/stream.m3u8 +``` + Installation ----- diff --git a/src/input/playlist.rs b/src/input/playlist.rs index 95761f97..73333967 100644 --- a/src/input/playlist.rs +++ b/src/input/playlist.rs @@ -83,6 +83,9 @@ impl CurrentProgram { self.json_mod = json.modified; self.nodes = json.program; + + self.get_current_clip(); + self.index += 1; } } else { error!( @@ -159,28 +162,43 @@ impl CurrentProgram { } } - fn get_init_clip(&mut self) { + fn get_current_time(&mut self) -> f64 { let mut time_sec = get_sec(); if time_sec < self.start_sec { time_sec += self.config.playlist.length_sec.unwrap() } + time_sec + } + + fn get_current_clip(&mut self) { + let time_sec = self.get_current_time(); + for (i, item) in self.nodes.iter_mut().enumerate() { if item.begin.unwrap() + item.out - item.seek > time_sec { *self.init.lock().unwrap() = false; - self.index = i + 1; - - // de-instance node to preserve original values in list - let mut node_clone = item.clone(); - node_clone.seek = time_sec - node_clone.begin.unwrap(); - - self.current_node = handle_list_init(node_clone); + self.index = i; break; } } } + + fn init_clip(&mut self) { + self.get_current_clip(); + + if !*self.init.lock().unwrap() { + let time_sec = self.get_current_time(); + + // de-instance node to preserve original values in list + let mut node_clone = self.nodes[self.index].clone(); + self.index += 1; + + node_clone.seek = time_sec - node_clone.begin.unwrap(); + self.current_node = handle_list_init(node_clone); + } + } } impl Iterator for CurrentProgram { @@ -192,7 +210,7 @@ impl Iterator for CurrentProgram { self.check_update(true); if self.json_path.is_some() { - self.get_init_clip(); + self.init_clip(); } if *self.init.lock().unwrap() { @@ -209,7 +227,7 @@ impl Iterator for CurrentProgram { >= self.config.playlist.length_sec.unwrap() + self.config.playlist.start_sec.unwrap() { - self.get_init_clip(); + self.init_clip(); } else { let mut current_time = get_sec(); let (_, total_delta) = get_delta(¤t_time); diff --git a/src/utils/json_reader.rs b/src/utils/json_reader.rs index 3f43a597..8fcd8697 100644 --- a/src/utils/json_reader.rs +++ b/src/utils/json_reader.rs @@ -10,7 +10,7 @@ use tokio::runtime::Handle; use crate::utils::{get_date, modified_time, validate_playlist, GlobalConfig, Media}; -pub const DUMMY_LEN: f64 = 20.0; +pub const DUMMY_LEN: f64 = 60.0; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Playlist { @@ -62,6 +62,7 @@ pub fn read_json( let mut current_file: String = playlist_path.as_path().display().to_string(); if let Some(p) = path { + playlist_path = Path::new(&p).to_owned(); current_file = p } @@ -73,7 +74,11 @@ pub fn read_json( info!("Read Playlist: {}", ¤t_file); - let f = File::open(¤t_file).expect("Could not open json playlist file."); + let f = File::options() + .read(true) + .write(false) + .open(¤t_file) + .expect("Could not open json playlist file."); let mut playlist: Playlist = serde_json::from_reader(f).expect("Could not read json playlist file."); diff --git a/src/utils/json_validate.rs b/src/utils/json_validate.rs index eccbf061..b96da6c9 100644 --- a/src/utils/json_validate.rs +++ b/src/utils/json_validate.rs @@ -6,14 +6,16 @@ use crate::utils::{sec_to_time, GlobalConfig, MediaProbe, Playlist}; pub async fn validate_playlist(playlist: Playlist, is_terminated: Arc>, config: GlobalConfig) { let date = playlist.date; - let length = config.playlist.length_sec.unwrap(); - let mut start_sec = 0.0; + let mut length = config.playlist.length_sec.unwrap(); + let mut begin = config.playlist.start_sec.unwrap(); + + length += begin; debug!("validate playlist from: {date}"); for item in playlist.program.iter() { if *is_terminated.lock().unwrap() { - break + return } if Path::new(&item.source).is_file() { @@ -22,25 +24,25 @@ pub async fn validate_playlist(playlist: Playlist, is_terminated: Arc{} at {}", - sec_to_time(start_sec), + sec_to_time(begin), item.source ); } } else { error!( "File on position {} not exists: {}", - sec_to_time(start_sec), + sec_to_time(begin), item.source ); } - start_sec += item.out - item.seek; + begin += item.out - item.seek; } - if length > start_sec + 1.0 && !*is_terminated.lock().unwrap() { + if length > begin + 1.0 { error!( "Playlist from {date} not long enough, {} needed!", - sec_to_time(length - start_sec), + sec_to_time(length - begin), ); } } diff --git a/src/utils/logging.rs b/src/utils/logging.rs index 43cc8021..27654b85 100644 --- a/src/utils/logging.rs +++ b/src/utils/logging.rs @@ -10,7 +10,10 @@ use std::{ }; use file_rotate::{compression::Compression, suffix::AppendCount, ContentLimit, FileRotate}; -use lettre::{transport::smtp::authentication::Credentials, Message, SmtpTransport, Transport}; +use lettre::{ + message::header, transport::smtp::authentication::Credentials, Message, SmtpTransport, + Transport, +}; use log::{Level, LevelFilter, Log, Metadata, Record}; use simplelog::*; use tokio::runtime::Handle; @@ -24,6 +27,7 @@ fn send_mail(msg: String) { .from(config.mail.sender_addr.parse().unwrap()) .to(config.mail.recipient.parse().unwrap()) .subject(config.mail.subject.clone()) + .header(header::ContentType::TEXT_PLAIN) .body(clean_string(msg.clone())) .unwrap(); @@ -79,7 +83,11 @@ pub struct LogMailer { } impl LogMailer { - pub fn new(log_level: LevelFilter, config: Config, messages: Arc>>) -> Box { + pub fn new( + log_level: LevelFilter, + config: Config, + messages: Arc>>, + ) -> Box { Box::new(LogMailer { level: log_level, config, @@ -97,10 +105,16 @@ impl Log for LogMailer { if self.enabled(record.metadata()) { match record.level() { Level::Error => { - self.messages.lock().unwrap().push(record.args().to_string()); + self.messages + .lock() + .unwrap() + .push(record.args().to_string()); } Level::Warn => { - self.messages.lock().unwrap().push(record.args().to_string()); + self.messages + .lock() + .unwrap() + .push(record.args().to_string()); } _ => (), } @@ -201,10 +215,7 @@ pub fn init_logging( let mut filter = LevelFilter::Error; let messages: Arc>> = Arc::new(Mutex::new(Vec::new())); - rt_handle.spawn(mail_queue( - messages.clone(), - is_terminated.clone(), - )); + rt_handle.spawn(mail_queue(messages.clone(), is_terminated.clone())); let mail_config = log_config .clone() diff --git a/src/utils/mod.rs b/src/utils/mod.rs index ca93036c..69cd733c 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -292,13 +292,11 @@ pub fn seek_and_length(src: String, seek: f64, out: f64, duration: f64) -> Vec out { source_cmd.append(&mut vec![ "-t".to_string(),