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:
jb-alvarado 2024-02-02 13:04:15 +01:00 committed by GitHub
commit 139c6f0434
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 19653 additions and 18754 deletions

53
Cargo.lock generated
View File

@ -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",
]

View File

@ -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>"]

View File

@ -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, &current_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() && &current_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()
&& &current_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, &current_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

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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(),
};

View File

@ -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),
);
}

View File

@ -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;

Binary file not shown.

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 202 KiB

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

View 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"
}
]
}

View 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

View File

@ -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"
}
]

View 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"
}
]
}

View File

@ -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",

View File

@ -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);

View File

@ -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;