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.
|
||||
fn load_or_update_playlist(&mut self, seek: bool) {
|
||||
let mut get_current = false;
|
||||
let mut reload = false;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if get_current {
|
||||
let json = read_json(
|
||||
&self.config,
|
||||
&self.player_control,
|
||||
None,
|
||||
self.json_path.clone(),
|
||||
self.is_terminated.clone(),
|
||||
seek,
|
||||
false,
|
||||
);
|
||||
|
||||
if let Some(file) = &json.current_file {
|
||||
info!("Read Playlist: <b><magenta>{file}</></b>");
|
||||
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;
|
||||
} 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()
|
||||
);
|
||||
|
||||
let json = read_json(
|
||||
&self.config,
|
||||
&self.player_control,
|
||||
self.json_path.clone(),
|
||||
self.is_terminated.clone(),
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
self.json_mod = json.modified;
|
||||
*self.player_control.current_list.lock().unwrap() = json.program;
|
||||
if self.json_path.is_none() {
|
||||
trace!("missing playlist");
|
||||
|
||||
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);
|
||||
}
|
||||
} 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()
|
||||
);
|
||||
|
||||
let media = Media::new(0, "", false);
|
||||
|
||||
self.json_mod = None;
|
||||
self.json_path = None;
|
||||
self.current_node = media.clone();
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
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,118 +344,37 @@ 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;
|
||||
let mut current_time = time_in_seconds();
|
||||
let (_, total_delta) = get_delta(&self.config, ¤t_time);
|
||||
|
||||
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 (_, 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.config.playlist.start_sec.unwrap() > current_time {
|
||||
current_time += self.config.playlist.length_sec.unwrap() + 1.0;
|
||||
}
|
||||
|
||||
let mut nodes = self.player_control.current_list.lock().unwrap();
|
||||
let index = nodes.len();
|
||||
|
||||
let mut media = Media::new(index, "", false);
|
||||
media.begin = Some(current_time);
|
||||
media.duration = duration;
|
||||
media.out = out;
|
||||
|
||||
self.current_node = gen_source(
|
||||
&self.config,
|
||||
media,
|
||||
&self.playout_stat,
|
||||
&self.player_control,
|
||||
last_index,
|
||||
);
|
||||
|
||||
nodes.push(self.current_node.clone());
|
||||
self.player_control
|
||||
.current_index
|
||||
.store(nodes.len(), Ordering::SeqCst);
|
||||
if self.start_sec > current_time {
|
||||
current_time += self.end_sec + 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 media = Media::new(length, "", false);
|
||||
media.begin = Some(current_time);
|
||||
media.duration = total_delta;
|
||||
media.out = total_delta;
|
||||
|
||||
self.current_node = gen_source(
|
||||
&self.config,
|
||||
media,
|
||||
&self.playout_stat,
|
||||
&self.player_control,
|
||||
last_index,
|
||||
);
|
||||
}
|
||||
|
||||
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,26 +534,26 @@ pub fn gen_source(
|
||||
warn!("Clip is less then 1 second long (<yellow>{duration:.3}</>), adjust length.");
|
||||
|
||||
duration = 1.2;
|
||||
|
||||
if node.seek > 1.0 {
|
||||
node.seek -= 1.0;
|
||||
} else {
|
||||
node.out += 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
trace!("Clip out: {duration}, duration: {}", node.duration);
|
||||
trace!("Clip new length: {duration}, duration: {}", node.duration);
|
||||
|
||||
if node.probe.is_none() && !node.source.is_empty() {
|
||||
node.add_probe(true);
|
||||
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.out - node.seek < 1.0 {
|
||||
if node.seek > 1.0 {
|
||||
node.seek -= 1.0;
|
||||
} else {
|
||||
node.out += 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
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,18 +122,15 @@ impl Media {
|
||||
let mut probe = None;
|
||||
|
||||
if do_probe && (is_remote(src) || Path::new(src).is_file()) {
|
||||
match MediaProbe::new(src) {
|
||||
Ok(p) => {
|
||||
probe = Some(p.clone());
|
||||
if let Ok(p) = MediaProbe::new(src) {
|
||||
probe = Some(p.clone());
|
||||
|
||||
duration = p
|
||||
.format
|
||||
.duration
|
||||
.unwrap_or_default()
|
||||
.parse()
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Err(e) => error!("{e}"),
|
||||
duration = p
|
||||
.format
|
||||
.duration
|
||||
.unwrap_or_default()
|
||||
.parse()
|
||||
.unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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