Merge pull request #514 from jb-alvarado/master
restructure and simplify playlist code, add more tests and test clips/playlists
This commit is contained in:
commit
139c6f0434
53
Cargo.lock
generated
53
Cargo.lock
generated
@ -520,7 +520,7 @@ dependencies = [
|
||||
"futures-lite 2.2.0",
|
||||
"parking",
|
||||
"polling 3.3.2",
|
||||
"rustix 0.38.30",
|
||||
"rustix 0.38.31",
|
||||
"slab",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
@ -1210,7 +1210,7 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
|
||||
|
||||
[[package]]
|
||||
name = "ffplayout"
|
||||
version = "0.20.4"
|
||||
version = "0.20.4-beta1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
@ -1232,7 +1232,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ffplayout-api"
|
||||
version = "0.20.4"
|
||||
version = "0.20.4-beta1"
|
||||
dependencies = [
|
||||
"actix-files",
|
||||
"actix-multipart",
|
||||
@ -1271,7 +1271,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ffplayout-lib"
|
||||
version = "0.20.4"
|
||||
version = "0.20.4-beta1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crossbeam-channel",
|
||||
@ -1767,9 +1767,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.1"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b"
|
||||
checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
@ -1949,9 +1949,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.152"
|
||||
version = "0.2.153"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
@ -2204,6 +2204,12 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
@ -2448,7 +2454,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"pin-project-lite",
|
||||
"rustix 0.38.30",
|
||||
"rustix 0.38.31",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
@ -2606,9 +2612,9 @@ checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.23"
|
||||
version = "0.11.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41"
|
||||
checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
@ -2632,6 +2638,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
@ -2730,9 +2737,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.30"
|
||||
version = "0.38.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
|
||||
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"errno",
|
||||
@ -3363,6 +3370,12 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.30.5"
|
||||
@ -3408,7 +3421,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand 2.0.1",
|
||||
"redox_syscall",
|
||||
"rustix 0.38.30",
|
||||
"rustix 0.38.31",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
@ -3423,7 +3436,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tests"
|
||||
version = "0.20.4"
|
||||
version = "0.20.4-beta1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crossbeam-channel",
|
||||
@ -3468,13 +3481,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.31"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e"
|
||||
checksum = "fe80ced77cbfb4cb91a94bf72b378b4b6791a0d9b7f09d0be747d1bdff4e68bd"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"libc",
|
||||
"num-conv",
|
||||
"num_threads",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
@ -3490,10 +3504,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.16"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f"
|
||||
checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
|
@ -4,7 +4,7 @@ default-members = ["ffplayout-api", "ffplayout-engine", "tests"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "0.20.4"
|
||||
version = "0.20.4-beta1"
|
||||
license = "GPL-3.0"
|
||||
repository = "https://github.com/ffplayout/ffplayout"
|
||||
authors = ["Jonathan Baecker <jonbae77@gmail.com>"]
|
||||
|
@ -11,9 +11,9 @@ use serde_json::json;
|
||||
use simplelog::*;
|
||||
|
||||
use ffplayout_lib::utils::{
|
||||
controller::PlayerControl, gen_dummy, get_delta, get_sec, is_close, is_remote,
|
||||
json_serializer::read_json, loop_filler, loop_image, modified_time, seek_and_length, Media,
|
||||
MediaProbe, PlayoutConfig, PlayoutStatus, DUMMY_LEN, IMAGE_FORMAT,
|
||||
controller::PlayerControl, gen_dummy, get_delta, is_close, is_remote,
|
||||
json_serializer::read_json, loop_filler, loop_image, modified_time, seek_and_length,
|
||||
time_in_seconds, Media, MediaProbe, PlayoutConfig, PlayoutStatus, IMAGE_FORMAT,
|
||||
};
|
||||
|
||||
/// Struct for current playlist.
|
||||
@ -23,6 +23,7 @@ use ffplayout_lib::utils::{
|
||||
pub struct CurrentProgram {
|
||||
config: PlayoutConfig,
|
||||
start_sec: f64,
|
||||
end_sec: f64,
|
||||
json_mod: Option<String>,
|
||||
json_path: Option<String>,
|
||||
json_date: String,
|
||||
@ -30,6 +31,8 @@ pub struct CurrentProgram {
|
||||
current_node: Media,
|
||||
is_terminated: Arc<AtomicBool>,
|
||||
playout_stat: PlayoutStatus,
|
||||
last_json_path: Option<String>,
|
||||
last_node_ad: bool,
|
||||
}
|
||||
|
||||
/// Prepare a playlist iterator.
|
||||
@ -40,125 +43,81 @@ impl CurrentProgram {
|
||||
is_terminated: Arc<AtomicBool>,
|
||||
player_control: &PlayerControl,
|
||||
) -> Self {
|
||||
let json = read_json(
|
||||
config,
|
||||
player_control,
|
||||
None,
|
||||
is_terminated.clone(),
|
||||
true,
|
||||
false,
|
||||
);
|
||||
|
||||
if let Some(file) = &json.current_file {
|
||||
info!("Read Playlist: <b><magenta>{}</></b>", file);
|
||||
}
|
||||
|
||||
*player_control.current_list.lock().unwrap() = json.program;
|
||||
*playout_stat.current_date.lock().unwrap() = json.date.clone();
|
||||
|
||||
if *playout_stat.date.lock().unwrap() != json.date {
|
||||
let data = json!({
|
||||
"time_shift": 0.0,
|
||||
"date": json.date,
|
||||
});
|
||||
|
||||
let json: String = serde_json::to_string(&data).expect("Serialize status data failed");
|
||||
if let Err(e) = fs::write(config.general.stat_file.clone(), json) {
|
||||
error!("Unable to write status file: {e}");
|
||||
};
|
||||
}
|
||||
|
||||
Self {
|
||||
config: config.clone(),
|
||||
start_sec: json.start_sec.unwrap(),
|
||||
json_mod: json.modified,
|
||||
json_path: json.current_file,
|
||||
json_date: json.date,
|
||||
start_sec: config.playlist.start_sec.unwrap(),
|
||||
end_sec: config.playlist.length_sec.unwrap(),
|
||||
json_mod: None,
|
||||
json_path: None,
|
||||
json_date: String::new(),
|
||||
player_control: player_control.clone(),
|
||||
current_node: Media::new(0, "", false),
|
||||
is_terminated,
|
||||
playout_stat,
|
||||
last_json_path: None,
|
||||
last_node_ad: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Check if playlist file got updated, and when yes we reload it and setup everything in place.
|
||||
fn check_update(&mut self, seek: bool) {
|
||||
if self.json_path.is_none() {
|
||||
// If the playlist was missing, we check here to see if it came back.
|
||||
let json = read_json(
|
||||
&self.config,
|
||||
&self.player_control,
|
||||
None,
|
||||
self.is_terminated.clone(),
|
||||
seek,
|
||||
false,
|
||||
);
|
||||
fn load_or_update_playlist(&mut self, seek: bool) {
|
||||
let mut get_current = false;
|
||||
let mut reload = false;
|
||||
|
||||
if let Some(file) = &json.current_file {
|
||||
info!("Read Playlist: <b><magenta>{file}</></b>");
|
||||
if let Some(path) = self.json_path.clone() {
|
||||
if (Path::new(&path).is_file() || is_remote(&path))
|
||||
&& self.json_mod != modified_time(&path)
|
||||
{
|
||||
info!("Reload playlist <b><magenta>{path}</></b>");
|
||||
self.playout_stat.list_init.store(true, Ordering::SeqCst);
|
||||
get_current = true;
|
||||
reload = true;
|
||||
}
|
||||
} else {
|
||||
get_current = true;
|
||||
}
|
||||
|
||||
self.json_path = json.current_file;
|
||||
self.json_mod = json.modified;
|
||||
*self.player_control.current_list.lock().unwrap() = json.program;
|
||||
} else if Path::new(&self.json_path.clone().unwrap()).is_file()
|
||||
|| is_remote(&self.json_path.clone().unwrap())
|
||||
{
|
||||
// If the playlist exists, we check here if it has been modified.
|
||||
let mod_time = modified_time(&self.json_path.clone().unwrap());
|
||||
|
||||
if self.json_mod != mod_time {
|
||||
// when playlist has changed, reload it
|
||||
info!(
|
||||
"Reload playlist <b><magenta>{}</></b>",
|
||||
self.json_path.clone().unwrap()
|
||||
);
|
||||
|
||||
if get_current {
|
||||
let json = read_json(
|
||||
&self.config,
|
||||
&self.player_control,
|
||||
self.json_path.clone(),
|
||||
self.is_terminated.clone(),
|
||||
false,
|
||||
seek,
|
||||
false,
|
||||
);
|
||||
|
||||
if !reload {
|
||||
if let Some(file) = &json.current_file {
|
||||
info!("Read playlist: <b><magenta>{file}</></b>");
|
||||
}
|
||||
}
|
||||
|
||||
self.json_path = json.current_file;
|
||||
self.json_mod = json.modified;
|
||||
*self.player_control.current_list.lock().unwrap() = json.program;
|
||||
|
||||
self.playout_stat.list_init.store(true, Ordering::SeqCst);
|
||||
}
|
||||
} else {
|
||||
// If the playlist disappears after normal run, we end up here.
|
||||
trace!("check_update, missing playlist");
|
||||
error!(
|
||||
"Playlist <b><magenta>{}</></b> not exist!",
|
||||
self.json_path.clone().unwrap()
|
||||
);
|
||||
if self.json_path.is_none() {
|
||||
trace!("missing playlist");
|
||||
|
||||
let media = Media::new(0, "", false);
|
||||
|
||||
self.json_mod = None;
|
||||
self.json_path = None;
|
||||
self.current_node = media.clone();
|
||||
self.current_node = Media::new(0, "", false);
|
||||
self.playout_stat.list_init.store(true, Ordering::SeqCst);
|
||||
self.player_control.current_index.store(0, Ordering::SeqCst);
|
||||
*self.player_control.current_list.lock().unwrap() = vec![media];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if day is past and it is time for a new playlist.
|
||||
fn check_for_next_playlist(&mut self) -> bool {
|
||||
let current_time = get_sec();
|
||||
let start_sec = self.config.playlist.start_sec.unwrap();
|
||||
let target_length = self.config.playlist.length_sec.unwrap();
|
||||
let (delta, total_delta) = get_delta(&self.config, ¤t_time);
|
||||
let mut duration = self.current_node.out;
|
||||
fn check_for_playlist(&mut self, seek: bool) -> bool {
|
||||
let (delta, total_delta) = get_delta(&self.config, &time_in_seconds());
|
||||
let mut next = false;
|
||||
|
||||
if self.current_node.duration > self.current_node.out {
|
||||
duration = self.current_node.duration
|
||||
}
|
||||
let duration = if self.current_node.duration >= self.current_node.out {
|
||||
self.current_node.duration
|
||||
} else {
|
||||
// maybe out is longer to be able to loop
|
||||
self.current_node.out
|
||||
};
|
||||
|
||||
trace!(
|
||||
"delta: {delta}, total_delta: {total_delta}, current index: {}",
|
||||
@ -166,7 +125,7 @@ impl CurrentProgram {
|
||||
);
|
||||
|
||||
let mut next_start =
|
||||
self.current_node.begin.unwrap_or_default() - start_sec + duration + delta;
|
||||
self.current_node.begin.unwrap_or_default() - self.start_sec + duration + delta;
|
||||
|
||||
if self.player_control.current_index.load(Ordering::SeqCst)
|
||||
== self.player_control.current_list.lock().unwrap().len() - 1
|
||||
@ -174,12 +133,12 @@ impl CurrentProgram {
|
||||
next_start += self.config.general.stop_threshold;
|
||||
}
|
||||
|
||||
trace!("next_start: {next_start}, target_length: {target_length}");
|
||||
trace!("next_start: {next_start}, end_sec: {}", self.end_sec);
|
||||
|
||||
// Check if we over the target length or we are close to it, if so we load the next playlist.
|
||||
if next_start >= target_length
|
||||
if next_start >= self.end_sec
|
||||
|| is_close(total_delta, 0.0, 2.0)
|
||||
|| is_close(total_delta, target_length, 2.0)
|
||||
|| is_close(total_delta, self.end_sec, 2.0)
|
||||
{
|
||||
trace!("get next day");
|
||||
next = true;
|
||||
@ -194,60 +153,62 @@ impl CurrentProgram {
|
||||
);
|
||||
|
||||
if let Some(file) = &json.current_file {
|
||||
info!("Read next Playlist: <b><magenta>{}</></b>", file);
|
||||
info!("Read next playlist: <b><magenta>{file}</></b>");
|
||||
}
|
||||
|
||||
let data = json!({
|
||||
"time_shift": 0.0,
|
||||
"date": json.date,
|
||||
});
|
||||
|
||||
*self.playout_stat.current_date.lock().unwrap() = json.date.clone();
|
||||
*self.playout_stat.time_shift.lock().unwrap() = 0.0;
|
||||
let status_data: String =
|
||||
serde_json::to_string(&data).expect("Serialize status data failed");
|
||||
|
||||
if let Err(e) = fs::write(self.config.general.stat_file.clone(), status_data) {
|
||||
error!("Unable to write status file: {e}");
|
||||
};
|
||||
self.playout_stat.list_init.store(false, Ordering::SeqCst);
|
||||
self.set_status(json.date.clone());
|
||||
|
||||
self.json_path = json.current_file.clone();
|
||||
self.json_mod = json.modified;
|
||||
self.json_date = json.date;
|
||||
|
||||
*self.player_control.current_list.lock().unwrap() = json.program;
|
||||
self.player_control.current_index.store(0, Ordering::SeqCst);
|
||||
|
||||
if json.current_file.is_none() {
|
||||
self.playout_stat.list_init.store(true, Ordering::SeqCst);
|
||||
} else {
|
||||
self.playout_stat.list_init.store(false, Ordering::SeqCst);
|
||||
}
|
||||
self.load_or_update_playlist(seek)
|
||||
}
|
||||
|
||||
next
|
||||
}
|
||||
|
||||
fn set_status(&mut self, date: String) {
|
||||
*self.playout_stat.current_date.lock().unwrap() = date.clone();
|
||||
*self.playout_stat.time_shift.lock().unwrap() = 0.0;
|
||||
|
||||
if let Err(e) = fs::write(
|
||||
&self.config.general.stat_file,
|
||||
serde_json::to_string(&json!({
|
||||
"time_shift": 0.0,
|
||||
"date": date,
|
||||
}))
|
||||
.unwrap(),
|
||||
) {
|
||||
error!("Unable to write status file: {e}");
|
||||
};
|
||||
}
|
||||
|
||||
// Check if last and/or next clip is a advertisement.
|
||||
fn last_next_ad(&mut self) {
|
||||
let index = self.player_control.current_index.load(Ordering::SeqCst);
|
||||
let current_list = self.player_control.current_list.lock().unwrap();
|
||||
|
||||
if index + 1 < current_list.len() && ¤t_list[index + 1].category == "advertisement" {
|
||||
self.current_node.next_ad = Some(true);
|
||||
self.current_node.next_ad = true;
|
||||
}
|
||||
|
||||
if index > 0
|
||||
&& index < current_list.len()
|
||||
&& ¤t_list[index - 1].category == "advertisement"
|
||||
{
|
||||
self.current_node.last_ad = Some(true);
|
||||
self.current_node.last_ad = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Get current time and when we are before start time,
|
||||
// we add full seconds of a day to it.
|
||||
fn get_current_time(&mut self) -> f64 {
|
||||
let mut time_sec = get_sec();
|
||||
let mut time_sec = time_in_seconds();
|
||||
|
||||
if time_sec < self.start_sec {
|
||||
time_sec += self.config.playlist.length_sec.unwrap()
|
||||
@ -304,6 +265,8 @@ impl CurrentProgram {
|
||||
// de-instance node to preserve original values in list
|
||||
let mut node_clone = nodes[index].clone();
|
||||
|
||||
trace!("Clip from init: {}", node_clone.source);
|
||||
|
||||
// Important! When no manual drop is happen here, lock is still active in handle_list_init
|
||||
drop(nodes);
|
||||
|
||||
@ -329,6 +292,39 @@ impl CurrentProgram {
|
||||
|
||||
is_filler
|
||||
}
|
||||
|
||||
fn fill_end(&mut self, total_delta: f64) {
|
||||
// Fill end from playlist
|
||||
|
||||
let index = self.player_control.current_index.load(Ordering::SeqCst);
|
||||
let mut media = Media::new(index, "", false);
|
||||
media.begin = Some(time_in_seconds());
|
||||
media.duration = total_delta;
|
||||
media.out = total_delta;
|
||||
|
||||
self.current_node = gen_source(
|
||||
&self.config,
|
||||
media,
|
||||
&self.playout_stat,
|
||||
&self.player_control,
|
||||
0,
|
||||
);
|
||||
|
||||
self.player_control
|
||||
.current_list
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(self.current_node.clone());
|
||||
self.last_next_ad();
|
||||
|
||||
self.current_node.last_ad = self.last_node_ad;
|
||||
self.current_node
|
||||
.add_filter(&self.config, &self.playout_stat.chain);
|
||||
|
||||
self.player_control
|
||||
.current_index
|
||||
.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the playlist iterator
|
||||
@ -336,7 +332,9 @@ impl Iterator for CurrentProgram {
|
||||
type Item = Media;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.check_update(self.playout_stat.list_init.load(Ordering::SeqCst));
|
||||
self.last_json_path = self.json_path.clone();
|
||||
self.last_node_ad = self.current_node.last_ad;
|
||||
self.check_for_playlist(self.playout_stat.list_init.load(Ordering::SeqCst));
|
||||
|
||||
if self.playout_stat.list_init.load(Ordering::SeqCst) {
|
||||
trace!("Init playlist, from next iterator");
|
||||
@ -346,104 +344,29 @@ impl Iterator for CurrentProgram {
|
||||
init_clip_is_filler = self.init_clip();
|
||||
}
|
||||
|
||||
if self.playout_stat.list_init.load(Ordering::SeqCst) {
|
||||
// On init load, playlist could be not long enough,
|
||||
// so we check if we can take the next playlist already,
|
||||
// or we fill the gap with a dummy.
|
||||
if self.playout_stat.list_init.load(Ordering::SeqCst) && !init_clip_is_filler {
|
||||
// On init load, playlist could be not long enough, or clips are not found
|
||||
// so we fill the gap with a dummy.
|
||||
trace!("Init clip is no filler");
|
||||
|
||||
let (last_index, new_length) = if let Ok(c_list) =
|
||||
self.player_control.current_list.try_lock()
|
||||
{
|
||||
let last_index = c_list.len() - 1;
|
||||
let mut new_length =
|
||||
self.current_node.begin.unwrap_or_default() + self.current_node.duration;
|
||||
|
||||
if self.current_node.index.unwrap_or_default() >= last_index {
|
||||
// Only take last clip when index match or over
|
||||
trace!(
|
||||
"change current node, because index ({}) is over last item index: {last_index}",
|
||||
self.current_node.index.unwrap_or_default()
|
||||
);
|
||||
|
||||
self.current_node = c_list[last_index].clone();
|
||||
new_length = c_list[last_index].begin.unwrap_or_default()
|
||||
+ c_list[last_index].duration;
|
||||
}
|
||||
|
||||
(last_index, new_length)
|
||||
} else {
|
||||
trace!("Lock from current_list not possible!");
|
||||
|
||||
(0, 0.0)
|
||||
};
|
||||
|
||||
trace!(
|
||||
"Init playlist after playlist end, or missing files, index: {}",
|
||||
self.current_node.index.unwrap_or_default()
|
||||
);
|
||||
|
||||
let next_playlist = self.check_for_next_playlist();
|
||||
|
||||
if new_length
|
||||
>= self.config.playlist.length_sec.unwrap()
|
||||
+ self.config.playlist.start_sec.unwrap()
|
||||
{
|
||||
self.init_clip();
|
||||
} else if next_playlist
|
||||
&& self.player_control.current_list.lock().unwrap().len() > 1
|
||||
{
|
||||
let index = self
|
||||
.player_control
|
||||
.current_index
|
||||
.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
let nodes = self.player_control.current_list.lock().unwrap();
|
||||
let node_clone = nodes[index].clone();
|
||||
|
||||
drop(nodes);
|
||||
|
||||
self.current_node = gen_source(
|
||||
&self.config,
|
||||
node_clone,
|
||||
&self.playout_stat,
|
||||
&self.player_control,
|
||||
0,
|
||||
);
|
||||
|
||||
return Some(self.current_node.clone());
|
||||
} else if !init_clip_is_filler {
|
||||
// TODO: maybe this should be never true
|
||||
|
||||
// fill missing length from playlist
|
||||
let mut current_time = get_sec();
|
||||
let mut current_time = time_in_seconds();
|
||||
let (_, total_delta) = get_delta(&self.config, ¤t_time);
|
||||
|
||||
trace!("Total delta on list init: {total_delta}");
|
||||
|
||||
let out = if DUMMY_LEN > total_delta {
|
||||
total_delta
|
||||
} else {
|
||||
DUMMY_LEN
|
||||
};
|
||||
|
||||
let duration = out + 0.001;
|
||||
|
||||
if self.json_path.is_some() {
|
||||
// When playlist is missing, we always need to init the playlist the next iteration.
|
||||
self.playout_stat.list_init.store(true, Ordering::SeqCst);
|
||||
if self.start_sec > current_time {
|
||||
current_time += self.end_sec + 1.0;
|
||||
}
|
||||
|
||||
if self.config.playlist.start_sec.unwrap() > current_time {
|
||||
current_time += self.config.playlist.length_sec.unwrap() + 1.0;
|
||||
let mut last_index = 0;
|
||||
let length = self.player_control.current_list.lock().unwrap().len();
|
||||
|
||||
if length > 0 {
|
||||
last_index = length - 1;
|
||||
}
|
||||
|
||||
let mut nodes = self.player_control.current_list.lock().unwrap();
|
||||
let index = nodes.len();
|
||||
|
||||
let mut media = Media::new(index, "", false);
|
||||
let mut media = Media::new(length, "", false);
|
||||
media.begin = Some(current_time);
|
||||
media.duration = duration;
|
||||
media.out = out;
|
||||
media.duration = total_delta;
|
||||
media.out = total_delta;
|
||||
|
||||
self.current_node = gen_source(
|
||||
&self.config,
|
||||
@ -452,12 +375,6 @@ impl Iterator for CurrentProgram {
|
||||
&self.player_control,
|
||||
last_index,
|
||||
);
|
||||
|
||||
nodes.push(self.current_node.clone());
|
||||
self.player_control
|
||||
.current_index
|
||||
.store(nodes.len(), Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
self.last_next_ad();
|
||||
@ -468,7 +385,8 @@ impl Iterator for CurrentProgram {
|
||||
if self.player_control.current_index.load(Ordering::SeqCst)
|
||||
< self.player_control.current_list.lock().unwrap().len()
|
||||
{
|
||||
self.check_for_next_playlist();
|
||||
// get next clip from current playlist
|
||||
|
||||
let mut is_last = false;
|
||||
let index = self.player_control.current_index.load(Ordering::SeqCst);
|
||||
let node_list = self.player_control.current_list.lock().unwrap();
|
||||
@ -496,71 +414,37 @@ impl Iterator for CurrentProgram {
|
||||
|
||||
Some(self.current_node.clone())
|
||||
} else {
|
||||
let last_playlist = self.json_path.clone();
|
||||
let last_ad = self.current_node.last_ad;
|
||||
self.check_for_next_playlist();
|
||||
let (_, total_delta) =
|
||||
get_delta(&self.config, &self.config.playlist.start_sec.unwrap());
|
||||
let (_, total_delta) = get_delta(&self.config, &self.start_sec);
|
||||
|
||||
if !self.config.playlist.infinit
|
||||
&& last_playlist == self.json_path
|
||||
&& self.last_json_path == self.json_path
|
||||
&& total_delta.abs() > 1.0
|
||||
{
|
||||
trace!("Total delta on list end: {total_delta}");
|
||||
|
||||
// Playlist is to early finish,
|
||||
// and if we have to fill it with a placeholder.
|
||||
let index = self.player_control.current_index.load(Ordering::SeqCst);
|
||||
self.current_node = Media::new(index, "", false);
|
||||
self.current_node.begin = Some(get_sec());
|
||||
trace!("Total delta on list end: {total_delta}");
|
||||
|
||||
let out = if DUMMY_LEN > total_delta {
|
||||
total_delta
|
||||
} else {
|
||||
DUMMY_LEN
|
||||
};
|
||||
|
||||
let duration = out + 0.001;
|
||||
|
||||
self.current_node.duration = duration;
|
||||
self.current_node.out = out;
|
||||
self.current_node = gen_source(
|
||||
&self.config,
|
||||
self.current_node.clone(),
|
||||
&self.playout_stat,
|
||||
&self.player_control,
|
||||
0,
|
||||
);
|
||||
self.player_control
|
||||
.current_list
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(self.current_node.clone());
|
||||
self.last_next_ad();
|
||||
|
||||
self.current_node.last_ad = last_ad;
|
||||
self.current_node
|
||||
.add_filter(&self.config, &self.playout_stat.chain);
|
||||
|
||||
self.player_control
|
||||
.current_index
|
||||
.fetch_add(1, Ordering::SeqCst);
|
||||
self.fill_end(total_delta);
|
||||
|
||||
return Some(self.current_node.clone());
|
||||
}
|
||||
|
||||
// Get first clip from next playlist.
|
||||
|
||||
let c_list = self.player_control.current_list.lock().unwrap();
|
||||
let first_node = c_list[0].clone();
|
||||
|
||||
drop(c_list);
|
||||
|
||||
self.player_control.current_index.store(0, Ordering::SeqCst);
|
||||
self.current_node = gen_source(
|
||||
&self.config,
|
||||
self.player_control.current_list.lock().unwrap()[0].clone(),
|
||||
first_node,
|
||||
&self.playout_stat,
|
||||
&self.player_control,
|
||||
0,
|
||||
);
|
||||
self.last_next_ad();
|
||||
self.current_node.last_ad = last_ad;
|
||||
|
||||
self.current_node.last_ad = self.last_node_ad;
|
||||
self.player_control.current_index.store(1, Ordering::SeqCst);
|
||||
|
||||
Some(self.current_node.clone())
|
||||
@ -650,19 +534,7 @@ pub fn gen_source(
|
||||
warn!("Clip is less then 1 second long (<yellow>{duration:.3}</>), adjust length.");
|
||||
|
||||
duration = 1.2;
|
||||
}
|
||||
|
||||
trace!("Clip out: {duration}, duration: {}", node.duration);
|
||||
|
||||
if node.probe.is_none() && !node.source.is_empty() {
|
||||
node.add_probe(true);
|
||||
} else {
|
||||
trace!("Node has a probe...")
|
||||
}
|
||||
|
||||
// separate if condition, because of node.add_probe() in last condition
|
||||
if node.probe.is_some() {
|
||||
if node.out - node.seek < 1.0 {
|
||||
if node.seek > 1.0 {
|
||||
node.seek -= 1.0;
|
||||
} else {
|
||||
@ -670,6 +542,18 @@ pub fn gen_source(
|
||||
}
|
||||
}
|
||||
|
||||
trace!("Clip new length: {duration}, duration: {}", node.duration);
|
||||
|
||||
if node.probe.is_none() && !node.source.is_empty() {
|
||||
if let Err(e) = node.add_probe(true) {
|
||||
trace!("{e:?}");
|
||||
};
|
||||
} else {
|
||||
trace!("Node has a probe...")
|
||||
}
|
||||
|
||||
// separate if condition, because of node.add_probe() in last condition
|
||||
if node.probe.is_some() {
|
||||
if node
|
||||
.source
|
||||
.rsplit_once('.')
|
||||
@ -686,29 +570,36 @@ pub fn gen_source(
|
||||
|
||||
// Last index is the index from the last item from the node list.
|
||||
if node_index < last_index {
|
||||
error!("Source not found: <b><magenta>\"{}\"</></b>", node.source);
|
||||
error!("Source not found: <b><magenta>{}</></b>", node.source);
|
||||
}
|
||||
|
||||
let filler_source = &config.storage.filler;
|
||||
let mut filler_list = vec![];
|
||||
|
||||
if filler_source.is_dir() && !player_control.filler_list.lock().unwrap().is_empty() {
|
||||
match player_control.filler_list.try_lock() {
|
||||
Ok(list) => filler_list = list.to_vec(),
|
||||
Err(e) => error!("Lock filler list error: {e}"),
|
||||
}
|
||||
|
||||
if config.storage.filler.is_dir() && !filler_list.is_empty() {
|
||||
let filler_index = player_control.filler_index.fetch_add(1, Ordering::SeqCst);
|
||||
let mut filler_media = player_control.filler_list.lock().unwrap()[filler_index].clone();
|
||||
let mut filler_media = filler_list[filler_index].clone();
|
||||
|
||||
trace!("take filler: {}", filler_media.source);
|
||||
|
||||
// Set list_init to true, to stay in sync.
|
||||
playout_stat.list_init.store(true, Ordering::SeqCst);
|
||||
|
||||
if filler_index == player_control.filler_list.lock().unwrap().len() - 1 {
|
||||
if filler_index == filler_list.len() - 1 {
|
||||
player_control.filler_index.store(0, Ordering::SeqCst)
|
||||
}
|
||||
|
||||
if filler_media.probe.is_none() {
|
||||
filler_media.add_probe(false);
|
||||
if let Err(e) = filler_media.add_probe(false) {
|
||||
error!("{e:?}");
|
||||
};
|
||||
}
|
||||
|
||||
if node.duration > duration && filler_media.duration > duration {
|
||||
if filler_media.duration > duration {
|
||||
filler_media.out = duration;
|
||||
}
|
||||
|
||||
@ -741,8 +632,15 @@ pub fn gen_source(
|
||||
.and_then(|d| d.parse::<f64>().ok())
|
||||
{
|
||||
// Create placeholder from config filler.
|
||||
let mut filler_out = filler_duration;
|
||||
|
||||
if filler_duration > duration {
|
||||
filler_out = duration;
|
||||
}
|
||||
|
||||
node.source = config.storage.filler.clone().to_string_lossy().to_string();
|
||||
node.out = duration;
|
||||
node.seek = 0.0;
|
||||
node.out = filler_out;
|
||||
node.duration = filler_duration;
|
||||
node.cmd = Some(loop_filler(&node));
|
||||
node.probe = Some(probe);
|
||||
@ -754,10 +652,19 @@ pub fn gen_source(
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
|
||||
// Create colored placeholder.
|
||||
let (source, cmd) = gen_dummy(config, duration);
|
||||
error!("Filler error: {e}");
|
||||
|
||||
let mut dummy_duration = 60.0;
|
||||
|
||||
if dummy_duration > duration {
|
||||
dummy_duration = duration;
|
||||
}
|
||||
|
||||
let (source, cmd) = gen_dummy(config, dummy_duration);
|
||||
node.seek = 0.0;
|
||||
node.out = dummy_duration;
|
||||
node.duration = dummy_duration;
|
||||
node.source = source;
|
||||
node.cmd = Some(cmd);
|
||||
}
|
||||
@ -776,7 +683,7 @@ pub fn gen_source(
|
||||
"return gen_source: {}, seek: {}, out: {}",
|
||||
node.source,
|
||||
node.seek,
|
||||
node.out
|
||||
node.out,
|
||||
);
|
||||
|
||||
node
|
||||
|
@ -212,7 +212,10 @@ fn control_back(
|
||||
let mut data_map = Map::new();
|
||||
let mut media = current_list[index - 2].clone();
|
||||
play_control.current_index.fetch_sub(2, Ordering::SeqCst);
|
||||
media.add_probe(false);
|
||||
|
||||
if let Err(e) = media.add_probe(false) {
|
||||
error!("{e:?}");
|
||||
};
|
||||
|
||||
let (delta, _) = get_delta(config, &media.begin.unwrap_or(0.0));
|
||||
*time_shift = delta;
|
||||
@ -258,7 +261,10 @@ fn control_next(
|
||||
|
||||
let mut data_map = Map::new();
|
||||
let mut media = current_list[index].clone();
|
||||
media.add_probe(false);
|
||||
|
||||
if let Err(e) = media.add_probe(false) {
|
||||
error!("{e:?}");
|
||||
};
|
||||
|
||||
let (delta, _) = get_delta(config, &media.begin.unwrap_or(0.0));
|
||||
*time_shift = delta;
|
||||
|
@ -14,7 +14,7 @@ pub use arg_parse::Args;
|
||||
use ffplayout_lib::{
|
||||
filter::Filters,
|
||||
utils::{
|
||||
config::Template, errors::ProcError, get_sec, parse_log_level_filter, sec_to_time,
|
||||
config::Template, errors::ProcError, parse_log_level_filter, sec_to_time, time_in_seconds,
|
||||
time_to_sec, Media, OutputMode::*, PlayoutConfig, PlayoutStatus, ProcessMode::*,
|
||||
},
|
||||
vec_strings,
|
||||
@ -254,7 +254,7 @@ pub fn get_data_map(
|
||||
server_is_running: bool,
|
||||
) -> Map<String, Value> {
|
||||
let mut data_map = Map::new();
|
||||
let current_time = get_sec();
|
||||
let current_time = time_in_seconds();
|
||||
let shift = *playout_stat.time_shift.lock().unwrap();
|
||||
let begin = media.begin.unwrap_or(0.0) - shift;
|
||||
|
||||
|
@ -304,11 +304,11 @@ fn overlay(node: &mut Media, chain: &mut Filters, config: &PlayoutConfig) {
|
||||
config.processing.logo.replace('\\', "/").replace(':', "\\\\:"), config.processing.logo_opacity, config.processing.logo_filter
|
||||
);
|
||||
|
||||
if node.last_ad.unwrap_or(false) {
|
||||
if node.last_ad {
|
||||
logo_chain.push_str(",fade=in:st=0:d=1.0:alpha=1")
|
||||
}
|
||||
|
||||
if node.next_ad.unwrap_or(false) {
|
||||
if node.next_ad {
|
||||
logo_chain.push_str(&format!(
|
||||
",fade=out:st={}:d=1.0:alpha=1",
|
||||
node.out - node.seek - 1.0
|
||||
|
@ -9,7 +9,7 @@ use simplelog::*;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::utils::{
|
||||
controller::PlayerControl, get_sec, include_file_extension, Media, PlayoutConfig,
|
||||
controller::PlayerControl, include_file_extension, time_in_seconds, Media, PlayoutConfig,
|
||||
};
|
||||
|
||||
/// Folder Sources
|
||||
@ -136,10 +136,10 @@ impl Iterator for FolderSource {
|
||||
{
|
||||
let i = self.player_control.current_index.load(Ordering::SeqCst);
|
||||
self.current_node = self.player_control.current_list.lock().unwrap()[i].clone();
|
||||
self.current_node.add_probe(false);
|
||||
let _ = self.current_node.add_probe(false).ok();
|
||||
self.current_node
|
||||
.add_filter(&self.config, &self.filter_chain);
|
||||
self.current_node.begin = Some(get_sec());
|
||||
self.current_node.begin = Some(time_in_seconds());
|
||||
|
||||
self.player_control
|
||||
.current_index
|
||||
@ -162,10 +162,10 @@ impl Iterator for FolderSource {
|
||||
}
|
||||
|
||||
self.current_node = self.player_control.current_list.lock().unwrap()[0].clone();
|
||||
self.current_node.add_probe(false);
|
||||
let _ = self.current_node.add_probe(false).ok();
|
||||
self.current_node
|
||||
.add_filter(&self.config, &self.filter_chain);
|
||||
self.current_node.begin = Some(get_sec());
|
||||
self.current_node.begin = Some(time_in_seconds());
|
||||
|
||||
self.player_control.current_index.store(1, Ordering::SeqCst);
|
||||
|
||||
@ -192,7 +192,9 @@ pub fn fill_filler_list(
|
||||
let mut media = Media::new(index, &entry.path().to_string_lossy(), false);
|
||||
|
||||
if player_control.is_none() {
|
||||
media.add_probe(false);
|
||||
if let Err(e) = media.add_probe(false) {
|
||||
error!("{e:?}");
|
||||
};
|
||||
}
|
||||
|
||||
filler_list.push(media);
|
||||
@ -217,7 +219,9 @@ pub fn fill_filler_list(
|
||||
let mut media = Media::new(0, &config.storage.filler.to_string_lossy(), false);
|
||||
|
||||
if player_control.is_none() {
|
||||
media.add_probe(false);
|
||||
if let Err(e) = media.add_probe(false) {
|
||||
error!("{e:?}");
|
||||
};
|
||||
}
|
||||
|
||||
filler_list.push(media);
|
||||
|
@ -73,8 +73,8 @@ fn set_defaults(
|
||||
for (i, item) in playlist.program.iter_mut().enumerate() {
|
||||
item.begin = Some(start_sec);
|
||||
item.index = Some(i);
|
||||
item.last_ad = Some(false);
|
||||
item.next_ad = Some(false);
|
||||
item.last_ad = false;
|
||||
item.next_ad = false;
|
||||
item.process = Some(true);
|
||||
item.filter = None;
|
||||
|
||||
@ -115,8 +115,8 @@ fn loop_playlist(
|
||||
probe_audio: item.probe_audio.clone(),
|
||||
process: Some(true),
|
||||
unit: Decoder,
|
||||
last_ad: Some(false),
|
||||
next_ad: Some(false),
|
||||
last_ad: false,
|
||||
next_ad: false,
|
||||
filter: None,
|
||||
custom_filter: String::new(),
|
||||
};
|
||||
|
@ -173,9 +173,17 @@ pub fn validate_playlist(
|
||||
let pos = index + 1;
|
||||
|
||||
if item.audio.is_empty() {
|
||||
item.add_probe(false);
|
||||
} else {
|
||||
item.add_probe(true);
|
||||
if let Err(e) = item.add_probe(false) {
|
||||
error!(
|
||||
"[Validation] Error on position <yellow>{pos:0>3}</> <yellow>{}</>: {e}",
|
||||
sec_to_time(begin)
|
||||
);
|
||||
}
|
||||
} else if let Err(e) = item.add_probe(true) {
|
||||
error!(
|
||||
"[Validation] Error on position <yellow>{pos:0>3}</> <yellow>{}</>: {e}",
|
||||
sec_to_time(begin)
|
||||
);
|
||||
}
|
||||
|
||||
if item.probe.is_some() {
|
||||
@ -183,7 +191,7 @@ pub fn validate_playlist(
|
||||
error!("{e}");
|
||||
} else if config.general.validate {
|
||||
debug!(
|
||||
"Source at <yellow>{}</>, seems fine: <b><magenta>{}</></b>",
|
||||
"[Validation] Source at <yellow>{}</>, seems fine: <b><magenta>{}</></b>",
|
||||
sec_to_time(begin),
|
||||
item.source
|
||||
)
|
||||
@ -200,7 +208,7 @@ pub fn validate_playlist(
|
||||
|
||||
if !is_close(o.duration, probe_duration, 1.2) {
|
||||
error!(
|
||||
"File duration (at: <yellow>{}</>) differs from playlist value. File duration: <yellow>{}</>, playlist value: <yellow>{}</>, source <b><magenta>{}</></b>",
|
||||
"[Validation] File duration (at: <yellow>{}</>) differs from playlist value. File duration: <yellow>{}</>, playlist value: <yellow>{}</>, source <b><magenta>{}</></b>",
|
||||
sec_to_time(o.begin.unwrap_or_default()), sec_to_time(probe_duration), sec_to_time(o.duration), o.source
|
||||
);
|
||||
|
||||
@ -214,12 +222,6 @@ pub fn validate_playlist(
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
error!(
|
||||
"Error on position <yellow>{pos:0>3}</> <yellow>{}</>, file: <b><magenta>{}</></b>",
|
||||
sec_to_time(begin),
|
||||
item.source
|
||||
);
|
||||
}
|
||||
|
||||
begin += item.out - item.seek;
|
||||
@ -227,7 +229,7 @@ pub fn validate_playlist(
|
||||
|
||||
if !config.playlist.infinit && length > begin + 1.2 {
|
||||
error!(
|
||||
"Playlist from <yellow>{date}</> not long enough, <yellow>{}</> needed!",
|
||||
"[Validation] Playlist from <yellow>{date}</> not long enough, <yellow>{}</> needed!",
|
||||
sec_to_time(length - begin),
|
||||
);
|
||||
}
|
||||
|
@ -104,10 +104,10 @@ pub struct Media {
|
||||
pub probe_audio: Option<MediaProbe>,
|
||||
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub last_ad: Option<bool>,
|
||||
pub last_ad: bool,
|
||||
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub next_ad: Option<bool>,
|
||||
pub next_ad: bool,
|
||||
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub process: Option<bool>,
|
||||
@ -122,8 +122,7 @@ impl Media {
|
||||
let mut probe = None;
|
||||
|
||||
if do_probe && (is_remote(src) || Path::new(src).is_file()) {
|
||||
match MediaProbe::new(src) {
|
||||
Ok(p) => {
|
||||
if let Ok(p) = MediaProbe::new(src) {
|
||||
probe = Some(p.clone());
|
||||
|
||||
duration = p
|
||||
@ -133,8 +132,6 @@ impl Media {
|
||||
.parse()
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Err(e) => error!("{e}"),
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
@ -152,14 +149,16 @@ impl Media {
|
||||
custom_filter: String::new(),
|
||||
probe,
|
||||
probe_audio: None,
|
||||
last_ad: Some(false),
|
||||
next_ad: Some(false),
|
||||
last_ad: false,
|
||||
next_ad: false,
|
||||
process: Some(true),
|
||||
unit: Decoder,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_probe(&mut self, check_audio: bool) {
|
||||
pub fn add_probe(&mut self, check_audio: bool) -> Result<(), String> {
|
||||
let mut errors = vec![];
|
||||
|
||||
if self.probe.is_none() {
|
||||
match MediaProbe::new(&self.source) {
|
||||
Ok(probe) => {
|
||||
@ -178,7 +177,7 @@ impl Media {
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => error!("{e}"),
|
||||
Err(e) => errors.push(e.to_string()),
|
||||
};
|
||||
|
||||
if check_audio && Path::new(&self.audio).is_file() {
|
||||
@ -194,10 +193,16 @@ impl Media {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
Err(e) => error!("{e}"),
|
||||
Err(e) => errors.push(e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
return Err(errors.join(", "));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_filter(
|
||||
@ -274,7 +279,9 @@ impl MediaProbe {
|
||||
}
|
||||
Err(e) => {
|
||||
if !Path::new(input).is_file() && !is_remote(input) {
|
||||
Err(ProcError::Custom(format!("File '{input}' not exist!")))
|
||||
Err(ProcError::Custom(format!(
|
||||
"File <b><magenta>{input}</></b> not exist!"
|
||||
)))
|
||||
} else {
|
||||
Err(ProcError::Ffprobe(e))
|
||||
}
|
||||
@ -337,7 +344,7 @@ pub fn write_status(config: &PlayoutConfig, date: &str, shift: f64) {
|
||||
// }
|
||||
|
||||
/// Get current time in seconds.
|
||||
pub fn get_sec() -> f64 {
|
||||
pub fn time_in_seconds() -> f64 {
|
||||
let local: DateTime<Local> = time_now();
|
||||
|
||||
(local.hour() * 3600 + local.minute() * 60 + local.second()) as f64
|
||||
@ -351,11 +358,11 @@ pub fn get_sec() -> f64 {
|
||||
pub fn get_date(seek: bool, start: f64, get_next: bool) -> String {
|
||||
let local: DateTime<Local> = time_now();
|
||||
|
||||
if seek && start > get_sec() {
|
||||
if seek && start > time_in_seconds() {
|
||||
return (local - Duration::days(1)).format("%Y-%m-%d").to_string();
|
||||
}
|
||||
|
||||
if start == 0.0 && get_next && get_sec() > 86397.9 {
|
||||
if start == 0.0 && get_next && time_in_seconds() > 86397.9 {
|
||||
return (local + Duration::days(1)).format("%Y-%m-%d").to_string();
|
||||
}
|
||||
|
||||
@ -401,7 +408,7 @@ pub fn modified_time(path: &str) -> Option<String> {
|
||||
/// Convert a formatted time string to seconds.
|
||||
pub fn time_to_sec(time_str: &str) -> f64 {
|
||||
if matches!(time_str, "now" | "" | "none") || !time_str.contains(':') {
|
||||
return get_sec();
|
||||
return time_in_seconds();
|
||||
}
|
||||
|
||||
let t: Vec<&str> = time_str.split(':').collect();
|
||||
@ -452,7 +459,7 @@ pub fn sum_durations(clip_list: &Vec<Media>) -> f64 {
|
||||
///
|
||||
/// We also get here the global delta between clip start and time when a new playlist should start.
|
||||
pub fn get_delta(config: &PlayoutConfig, begin: &f64) -> (f64, f64) {
|
||||
let mut current_time = get_sec();
|
||||
let mut current_time = time_in_seconds();
|
||||
let start = config.playlist.start_sec.unwrap();
|
||||
let length = time_to_sec(&config.playlist.length);
|
||||
let mut target_length = 86400.0;
|
||||
|
BIN
tests/assets/media_filler/filler_0.mp4
Normal file
BIN
tests/assets/media_filler/filler_0.mp4
Normal file
Binary file not shown.
BIN
tests/assets/media_filler/filler_1.mp4
Normal file
BIN
tests/assets/media_filler/filler_1.mp4
Normal file
Binary file not shown.
Before Width: | Height: | Size: 202 KiB After Width: | Height: | Size: 202 KiB |
BIN
tests/assets/media_sorted/Aqua_00-00-30.mp4
Normal file
BIN
tests/assets/media_sorted/Aqua_00-00-30.mp4
Normal file
Binary file not shown.
BIN
tests/assets/media_sorted/Cornsilk_00-00-50.mp4
Normal file
BIN
tests/assets/media_sorted/Cornsilk_00-00-50.mp4
Normal file
Binary file not shown.
BIN
tests/assets/media_sorted/Cyan_00-00-30.mp4
Normal file
BIN
tests/assets/media_sorted/Cyan_00-00-30.mp4
Normal file
Binary file not shown.
BIN
tests/assets/media_sorted/DarkGray_00-00-10.mp4
Normal file
BIN
tests/assets/media_sorted/DarkGray_00-00-10.mp4
Normal file
Binary file not shown.
BIN
tests/assets/media_sorted/DarkOrchid_00-00-25.mp4
Normal file
BIN
tests/assets/media_sorted/DarkOrchid_00-00-25.mp4
Normal file
Binary file not shown.
BIN
tests/assets/media_sorted/ForestGreen_01-00-00.mp4
Normal file
BIN
tests/assets/media_sorted/ForestGreen_01-00-00.mp4
Normal file
Binary file not shown.
BIN
tests/assets/media_sorted/IndianRed_01-00-00.mp4
Normal file
BIN
tests/assets/media_sorted/IndianRed_01-00-00.mp4
Normal file
Binary file not shown.
BIN
tests/assets/media_sorted/Indigo_00-00-15.mp4
Normal file
BIN
tests/assets/media_sorted/Indigo_00-00-15.mp4
Normal file
Binary file not shown.
BIN
tests/assets/media_sorted/LightGoldenRodYellow_00-00-20.mp4
Normal file
BIN
tests/assets/media_sorted/LightGoldenRodYellow_00-00-20.mp4
Normal file
Binary file not shown.
BIN
tests/assets/media_sorted/LightSeaGreen_00-00-15.mp4
Normal file
BIN
tests/assets/media_sorted/LightSeaGreen_00-00-15.mp4
Normal file
Binary file not shown.
BIN
tests/assets/media_sorted/MediumOrchid_00-30-00.mp4
Normal file
BIN
tests/assets/media_sorted/MediumOrchid_00-30-00.mp4
Normal file
Binary file not shown.
BIN
tests/assets/media_sorted/MediumSeaGreen_00-25-00.mp4
Normal file
BIN
tests/assets/media_sorted/MediumSeaGreen_00-25-00.mp4
Normal file
Binary file not shown.
BIN
tests/assets/media_sorted/Olive_00-00-30.mp4
Normal file
BIN
tests/assets/media_sorted/Olive_00-00-30.mp4
Normal file
Binary file not shown.
BIN
tests/assets/media_sorted/Orange_00-00-45.mp4
Normal file
BIN
tests/assets/media_sorted/Orange_00-00-45.mp4
Normal file
Binary file not shown.
BIN
tests/assets/media_sorted/Plum_01-00-00.mp4
Normal file
BIN
tests/assets/media_sorted/Plum_01-00-00.mp4
Normal file
Binary file not shown.
BIN
tests/assets/media_sorted/Yellow_00-00-30.mp4
Normal file
BIN
tests/assets/media_sorted/Yellow_00-00-30.mp4
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
294
tests/assets/playlists/2024/02/2024-02-01.json
Normal file
294
tests/assets/playlists/2024/02/2024-02-01.json
Normal file
@ -0,0 +1,294 @@
|
||||
{
|
||||
"channel": "Test 1",
|
||||
"date": "2024-02-01",
|
||||
"program": [
|
||||
{
|
||||
"in": 0,
|
||||
"out": 10.0,
|
||||
"duration": 10.0,
|
||||
"source": "tests/assets/media_sorted/DarkGray_00-00-10.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Olive_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 15.0,
|
||||
"duration": 15.0,
|
||||
"source": "tests/assets/media_sorted/Indigo_00-00-15.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 25.0,
|
||||
"duration": 25.0,
|
||||
"source": "tests/assets/media_sorted/DarkOrchid_00-00-25.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 45.0,
|
||||
"duration": 45.0,
|
||||
"source": "tests/assets/media_sorted/Orange_00-00-45.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 20.0,
|
||||
"duration": 20.0,
|
||||
"source": "tests/assets/media_sorted/LightGoldenRodYellow_00-00-20.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Cyan_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 50.0,
|
||||
"duration": 50.0,
|
||||
"source": "tests/assets/media_sorted/Cornsilk_00-00-50.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 15.0,
|
||||
"duration": 15.0,
|
||||
"source": "tests/assets/media_sorted/LightSeaGreen_00-00-15.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Yellow_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Aqua_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 1500.0,
|
||||
"duration": 1500.0,
|
||||
"source": "tests/assets/media_sorted/MediumSeaGreen_00-25-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 1800.0,
|
||||
"duration": 1800.0,
|
||||
"source": "tests/assets/media_sorted/MediumOrchid_00-30-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 1800.0,
|
||||
"duration": 1800.0,
|
||||
"source": "tests/assets/media_sorted/MediumOrchid_00-30-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 1500.0,
|
||||
"duration": 1500.0,
|
||||
"source": "tests/assets/media_sorted/MediumSeaGreen_00-25-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 10.0,
|
||||
"duration": 10.0,
|
||||
"source": "tests/assets/media_sorted/DarkGray_00-00-10.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Olive_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 15.0,
|
||||
"duration": 15.0,
|
||||
"source": "tests/assets/media_sorted/Indigo_00-00-15.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 25.0,
|
||||
"duration": 25.0,
|
||||
"source": "tests/assets/media_sorted/DarkOrchid_00-00-25.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 45.0,
|
||||
"duration": 45.0,
|
||||
"source": "tests/assets/media_sorted/Orange_00-00-45.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 20.0,
|
||||
"duration": 20.0,
|
||||
"source": "tests/assets/media_sorted/LightGoldenRodYellow_00-00-20.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Cyan_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 50.0,
|
||||
"duration": 50.0,
|
||||
"source": "tests/assets/media_sorted/Cornsilk_00-00-50.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 15.0,
|
||||
"duration": 15.0,
|
||||
"source": "tests/assets/media_sorted/LightSeaGreen_00-00-15.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Yellow_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Aqua_00-00-30.mp4"
|
||||
}
|
||||
]
|
||||
}
|
294
tests/assets/playlists/2024/02/2024-02-02.json
Normal file
294
tests/assets/playlists/2024/02/2024-02-02.json
Normal file
@ -0,0 +1,294 @@
|
||||
{
|
||||
"channel": "Test 1",
|
||||
"date": "2024-02-01",
|
||||
"program": [
|
||||
{
|
||||
"in": 0,
|
||||
"out": 10.0,
|
||||
"duration": 10.0,
|
||||
"source": "tests/assets/media_sorted/DarkGray_00-00-10.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Olive_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 15.0,
|
||||
"duration": 15.0,
|
||||
"source": "tests/assets/media_sorted/Indigo_00-00-15.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 25.0,
|
||||
"duration": 25.0,
|
||||
"source": "tests/assets/media_sorted/DarkOrchid_00-00-25.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 45.0,
|
||||
"duration": 45.0,
|
||||
"source": "tests/assets/media_sorted/Orange_00-00-45.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 20.0,
|
||||
"duration": 20.0,
|
||||
"source": "tests/assets/media_sorted/LightGoldenRodYellow_00-00-20.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Cyan_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 50.0,
|
||||
"duration": 50.0,
|
||||
"source": "tests/assets/media_sorted/Cornsilk_00-00-50.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 15.0,
|
||||
"duration": 15.0,
|
||||
"source": "tests/assets/media_sorted/LightSeaGreen_00-00-15.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Yellow_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Aqua_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 1500.0,
|
||||
"duration": 1500.0,
|
||||
"source": "tests/assets/media_sorted/MediumSeaGreen_00-25-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 1800.0,
|
||||
"duration": 1800.0,
|
||||
"source": "tests/assets/media_sorted/MediumOrchid_00-30-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 1800.0,
|
||||
"duration": 1800.0,
|
||||
"source": "tests/assets/media_sorted/MediumOrchid_00-30-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 1500.0,
|
||||
"duration": 1500.0,
|
||||
"source": "tests/assets/media_sorted/MediumSeaGreen_00-25-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 10.0,
|
||||
"duration": 10.0,
|
||||
"source": "tests/assets/media_sorted/DarkGray_00-00-10.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Olive_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 15.0,
|
||||
"duration": 15.0,
|
||||
"source": "tests/assets/media_sorted/Indigo_00-00-15.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 25.0,
|
||||
"duration": 25.0,
|
||||
"source": "tests/assets/media_sorted/DarkOrchid_00-00-25.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 45.0,
|
||||
"duration": 45.0,
|
||||
"source": "tests/assets/media_sorted/Orange_00-00-45.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 20.0,
|
||||
"duration": 20.0,
|
||||
"source": "tests/assets/media_sorted/LightGoldenRodYellow_00-00-20.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Cyan_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 50.0,
|
||||
"duration": 50.0,
|
||||
"source": "tests/assets/media_sorted/Cornsilk_00-00-50.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 15.0,
|
||||
"duration": 15.0,
|
||||
"source": "tests/assets/media_sorted/LightSeaGreen_00-00-15.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Yellow_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Aqua_00-00-30.mp4"
|
||||
}
|
||||
]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -6,50 +6,50 @@
|
||||
"in": 0.0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/av_sync.mp4"
|
||||
"source": "tests/assets/media_mix/av_sync.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0.0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/dual_audio.mp4"
|
||||
"source": "tests/assets/media_mix/dual_audio.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0.0,
|
||||
"out": 10.0,
|
||||
"duration": 10.0,
|
||||
"source": "tests/assets/short_video.mp4"
|
||||
"source": "tests/assets/media_mix/short_video.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0.0,
|
||||
"out": 10.0,
|
||||
"duration": 10.0,
|
||||
"source": "tests/assets/still.jpg"
|
||||
"source": "tests/assets/media_mix/still.jpg"
|
||||
},
|
||||
{
|
||||
"in": 0.0,
|
||||
"out": 10.0,
|
||||
"duration": 10.0,
|
||||
"source": "tests/assets/short_audio.mp4"
|
||||
"source": "tests/assets/media_mix/short_audio.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0.0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/no_audio.mp4"
|
||||
"source": "tests/assets/media_mix/no_audio.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0.0,
|
||||
"out": 10.0,
|
||||
"duration": 10.0,
|
||||
"source": "tests/assets/still.jpg",
|
||||
"audio": "tests/assets/audio.mp3"
|
||||
"source": "tests/assets/media_mix/still.jpg",
|
||||
"audio": "tests/assets/media_mix/audio.mp3"
|
||||
},
|
||||
{
|
||||
"in": 0.0,
|
||||
"out": 25.0,
|
||||
"duration": 25.0,
|
||||
"source": "tests/assets/ad.mp4",
|
||||
"source": "tests/assets/media_mix/ad.mp4",
|
||||
"category": "advertisement"
|
||||
}
|
||||
]
|
||||
|
294
tests/assets/playlists/playlist_sorted.json
Normal file
294
tests/assets/playlists/playlist_sorted.json
Normal file
@ -0,0 +1,294 @@
|
||||
{
|
||||
"channel": "Test 1",
|
||||
"date": "2024-02-01",
|
||||
"program": [
|
||||
{
|
||||
"in": 0,
|
||||
"out": 10.0,
|
||||
"duration": 10.0,
|
||||
"source": "tests/assets/media_sorted/DarkGray_00-00-10.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Olive_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 15.0,
|
||||
"duration": 15.0,
|
||||
"source": "tests/assets/media_sorted/Indigo_00-00-15.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 25.0,
|
||||
"duration": 25.0,
|
||||
"source": "tests/assets/media_sorted/DarkOrchid_00-00-25.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 45.0,
|
||||
"duration": 45.0,
|
||||
"source": "tests/assets/media_sorted/Orange_00-00-45.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 20.0,
|
||||
"duration": 20.0,
|
||||
"source": "tests/assets/media_sorted/LightGoldenRodYellow_00-00-20.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Cyan_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 50.0,
|
||||
"duration": 50.0,
|
||||
"source": "tests/assets/media_sorted/Cornsilk_00-00-50.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 15.0,
|
||||
"duration": 15.0,
|
||||
"source": "tests/assets/media_sorted/LightSeaGreen_00-00-15.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Yellow_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Aqua_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 1500.0,
|
||||
"duration": 1500.0,
|
||||
"source": "tests/assets/media_sorted/MediumSeaGreen_00-25-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 1800.0,
|
||||
"duration": 1800.0,
|
||||
"source": "tests/assets/media_sorted/MediumOrchid_00-30-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 3600.0,
|
||||
"duration": 3600.0,
|
||||
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 1800.0,
|
||||
"duration": 1800.0,
|
||||
"source": "tests/assets/media_sorted/MediumOrchid_00-30-00.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 10.0,
|
||||
"duration": 10.0,
|
||||
"source": "tests/assets/media_sorted/DarkGray_00-00-10.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Olive_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 15.0,
|
||||
"duration": 15.0,
|
||||
"source": "tests/assets/media_sorted/Indigo_00-00-15.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 25.0,
|
||||
"duration": 25.0,
|
||||
"source": "tests/assets/media_sorted/DarkOrchid_00-00-25.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 45.0,
|
||||
"duration": 45.0,
|
||||
"source": "tests/assets/media_sorted/Orange_00-00-45.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 20.0,
|
||||
"duration": 20.0,
|
||||
"source": "tests/assets/media_sorted/LightGoldenRodYellow_00-00-20.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Cyan_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 50.0,
|
||||
"duration": 50.0,
|
||||
"source": "tests/assets/media_sorted/Cornsilk_00-00-50.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 15.0,
|
||||
"duration": 15.0,
|
||||
"source": "tests/assets/media_sorted/LightSeaGreen_00-00-15.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Yellow_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 30.0,
|
||||
"duration": 30.0,
|
||||
"source": "tests/assets/media_sorted/Aqua_00-00-30.mp4"
|
||||
},
|
||||
{
|
||||
"in": 0,
|
||||
"out": 1500.0,
|
||||
"duration": 1500.0,
|
||||
"source": "tests/assets/media_sorted/MediumSeaGreen_00-25-00.mp4"
|
||||
}
|
||||
]
|
||||
}
|
@ -16,7 +16,7 @@ fn video_audio_input() {
|
||||
let logo_path = fs::canonicalize("./assets/logo.png").unwrap();
|
||||
config.processing.logo = logo_path.to_string_lossy().to_string();
|
||||
|
||||
let media_obj = Media::new(0, "./assets/with_audio.mp4", true);
|
||||
let media_obj = Media::new(0, "./assets/media_mix/with_audio.mp4", true);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let test_filter_cmd =
|
||||
@ -29,7 +29,7 @@ fn video_audio_input() {
|
||||
|
||||
assert_eq!(
|
||||
media.cmd,
|
||||
Some(vec_strings!["-i", "./assets/with_audio.mp4"])
|
||||
Some(vec_strings!["-i", "./assets/media_mix/with_audio.mp4"])
|
||||
);
|
||||
assert_eq!(media.filter.clone().unwrap().cmd(), test_filter_cmd);
|
||||
assert_eq!(media.filter.unwrap().map(), test_filter_map);
|
||||
@ -44,7 +44,7 @@ fn video_audio_custom_filter1_input() {
|
||||
config.processing.add_logo = false;
|
||||
config.processing.custom_filter = "[0:v]gblur=2[c_v_out];[0:a]volume=0.2[c_a_out]".to_string();
|
||||
|
||||
let media_obj = Media::new(0, "./assets/with_audio.mp4", true);
|
||||
let media_obj = Media::new(0, "./assets/media_mix/with_audio.mp4", true);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let test_filter_cmd = vec_strings![
|
||||
@ -56,7 +56,7 @@ fn video_audio_custom_filter1_input() {
|
||||
|
||||
assert_eq!(
|
||||
media.cmd,
|
||||
Some(vec_strings!["-i", "./assets/with_audio.mp4"])
|
||||
Some(vec_strings!["-i", "./assets/media_mix/with_audio.mp4"])
|
||||
);
|
||||
assert_eq!(media.filter.clone().unwrap().cmd(), test_filter_cmd);
|
||||
assert_eq!(media.filter.unwrap().map(), test_filter_map);
|
||||
@ -73,7 +73,7 @@ fn video_audio_custom_filter2_input() {
|
||||
"[0:v]null[v];movie=logo.png[l];[v][l]overlay[c_v_out];[0:a]volume=0.2[c_a_out]"
|
||||
.to_string();
|
||||
|
||||
let media_obj = Media::new(0, "./assets/with_audio.mp4", true);
|
||||
let media_obj = Media::new(0, "./assets/media_mix/with_audio.mp4", true);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let test_filter_cmd = vec_strings![
|
||||
@ -85,7 +85,7 @@ fn video_audio_custom_filter2_input() {
|
||||
|
||||
assert_eq!(
|
||||
media.cmd,
|
||||
Some(vec_strings!["-i", "./assets/with_audio.mp4"])
|
||||
Some(vec_strings!["-i", "./assets/media_mix/with_audio.mp4"])
|
||||
);
|
||||
assert_eq!(media.filter.clone().unwrap().cmd(), test_filter_cmd);
|
||||
assert_eq!(media.filter.unwrap().map(), test_filter_map);
|
||||
@ -101,7 +101,7 @@ fn video_audio_custom_filter3_input() {
|
||||
config.processing.custom_filter =
|
||||
"[v_in];movie=logo.png[l];[v_in][l]overlay[c_v_out];[0:a]volume=0.2[c_a_out]".to_string();
|
||||
|
||||
let media_obj = Media::new(0, "./assets/with_audio.mp4", true);
|
||||
let media_obj = Media::new(0, "./assets/media_mix/with_audio.mp4", true);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let test_filter_cmd = vec_strings![
|
||||
@ -113,7 +113,7 @@ fn video_audio_custom_filter3_input() {
|
||||
|
||||
assert_eq!(
|
||||
media.cmd,
|
||||
Some(vec_strings!["-i", "./assets/with_audio.mp4"])
|
||||
Some(vec_strings!["-i", "./assets/media_mix/with_audio.mp4"])
|
||||
);
|
||||
assert_eq!(media.filter.clone().unwrap().cmd(), test_filter_cmd);
|
||||
assert_eq!(media.filter.unwrap().map(), test_filter_map);
|
||||
@ -128,7 +128,7 @@ fn dual_audio_aevalsrc_input() {
|
||||
config.processing.audio_tracks = 2;
|
||||
config.processing.add_logo = false;
|
||||
|
||||
let media_obj = Media::new(0, "./assets/with_audio.mp4", true);
|
||||
let media_obj = Media::new(0, "./assets/media_mix/with_audio.mp4", true);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let test_filter_cmd =
|
||||
@ -141,7 +141,7 @@ fn dual_audio_aevalsrc_input() {
|
||||
|
||||
assert_eq!(
|
||||
media.cmd,
|
||||
Some(vec_strings!["-i", "./assets/with_audio.mp4"])
|
||||
Some(vec_strings!["-i", "./assets/media_mix/with_audio.mp4"])
|
||||
);
|
||||
assert_eq!(media.filter.clone().unwrap().cmd(), test_filter_cmd);
|
||||
assert_eq!(media.filter.unwrap().map(), test_filter_map);
|
||||
@ -156,7 +156,7 @@ fn dual_audio_input() {
|
||||
config.processing.audio_tracks = 2;
|
||||
config.processing.add_logo = false;
|
||||
|
||||
let media_obj = Media::new(0, "./assets/dual_audio.mp4", true);
|
||||
let media_obj = Media::new(0, "./assets/media_mix/dual_audio.mp4", true);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let test_filter_cmd = vec_strings![
|
||||
@ -168,7 +168,7 @@ fn dual_audio_input() {
|
||||
|
||||
assert_eq!(
|
||||
media.cmd,
|
||||
Some(vec_strings!["-i", "./assets/dual_audio.mp4"])
|
||||
Some(vec_strings!["-i", "./assets/media_mix/dual_audio.mp4"])
|
||||
);
|
||||
assert_eq!(media.filter.clone().unwrap().cmd(), test_filter_cmd);
|
||||
assert_eq!(media.filter.unwrap().map(), test_filter_map);
|
||||
@ -183,8 +183,8 @@ fn video_separate_audio_input() {
|
||||
config.processing.audio_tracks = 1;
|
||||
config.processing.add_logo = false;
|
||||
|
||||
let mut media_obj = Media::new(0, "./assets/no_audio.mp4", true);
|
||||
media_obj.audio = "./assets/audio.mp3".to_string();
|
||||
let mut media_obj = Media::new(0, "./assets/media_mix/no_audio.mp4", true);
|
||||
media_obj.audio = "./assets/media_mix/audio.mp3".to_string();
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let test_filter_cmd = vec_strings![
|
||||
@ -198,11 +198,11 @@ fn video_separate_audio_input() {
|
||||
media.cmd,
|
||||
Some(vec_strings![
|
||||
"-i",
|
||||
"./assets/no_audio.mp4",
|
||||
"./assets/media_mix/no_audio.mp4",
|
||||
"-stream_loop",
|
||||
"-1",
|
||||
"-i",
|
||||
"./assets/audio.mp3",
|
||||
"./assets/media_mix/audio.mp3",
|
||||
"-t",
|
||||
"30"
|
||||
])
|
||||
@ -1350,7 +1350,7 @@ fn video_audio_hls() {
|
||||
"/usr/share/ffplayout/public/live/stream.m3u8"
|
||||
]);
|
||||
|
||||
let media_obj = Media::new(0, "./assets/with_audio.mp4", true);
|
||||
let media_obj = Media::new(0, "./assets/media_mix/with_audio.mp4", true);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let enc_prefix = vec_strings![
|
||||
@ -1360,7 +1360,7 @@ fn video_audio_hls() {
|
||||
"level+error",
|
||||
"-re",
|
||||
"-i",
|
||||
"./assets/with_audio.mp4"
|
||||
"./assets/media_mix/with_audio.mp4"
|
||||
];
|
||||
|
||||
let enc_cmd = prepare_output_cmd(&config, enc_prefix, &media.filter);
|
||||
@ -1372,7 +1372,7 @@ fn video_audio_hls() {
|
||||
"level+error",
|
||||
"-re",
|
||||
"-i",
|
||||
"./assets/with_audio.mp4",
|
||||
"./assets/media_mix/with_audio.mp4",
|
||||
"-filter_complex",
|
||||
"[0:v:0]scale=1024:576[vout0];[0:a:0]anull[aout0]",
|
||||
"-map",
|
||||
@ -1441,7 +1441,7 @@ fn video_audio_sub_meta_hls() {
|
||||
"/usr/share/ffplayout/public/live/stream.m3u8"
|
||||
]);
|
||||
|
||||
let media_obj = Media::new(0, "./assets/with_audio.mp4", true);
|
||||
let media_obj = Media::new(0, "./assets/media_mix/with_audio.mp4", true);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let enc_prefix = vec_strings![
|
||||
@ -1451,7 +1451,7 @@ fn video_audio_sub_meta_hls() {
|
||||
"level+error",
|
||||
"-re",
|
||||
"-i",
|
||||
"./assets/with_audio.mp4"
|
||||
"./assets/media_mix/with_audio.mp4"
|
||||
];
|
||||
|
||||
let enc_cmd = prepare_output_cmd(&config, enc_prefix, &media.filter);
|
||||
@ -1463,7 +1463,7 @@ fn video_audio_sub_meta_hls() {
|
||||
"level+error",
|
||||
"-re",
|
||||
"-i",
|
||||
"./assets/with_audio.mp4",
|
||||
"./assets/media_mix/with_audio.mp4",
|
||||
"-filter_complex",
|
||||
"[0:v:0]scale=1024:576[vout0];[0:a:0]anull[aout0]",
|
||||
"-map",
|
||||
@ -1533,7 +1533,7 @@ fn video_multi_audio_hls() {
|
||||
"/usr/share/ffplayout/public/live/stream.m3u8"
|
||||
]);
|
||||
|
||||
let media_obj = Media::new(0, "./assets/dual_audio.mp4", true);
|
||||
let media_obj = Media::new(0, "./assets/media_mix/dual_audio.mp4", true);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let enc_prefix = vec_strings![
|
||||
@ -1543,7 +1543,7 @@ fn video_multi_audio_hls() {
|
||||
"level+error",
|
||||
"-re",
|
||||
"-i",
|
||||
"./assets/dual_audio.mp4"
|
||||
"./assets/media_mix/dual_audio.mp4"
|
||||
];
|
||||
|
||||
let enc_cmd = prepare_output_cmd(&config, enc_prefix, &media.filter);
|
||||
@ -1555,7 +1555,7 @@ fn video_multi_audio_hls() {
|
||||
"level+error",
|
||||
"-re",
|
||||
"-i",
|
||||
"./assets/dual_audio.mp4",
|
||||
"./assets/media_mix/dual_audio.mp4",
|
||||
"-filter_complex",
|
||||
"[0:v:0]scale=1024:576[vout0];[0:a:0]anull[aout0];[0:a:1]anull[aout1]",
|
||||
"-map",
|
||||
@ -1640,7 +1640,7 @@ fn multi_video_audio_hls() {
|
||||
"/usr/share/ffplayout/public/live/stream_%v.m3u8"
|
||||
]);
|
||||
|
||||
let media_obj = Media::new(0, "./assets/with_audio.mp4", true);
|
||||
let media_obj = Media::new(0, "./assets/media_mix/with_audio.mp4", true);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let enc_prefix = vec_strings![
|
||||
@ -1650,7 +1650,7 @@ fn multi_video_audio_hls() {
|
||||
"level+error",
|
||||
"-re",
|
||||
"-i",
|
||||
"./assets/with_audio.mp4"
|
||||
"./assets/media_mix/with_audio.mp4"
|
||||
];
|
||||
|
||||
let enc_cmd = prepare_output_cmd(&config, enc_prefix, &media.filter);
|
||||
@ -1662,7 +1662,7 @@ fn multi_video_audio_hls() {
|
||||
"level+error",
|
||||
"-re",
|
||||
"-i",
|
||||
"./assets/with_audio.mp4",
|
||||
"./assets/media_mix/with_audio.mp4",
|
||||
"-filter_complex",
|
||||
"[0:v:0]scale=1024:576,split=2[v1_out][v2];[v2]scale=w=512:h=288[v2_out];[0:a]asplit=2[a1][a2]",
|
||||
"-map",
|
||||
@ -1758,7 +1758,7 @@ fn multi_video_multi_audio_hls() {
|
||||
"/usr/share/ffplayout/public/live/stream_%v.m3u8"
|
||||
]);
|
||||
|
||||
let media_obj = Media::new(0, "./assets/dual_audio.mp4", true);
|
||||
let media_obj = Media::new(0, "./assets/media_mix/dual_audio.mp4", true);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let enc_prefix = vec_strings![
|
||||
@ -1768,7 +1768,7 @@ fn multi_video_multi_audio_hls() {
|
||||
"level+error",
|
||||
"-re",
|
||||
"-i",
|
||||
"./assets/dual_audio.mp4"
|
||||
"./assets/media_mix/dual_audio.mp4"
|
||||
];
|
||||
|
||||
let enc_cmd = prepare_output_cmd(&config, enc_prefix, &media.filter);
|
||||
@ -1780,7 +1780,7 @@ fn multi_video_multi_audio_hls() {
|
||||
"level+error",
|
||||
"-re",
|
||||
"-i",
|
||||
"./assets/dual_audio.mp4",
|
||||
"./assets/media_mix/dual_audio.mp4",
|
||||
"-filter_complex",
|
||||
"[0:v:0]scale=1024:576,split=2[v1_out][v2];[v2]scale=w=512:h=288[v2_out];[0:a:0]anull,asplit=2[a_0_1][a_0_2];[0:a:1]anull,asplit=2[a_1_1][a_1_2]",
|
||||
"-map",
|
||||
|
@ -15,10 +15,10 @@ use ffplayout_lib::utils::{
|
||||
#[test]
|
||||
fn test_random_list() {
|
||||
let clip_list = vec![
|
||||
Media::new(0, "./assets/with_audio.mp4", true), // 30 seconds
|
||||
Media::new(0, "./assets/dual_audio.mp4", true), // 30 seconds
|
||||
Media::new(0, "./assets/av_sync.mp4", true), // 30 seconds
|
||||
Media::new(0, "./assets/ad.mp4", true), // 25 seconds
|
||||
Media::new(0, "./assets/media_mix/with_audio.mp4", true), // 30 seconds
|
||||
Media::new(0, "./assets/media_mix/dual_audio.mp4", true), // 30 seconds
|
||||
Media::new(0, "./assets/media_mix/av_sync.mp4", true), // 30 seconds
|
||||
Media::new(0, "./assets/media_mix/ad.mp4", true), // 25 seconds
|
||||
];
|
||||
|
||||
let r_list = random_list(clip_list.clone(), 200.0);
|
||||
@ -31,10 +31,10 @@ fn test_random_list() {
|
||||
#[test]
|
||||
fn test_ordered_list() {
|
||||
let clip_list = vec![
|
||||
Media::new(0, "./assets/with_audio.mp4", true), // 30 seconds
|
||||
Media::new(0, "./assets/dual_audio.mp4", true), // 30 seconds
|
||||
Media::new(0, "./assets/av_sync.mp4", true), // 30 seconds
|
||||
Media::new(0, "./assets/ad.mp4", true), // 25 seconds
|
||||
Media::new(0, "./assets/media_mix/with_audio.mp4", true), // 30 seconds
|
||||
Media::new(0, "./assets/media_mix/dual_audio.mp4", true), // 30 seconds
|
||||
Media::new(0, "./assets/media_mix/av_sync.mp4", true), // 30 seconds
|
||||
Media::new(0, "./assets/media_mix/ad.mp4", true), // 25 seconds
|
||||
];
|
||||
|
||||
let o_list = ordered_list(clip_list.clone(), 85.0);
|
||||
|
@ -6,7 +6,7 @@ use std::{
|
||||
use serial_test::serial;
|
||||
use simplelog::*;
|
||||
|
||||
use ffplayout::output::player;
|
||||
use ffplayout::{input::playlist::gen_source, output::player};
|
||||
use ffplayout_lib::{utils::*, vec_strings};
|
||||
|
||||
fn timed_stop(sec: u64, proc_ctl: ProcessControl) {
|
||||
@ -17,6 +17,82 @@ fn timed_stop(sec: u64, proc_ctl: ProcessControl) {
|
||||
proc_ctl.stop_all();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_gen_source() {
|
||||
let mut config = PlayoutConfig::new(None);
|
||||
config.general.skip_validation = true;
|
||||
config.mail.recipient = "".into();
|
||||
config.processing.mode = Playlist;
|
||||
config.ingest.enable = false;
|
||||
config.text.add_text = false;
|
||||
config.playlist.day_start = "00:00:00".into();
|
||||
config.playlist.start_sec = Some(0.0);
|
||||
config.playlist.length = "24:00:00".into();
|
||||
config.playlist.length_sec = Some(86400.0);
|
||||
config.playlist.path = "assets/playlists".into();
|
||||
config.storage.filler = "assets/media_filler/filler_0.mp4".into();
|
||||
config.logging.log_to_file = false;
|
||||
config.logging.timestamp = false;
|
||||
config.logging.level = LevelFilter::Trace;
|
||||
let play_control = PlayerControl::new();
|
||||
let playout_stat = PlayoutStatus::new();
|
||||
|
||||
let logging = init_logging(&config, None, None);
|
||||
CombinedLogger::init(logging).unwrap_or_default();
|
||||
|
||||
let mut valid_source_with_probe = Media::new(0, "assets/media_mix/av_sync.mp4", true);
|
||||
let valid_media = gen_source(
|
||||
&config,
|
||||
valid_source_with_probe.clone(),
|
||||
&playout_stat,
|
||||
&play_control,
|
||||
100,
|
||||
);
|
||||
|
||||
assert_eq!(valid_source_with_probe.source, valid_media.source);
|
||||
|
||||
let mut valid_source_without_probe = Media::new(0, "assets/media_mix/av_sync.mp4", false);
|
||||
valid_source_without_probe.duration = 30.0;
|
||||
valid_source_without_probe.out = 20.0;
|
||||
let valid_media = gen_source(
|
||||
&config,
|
||||
valid_source_without_probe.clone(),
|
||||
&playout_stat,
|
||||
&play_control,
|
||||
100,
|
||||
);
|
||||
|
||||
assert_eq!(valid_source_without_probe.source, valid_media.source);
|
||||
assert_eq!(valid_media.out, 20.0);
|
||||
|
||||
valid_source_with_probe.out = 0.9;
|
||||
|
||||
let valid_media = gen_source(
|
||||
&config,
|
||||
valid_source_with_probe.clone(),
|
||||
&playout_stat,
|
||||
&play_control,
|
||||
100,
|
||||
);
|
||||
|
||||
assert_eq!(valid_media.out, 1.9);
|
||||
|
||||
let mut no_valid_source_with_probe = Media::new(0, "assets/media_mix/av_snc.mp4", true);
|
||||
no_valid_source_with_probe.duration = 30.0;
|
||||
no_valid_source_with_probe.out = 30.0;
|
||||
|
||||
let valid_media = gen_source(
|
||||
&config,
|
||||
no_valid_source_with_probe.clone(),
|
||||
&playout_stat,
|
||||
&play_control,
|
||||
100,
|
||||
);
|
||||
|
||||
assert_eq!(valid_media.source, "assets/media_filler/filler_0.mp4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
#[ignore]
|
||||
@ -32,7 +108,7 @@ fn playlist_missing() {
|
||||
config.playlist.length = "24:00:00".into();
|
||||
config.playlist.length_sec = Some(86400.0);
|
||||
config.playlist.path = "assets/playlists".into();
|
||||
config.storage.filler = "assets/with_audio.mp4".into();
|
||||
config.storage.filler = "assets/media_filler/filler_0.mp4".into();
|
||||
config.logging.log_to_file = false;
|
||||
config.logging.timestamp = false;
|
||||
config.logging.level = LevelFilter::Trace;
|
||||
@ -75,7 +151,7 @@ fn playlist_next_missing() {
|
||||
config.playlist.length = "24:00:00".into();
|
||||
config.playlist.length_sec = Some(86400.0);
|
||||
config.playlist.path = "assets/playlists".into();
|
||||
config.storage.filler = "assets/with_audio.mp4".into();
|
||||
config.storage.filler = "assets/media_filler/filler_0.mp4".into();
|
||||
config.logging.log_to_file = false;
|
||||
config.logging.timestamp = false;
|
||||
config.logging.level = LevelFilter::Trace;
|
||||
@ -118,7 +194,7 @@ fn playlist_to_short() {
|
||||
config.playlist.length = "24:00:00".into();
|
||||
config.playlist.length_sec = Some(86400.0);
|
||||
config.playlist.path = "assets/playlists".into();
|
||||
config.storage.filler = "assets/with_audio.mp4".into();
|
||||
config.storage.filler = "assets/media_filler/filler_0.mp4".into();
|
||||
config.logging.log_to_file = false;
|
||||
config.logging.timestamp = false;
|
||||
config.logging.level = log::LevelFilter::Trace;
|
||||
@ -161,7 +237,7 @@ fn playlist_init_after_list_end() {
|
||||
config.playlist.length = "24:00:00".into();
|
||||
config.playlist.length_sec = Some(86400.0);
|
||||
config.playlist.path = "assets/playlists".into();
|
||||
config.storage.filler = "assets/with_audio.mp4".into();
|
||||
config.storage.filler = "assets/media_filler/filler_0.mp4".into();
|
||||
config.logging.log_to_file = false;
|
||||
config.logging.timestamp = false;
|
||||
config.logging.level = log::LevelFilter::Trace;
|
||||
@ -204,7 +280,7 @@ fn playlist_change_at_midnight() {
|
||||
config.playlist.length = "24:00:00".into();
|
||||
config.playlist.length_sec = Some(86400.0);
|
||||
config.playlist.path = "assets/playlists".into();
|
||||
config.storage.filler = "assets/with_audio.mp4".into();
|
||||
config.storage.filler = "assets/media_filler/filler_0.mp4".into();
|
||||
config.logging.log_to_file = false;
|
||||
config.logging.timestamp = false;
|
||||
config.logging.level = LevelFilter::Trace;
|
||||
@ -247,7 +323,7 @@ fn playlist_change_before_midnight() {
|
||||
config.playlist.length = "24:00:00".into();
|
||||
config.playlist.length_sec = Some(86400.0);
|
||||
config.playlist.path = "assets/playlists".into();
|
||||
config.storage.filler = "assets/with_audio.mp4".into();
|
||||
config.storage.filler = "assets/media_filler/filler_0.mp4".into();
|
||||
config.logging.log_to_file = false;
|
||||
config.logging.timestamp = false;
|
||||
config.logging.level = LevelFilter::Trace;
|
||||
@ -290,7 +366,7 @@ fn playlist_change_at_six() {
|
||||
config.playlist.length = "24:00:00".into();
|
||||
config.playlist.length_sec = Some(86400.0);
|
||||
config.playlist.path = "assets/playlists".into();
|
||||
config.storage.filler = "assets/with_audio.mp4".into();
|
||||
config.storage.filler = "assets/media_filler/filler_0.mp4".into();
|
||||
config.logging.log_to_file = false;
|
||||
config.logging.timestamp = false;
|
||||
config.out.mode = Null;
|
||||
|
Loading…
x
Reference in New Issue
Block a user