add skip validation argument, fix case where clips not exist (introduced in v.20), fix lock deadlock, add more tests
This commit is contained in:
parent
ebfe9630bb
commit
b8d041be57
30
Cargo.lock
generated
30
Cargo.lock
generated
@ -4,11 +4,11 @@ version = 3
|
||||
|
||||
[[package]]
|
||||
name = "actix-codec"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8"
|
||||
checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bitflags 2.4.2",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@ -368,9 +368,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.4"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
|
||||
checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
@ -997,9 +997,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.3"
|
||||
version = "0.20.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e"
|
||||
checksum = "fc5d6b04b3fd0ba9926f945895de7d806260a2d7431ba82e7edaecb043c4c6b8"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
@ -1007,9 +1007,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.3"
|
||||
version = "0.20.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621"
|
||||
checksum = "04e48a959bcd5c761246f5d090ebc2fbf7b9cd527a492b07a67510c108f1e7e3"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
@ -1021,9 +1021,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.3"
|
||||
version = "0.20.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
|
||||
checksum = "1d1545d67a2149e1d93b7e5c7752dce5a7426eb5d1357ddcfd89336b94444f77"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
@ -1823,9 +1823,9 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0"
|
||||
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
@ -3733,9 +3733,9 @@ checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c"
|
||||
|
||||
[[package]]
|
||||
name = "value-bag"
|
||||
version = "1.6.0"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cdbaf5e132e593e9fc1de6a15bbec912395b11fb9719e061cf64f804524c503"
|
||||
checksum = "126e423afe2dd9ac52142e7e9d5ce4135d7e13776c529d27fd6bc49f19e3280b"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
|
@ -3,7 +3,7 @@ use std::{
|
||||
path::Path,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex,
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
@ -160,7 +160,10 @@ impl CurrentProgram {
|
||||
duration = self.current_node.duration
|
||||
}
|
||||
|
||||
trace!("delta: {delta}, total_delta: {total_delta}");
|
||||
trace!(
|
||||
"delta: {delta}, total_delta: {total_delta}, current index: {}",
|
||||
self.current_node.index.unwrap_or_default()
|
||||
);
|
||||
|
||||
let mut next_start =
|
||||
self.current_node.begin.unwrap_or_default() - start_sec + duration + delta;
|
||||
@ -284,9 +287,10 @@ impl CurrentProgram {
|
||||
}
|
||||
|
||||
// Prepare init clip.
|
||||
fn init_clip(&mut self) {
|
||||
fn init_clip(&mut self) -> bool {
|
||||
trace!("init_clip");
|
||||
self.get_current_clip();
|
||||
let mut is_filler = false;
|
||||
|
||||
if !self.playout_stat.list_init.load(Ordering::SeqCst) {
|
||||
let time_sec = self.get_current_time();
|
||||
@ -299,17 +303,31 @@ impl CurrentProgram {
|
||||
|
||||
// de-instance node to preserve original values in list
|
||||
let mut node_clone = nodes[index].clone();
|
||||
|
||||
// Important! When no manual drop is happen here, lock is still active in handle_list_init
|
||||
drop(nodes);
|
||||
|
||||
node_clone.seek = time_sec
|
||||
- (node_clone.begin.unwrap() - *self.playout_stat.time_shift.lock().unwrap());
|
||||
|
||||
self.current_node = handle_list_init(
|
||||
&self.config,
|
||||
node_clone,
|
||||
&self.playout_stat.chain,
|
||||
&self.playout_stat,
|
||||
&self.player_control,
|
||||
last_index,
|
||||
);
|
||||
|
||||
if self
|
||||
.current_node
|
||||
.source
|
||||
.contains(&self.config.storage.path.to_string_lossy().to_string())
|
||||
{
|
||||
is_filler = true;
|
||||
}
|
||||
}
|
||||
|
||||
is_filler
|
||||
}
|
||||
}
|
||||
|
||||
@ -322,20 +340,47 @@ impl Iterator for CurrentProgram {
|
||||
|
||||
if self.playout_stat.list_init.load(Ordering::SeqCst) {
|
||||
trace!("Init playlist, from next iterator");
|
||||
let mut init_clip_is_filler = false;
|
||||
|
||||
if self.json_path.is_some() {
|
||||
self.init_clip();
|
||||
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.
|
||||
let last_index = self.player_control.current_list.lock().unwrap().len() - 1;
|
||||
self.current_node =
|
||||
self.player_control.current_list.lock().unwrap()[last_index].clone();
|
||||
let new_node = self.player_control.current_list.lock().unwrap()[last_index].clone();
|
||||
let new_length = new_node.begin.unwrap_or_default() + new_node.duration;
|
||||
trace!("Init playlist after playlist end");
|
||||
|
||||
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();
|
||||
|
||||
@ -352,16 +397,23 @@ impl Iterator for CurrentProgram {
|
||||
.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,
|
||||
self.player_control.current_list.lock().unwrap()[index].clone(),
|
||||
&self.playout_stat.chain,
|
||||
node_clone,
|
||||
&self.playout_stat,
|
||||
&self.player_control,
|
||||
0,
|
||||
);
|
||||
|
||||
return Some(self.current_node.clone());
|
||||
} else {
|
||||
} 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);
|
||||
@ -396,7 +448,7 @@ impl Iterator for CurrentProgram {
|
||||
self.current_node = gen_source(
|
||||
&self.config,
|
||||
media,
|
||||
&self.playout_stat.chain,
|
||||
&self.playout_stat,
|
||||
&self.player_control,
|
||||
last_index,
|
||||
);
|
||||
@ -475,7 +527,7 @@ impl Iterator for CurrentProgram {
|
||||
self.current_node = gen_source(
|
||||
&self.config,
|
||||
self.current_node.clone(),
|
||||
&self.playout_stat.chain,
|
||||
&self.playout_stat,
|
||||
&self.player_control,
|
||||
0,
|
||||
);
|
||||
@ -502,7 +554,7 @@ impl Iterator for CurrentProgram {
|
||||
self.current_node = gen_source(
|
||||
&self.config,
|
||||
self.player_control.current_list.lock().unwrap()[0].clone(),
|
||||
&self.playout_stat.chain,
|
||||
&self.playout_stat,
|
||||
&self.player_control,
|
||||
0,
|
||||
);
|
||||
@ -566,13 +618,7 @@ fn timed_source(
|
||||
{
|
||||
// when we are in the 24 hour range, get the clip
|
||||
new_node.process = Some(true);
|
||||
new_node = gen_source(
|
||||
config,
|
||||
node,
|
||||
&playout_stat.chain,
|
||||
player_control,
|
||||
last_index,
|
||||
);
|
||||
new_node = gen_source(config, node, &playout_stat, player_control, last_index);
|
||||
} else if total_delta <= 0.0 {
|
||||
info!("Begin is over play time, skip: {}", node.source);
|
||||
} else if total_delta < node.duration - node.seek || last {
|
||||
@ -580,7 +626,7 @@ fn timed_source(
|
||||
config,
|
||||
node,
|
||||
total_delta,
|
||||
&playout_stat.chain,
|
||||
&playout_stat,
|
||||
player_control,
|
||||
last_index,
|
||||
);
|
||||
@ -593,11 +639,18 @@ fn timed_source(
|
||||
pub fn gen_source(
|
||||
config: &PlayoutConfig,
|
||||
mut node: Media,
|
||||
filter_chain: &Option<Arc<Mutex<Vec<String>>>>,
|
||||
playout_stat: &PlayoutStatus,
|
||||
player_control: &PlayerControl,
|
||||
last_index: usize,
|
||||
) -> Media {
|
||||
let duration = node.out - node.seek;
|
||||
let node_index = node.index.unwrap_or_default();
|
||||
let mut duration = node.out - node.seek;
|
||||
|
||||
if duration < 1.0 {
|
||||
warn!("Clip is less then 1 second long (<yellow>{duration:.3}</>), adjust length.");
|
||||
|
||||
duration = 1.2;
|
||||
}
|
||||
|
||||
trace!("Clip out: {duration}, duration: {}", node.duration);
|
||||
|
||||
@ -609,6 +662,11 @@ pub fn gen_source(
|
||||
|
||||
// separate if condition, because of node.add_probe() in last condition
|
||||
if node.probe.is_some() {
|
||||
if node.seek > 0.0 {
|
||||
node.seek = node.seek - 1.0;
|
||||
} else {
|
||||
node.out = node.out + 1.0;
|
||||
}
|
||||
if node
|
||||
.source
|
||||
.rsplit_once('.')
|
||||
@ -621,14 +679,10 @@ pub fn gen_source(
|
||||
node.cmd = Some(seek_and_length(&mut node));
|
||||
}
|
||||
} else {
|
||||
trace!(
|
||||
"clip index: {} | last index: {}",
|
||||
node.index.unwrap_or_default(),
|
||||
last_index
|
||||
);
|
||||
trace!("clip index: {node_index} | last index: {last_index}");
|
||||
|
||||
// last index is the index from the last item from the node list.
|
||||
if node.index.unwrap_or_default() < last_index {
|
||||
// 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);
|
||||
}
|
||||
|
||||
@ -638,6 +692,11 @@ pub fn gen_source(
|
||||
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();
|
||||
|
||||
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 {
|
||||
player_control.filler_index.store(0, Ordering::SeqCst)
|
||||
}
|
||||
@ -650,38 +709,12 @@ pub fn gen_source(
|
||||
filler_media.out = duration;
|
||||
}
|
||||
|
||||
// If necessary filler clip will be injected to the current list,
|
||||
// original clip get new begin and seek value, to keep everything in sync.
|
||||
|
||||
if node.index.unwrap_or_default() < last_index {
|
||||
player_control.current_list.lock().unwrap()[node.index.unwrap_or_default()].begin =
|
||||
Some(node.begin.unwrap_or_default() + filler_media.out);
|
||||
|
||||
player_control.current_list.lock().unwrap()[node.index.unwrap_or_default()].seek =
|
||||
node.seek + filler_media.out;
|
||||
}
|
||||
|
||||
node.source = filler_media.source;
|
||||
node.seek = 0.0;
|
||||
node.out = filler_media.out;
|
||||
node.duration = filler_media.duration;
|
||||
node.cmd = Some(loop_filler(&node));
|
||||
node.probe = filler_media.probe;
|
||||
|
||||
if node.out < duration - 1.0 && node.index.unwrap_or_default() < last_index {
|
||||
player_control
|
||||
.current_list
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(node.index.unwrap_or_default(), node.clone());
|
||||
|
||||
for (i, item) in (*player_control.current_list.lock().unwrap())
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
{
|
||||
item.index = Some(i);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match MediaProbe::new(&config.storage.filler.to_string_lossy()) {
|
||||
Ok(probe) => {
|
||||
@ -734,16 +767,9 @@ pub fn gen_source(
|
||||
);
|
||||
}
|
||||
|
||||
node.add_filter(config, filter_chain);
|
||||
node.add_filter(config, &playout_stat.chain);
|
||||
|
||||
if duration < 1.0 {
|
||||
warn!(
|
||||
"Clip is less then 1 second long (<yellow>{duration:.3}</>), skip: <b><magenta>{}</></b>",
|
||||
node.source
|
||||
);
|
||||
|
||||
node.process = Some(false);
|
||||
}
|
||||
trace!("return gen_source: {}", node.source);
|
||||
|
||||
node
|
||||
}
|
||||
@ -753,7 +779,7 @@ pub fn gen_source(
|
||||
fn handle_list_init(
|
||||
config: &PlayoutConfig,
|
||||
mut node: Media,
|
||||
filter_chain: &Option<Arc<Mutex<Vec<String>>>>,
|
||||
playout_stat: &PlayoutStatus,
|
||||
player_control: &PlayerControl,
|
||||
last_index: usize,
|
||||
) -> Media {
|
||||
@ -767,7 +793,7 @@ fn handle_list_init(
|
||||
|
||||
node.out = out;
|
||||
|
||||
gen_source(config, node, filter_chain, player_control, last_index)
|
||||
gen_source(config, node, playout_stat, player_control, last_index)
|
||||
}
|
||||
|
||||
/// when we come to last clip in playlist,
|
||||
@ -777,7 +803,7 @@ fn handle_list_end(
|
||||
config: &PlayoutConfig,
|
||||
mut node: Media,
|
||||
total_delta: f64,
|
||||
filter_chain: &Option<Arc<Mutex<Vec<String>>>>,
|
||||
playout_stat: &PlayoutStatus,
|
||||
player_control: &PlayerControl,
|
||||
last_index: usize,
|
||||
) -> Media {
|
||||
@ -807,5 +833,5 @@ fn handle_list_end(
|
||||
|
||||
node.process = Some(true);
|
||||
|
||||
gen_source(config, node, filter_chain, player_control, last_index)
|
||||
gen_source(config, node, playout_stat, player_control, last_index)
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ pub fn player(
|
||||
let play_stat = playout_stat.clone();
|
||||
|
||||
// get source iterator
|
||||
let get_source = source_generator(
|
||||
let node_sources = source_generator(
|
||||
config.clone(),
|
||||
play_control,
|
||||
playout_stat,
|
||||
@ -82,7 +82,7 @@ pub fn player(
|
||||
thread::spawn(move || ingest_server(config_clone, ingest_sender, proc_control_c));
|
||||
}
|
||||
|
||||
'source_iter: for node in get_source {
|
||||
'source_iter: for node in node_sources {
|
||||
*play_control.current_media.lock().unwrap() = Some(node.clone());
|
||||
|
||||
if proc_control.is_terminated.load(Ordering::SeqCst) {
|
||||
|
@ -87,6 +87,9 @@ pub struct Args {
|
||||
#[clap(short, long, help = "Set audio volume")]
|
||||
pub volume: Option<f64>,
|
||||
|
||||
#[clap(long, help = "Skip validation process")]
|
||||
pub skip_validation: bool,
|
||||
|
||||
#[clap(long, help = "validate given playlist")]
|
||||
pub validate: bool,
|
||||
}
|
||||
|
@ -121,6 +121,8 @@ pub fn get_config(args: Args) -> Result<PlayoutConfig, ProcError> {
|
||||
}
|
||||
}
|
||||
|
||||
config.general.skip_validation = args.skip_validation;
|
||||
|
||||
if let Some(volume) = args.volume {
|
||||
config.processing.volume = volume;
|
||||
}
|
||||
|
@ -180,6 +180,9 @@ pub struct General {
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub template: Option<Template>,
|
||||
|
||||
#[serde(default, skip_serializing, skip_deserializing)]
|
||||
pub skip_validation: bool,
|
||||
|
||||
#[serde(default, skip_serializing, skip_deserializing)]
|
||||
pub validate: bool,
|
||||
}
|
||||
|
@ -186,9 +186,16 @@ pub fn read_json(
|
||||
|
||||
let list_clone = playlist.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
validate_playlist(config_clone, control_clone, list_clone, is_terminated)
|
||||
});
|
||||
if !config.general.skip_validation {
|
||||
thread::spawn(move || {
|
||||
validate_playlist(
|
||||
config_clone,
|
||||
control_clone,
|
||||
list_clone,
|
||||
is_terminated,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
match config.playlist.infinit {
|
||||
true => return loop_playlist(config, current_file, playlist),
|
||||
@ -198,6 +205,8 @@ pub fn read_json(
|
||||
}
|
||||
}
|
||||
} else if playlist_path.is_file() {
|
||||
let modified = modified_time(¤t_file);
|
||||
|
||||
let f = File::options()
|
||||
.read(true)
|
||||
.write(false)
|
||||
@ -216,13 +225,15 @@ pub fn read_json(
|
||||
playlist = JsonPlaylist::new(date, start_sec)
|
||||
}
|
||||
|
||||
playlist.modified = modified_time(¤t_file);
|
||||
playlist.modified = modified;
|
||||
|
||||
let list_clone = playlist.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
validate_playlist(config_clone, control_clone, list_clone, is_terminated)
|
||||
});
|
||||
if !config.general.skip_validation {
|
||||
thread::spawn(move || {
|
||||
validate_playlist(config_clone, control_clone, list_clone, is_terminated)
|
||||
});
|
||||
}
|
||||
|
||||
match config.playlist.infinit {
|
||||
true => return loop_playlist(config, current_file, playlist),
|
||||
|
@ -187,26 +187,27 @@ pub fn validate_playlist(
|
||||
sec_to_time(begin),
|
||||
item.source
|
||||
)
|
||||
} else if let Ok(mut list) = player_control.current_list.lock() {
|
||||
list.iter_mut().for_each(|o| {
|
||||
if o.source == item.source {
|
||||
o.probe = item.probe.clone();
|
||||
} else if let Ok(mut list) = player_control.current_list.try_lock() {
|
||||
// Filter out same item in current playlist, then add the probe to it.
|
||||
// Check also if duration differs with playlist value, log error if so and adjust that value.
|
||||
list.iter_mut().filter(|list_item| list_item.source == item.source).for_each(|o| {
|
||||
o.probe = item.probe.clone();
|
||||
|
||||
if let Some(dur) =
|
||||
item.probe.as_ref().and_then(|f| f.format.duration.clone())
|
||||
{
|
||||
let probe_duration = dur.parse().unwrap_or_default();
|
||||
if let Some(dur) =
|
||||
item.probe.as_ref().and_then(|f| f.format.duration.clone())
|
||||
{
|
||||
let probe_duration = dur.parse().unwrap_or_default();
|
||||
|
||||
if !is_close(o.duration, probe_duration, 1.2) {
|
||||
error!(
|
||||
"File duration differs from playlist value. File duration: <yellow>{}</>, playlist value: <yellow>{}</>, source <b><magenta>{}</></b>",
|
||||
sec_to_time(o.duration), sec_to_time(probe_duration), o.source
|
||||
);
|
||||
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>",
|
||||
sec_to_time(o.begin.unwrap_or_default()), sec_to_time(probe_duration), sec_to_time(o.duration), o.source
|
||||
);
|
||||
|
||||
o.duration = probe_duration;
|
||||
}
|
||||
o.duration = probe_duration;
|
||||
}
|
||||
}
|
||||
|
||||
if o.audio == item.audio && item.probe_audio.is_some() {
|
||||
o.probe_audio = item.probe_audio.clone();
|
||||
o.duration_audio = item.duration_audio;
|
||||
@ -215,7 +216,7 @@ pub fn validate_playlist(
|
||||
}
|
||||
} else {
|
||||
error!(
|
||||
"Error on position <yellow>{pos:0>3}</> <b><magenta>{}</></b>, file: {}",
|
||||
"Error on position <yellow>{pos:0>3}</> <yellow>{}</>, file: <b><magenta>{}</></b>",
|
||||
sec_to_time(begin),
|
||||
item.source
|
||||
);
|
||||
|
@ -273,7 +273,6 @@ impl MediaProbe {
|
||||
})
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{e}");
|
||||
if !Path::new(input).is_file() && !is_remote(input) {
|
||||
Err(ProcError::Custom(format!("File '{input}' not exist!")))
|
||||
} else {
|
||||
|
22526
tests/assets/playlists/2024/01/2024-01-30.json
Normal file
22526
tests/assets/playlists/2024/01/2024-01-30.json
Normal file
File diff suppressed because it is too large
Load Diff
22526
tests/assets/playlists/2024/01/2024-01-31.json
Normal file
22526
tests/assets/playlists/2024/01/2024-01-31.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,7 @@ use std::{fs, path::PathBuf};
|
||||
|
||||
use ffplayout::{input::playlist::gen_source, utils::prepare_output_cmd};
|
||||
use ffplayout_lib::{
|
||||
utils::{Media, OutputMode::*, PlayerControl, PlayoutConfig, ProcessUnit::*},
|
||||
utils::{Media, OutputMode::*, PlayerControl, PlayoutConfig, PlayoutStatus, ProcessUnit::*},
|
||||
vec_strings,
|
||||
};
|
||||
|
||||
@ -10,13 +10,14 @@ use ffplayout_lib::{
|
||||
fn video_audio_input() {
|
||||
let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")));
|
||||
let player_control = PlayerControl::new();
|
||||
let playout_stat = PlayoutStatus::new();
|
||||
config.out.mode = Stream;
|
||||
config.processing.add_logo = true;
|
||||
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 = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let test_filter_cmd =
|
||||
vec_strings![
|
||||
@ -38,12 +39,13 @@ fn video_audio_input() {
|
||||
fn video_audio_custom_filter1_input() {
|
||||
let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")));
|
||||
let player_control = PlayerControl::new();
|
||||
let playout_stat = PlayoutStatus::new();
|
||||
config.out.mode = Stream;
|
||||
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 = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let test_filter_cmd = vec_strings![
|
||||
"-filter_complex",
|
||||
@ -64,6 +66,7 @@ fn video_audio_custom_filter1_input() {
|
||||
fn video_audio_custom_filter2_input() {
|
||||
let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")));
|
||||
let player_control = PlayerControl::new();
|
||||
let playout_stat = PlayoutStatus::new();
|
||||
config.out.mode = Stream;
|
||||
config.processing.add_logo = false;
|
||||
config.processing.custom_filter =
|
||||
@ -71,7 +74,7 @@ fn video_audio_custom_filter2_input() {
|
||||
.to_string();
|
||||
|
||||
let media_obj = Media::new(0, "./assets/with_audio.mp4", true);
|
||||
let media = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let test_filter_cmd = vec_strings![
|
||||
"-filter_complex",
|
||||
@ -92,13 +95,14 @@ fn video_audio_custom_filter2_input() {
|
||||
fn video_audio_custom_filter3_input() {
|
||||
let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")));
|
||||
let player_control = PlayerControl::new();
|
||||
let playout_stat = PlayoutStatus::new();
|
||||
config.out.mode = Stream;
|
||||
config.processing.add_logo = false;
|
||||
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 = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let test_filter_cmd = vec_strings![
|
||||
"-filter_complex",
|
||||
@ -119,12 +123,13 @@ fn video_audio_custom_filter3_input() {
|
||||
fn dual_audio_aevalsrc_input() {
|
||||
let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")));
|
||||
let player_control = PlayerControl::new();
|
||||
let playout_stat = PlayoutStatus::new();
|
||||
config.out.mode = Stream;
|
||||
config.processing.audio_tracks = 2;
|
||||
config.processing.add_logo = false;
|
||||
|
||||
let media_obj = Media::new(0, "./assets/with_audio.mp4", true);
|
||||
let media = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let test_filter_cmd =
|
||||
vec_strings![
|
||||
@ -146,12 +151,13 @@ fn dual_audio_aevalsrc_input() {
|
||||
fn dual_audio_input() {
|
||||
let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")));
|
||||
let player_control = PlayerControl::new();
|
||||
let playout_stat = PlayoutStatus::new();
|
||||
config.out.mode = Stream;
|
||||
config.processing.audio_tracks = 2;
|
||||
config.processing.add_logo = false;
|
||||
|
||||
let media_obj = Media::new(0, "./assets/dual_audio.mp4", true);
|
||||
let media = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let test_filter_cmd = vec_strings![
|
||||
"-filter_complex",
|
||||
@ -172,13 +178,14 @@ fn dual_audio_input() {
|
||||
fn video_separate_audio_input() {
|
||||
let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")));
|
||||
let player_control = PlayerControl::new();
|
||||
let playout_stat = PlayoutStatus::new();
|
||||
config.out.mode = Stream;
|
||||
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 media = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let test_filter_cmd = vec_strings![
|
||||
"-filter_complex",
|
||||
@ -1315,6 +1322,7 @@ fn video_audio_text_filter_stream() {
|
||||
fn video_audio_hls() {
|
||||
let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")));
|
||||
let player_control = PlayerControl::new();
|
||||
let playout_stat = PlayoutStatus::new();
|
||||
config.out.mode = HLS;
|
||||
config.processing.add_logo = false;
|
||||
config.text.add_text = false;
|
||||
@ -1343,7 +1351,7 @@ fn video_audio_hls() {
|
||||
]);
|
||||
|
||||
let media_obj = Media::new(0, "./assets/with_audio.mp4", true);
|
||||
let media = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let enc_prefix = vec_strings![
|
||||
"-hide_banner",
|
||||
@ -1401,6 +1409,7 @@ fn video_audio_hls() {
|
||||
fn video_audio_sub_meta_hls() {
|
||||
let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")));
|
||||
let player_control = PlayerControl::new();
|
||||
let playout_stat = PlayoutStatus::new();
|
||||
config.out.mode = HLS;
|
||||
config.processing.add_logo = false;
|
||||
config.text.add_text = false;
|
||||
@ -1433,7 +1442,7 @@ fn video_audio_sub_meta_hls() {
|
||||
]);
|
||||
|
||||
let media_obj = Media::new(0, "./assets/with_audio.mp4", true);
|
||||
let media = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let enc_prefix = vec_strings![
|
||||
"-hide_banner",
|
||||
@ -1495,6 +1504,7 @@ fn video_audio_sub_meta_hls() {
|
||||
fn video_multi_audio_hls() {
|
||||
let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")));
|
||||
let player_control = PlayerControl::new();
|
||||
let playout_stat = PlayoutStatus::new();
|
||||
config.out.mode = HLS;
|
||||
config.processing.add_logo = false;
|
||||
config.processing.audio_tracks = 2;
|
||||
@ -1524,7 +1534,7 @@ fn video_multi_audio_hls() {
|
||||
]);
|
||||
|
||||
let media_obj = Media::new(0, "./assets/dual_audio.mp4", true);
|
||||
let media = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let enc_prefix = vec_strings![
|
||||
"-hide_banner",
|
||||
@ -1584,6 +1594,7 @@ fn video_multi_audio_hls() {
|
||||
fn multi_video_audio_hls() {
|
||||
let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")));
|
||||
let player_control = PlayerControl::new();
|
||||
let playout_stat = PlayoutStatus::new();
|
||||
config.out.mode = HLS;
|
||||
config.processing.add_logo = false;
|
||||
config.text.add_text = false;
|
||||
@ -1630,7 +1641,7 @@ fn multi_video_audio_hls() {
|
||||
]);
|
||||
|
||||
let media_obj = Media::new(0, "./assets/with_audio.mp4", true);
|
||||
let media = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let enc_prefix = vec_strings![
|
||||
"-hide_banner",
|
||||
@ -1698,6 +1709,7 @@ fn multi_video_audio_hls() {
|
||||
fn multi_video_multi_audio_hls() {
|
||||
let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")));
|
||||
let player_control = PlayerControl::new();
|
||||
let playout_stat = PlayoutStatus::new();
|
||||
config.out.mode = HLS;
|
||||
config.processing.add_logo = false;
|
||||
config.processing.audio_tracks = 2;
|
||||
@ -1747,7 +1759,7 @@ fn multi_video_multi_audio_hls() {
|
||||
]);
|
||||
|
||||
let media_obj = Media::new(0, "./assets/dual_audio.mp4", true);
|
||||
let media = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
let media = gen_source(&config, media_obj, &playout_stat, &player_control, 1);
|
||||
|
||||
let enc_prefix = vec_strings![
|
||||
"-hide_banner",
|
||||
|
@ -22,6 +22,7 @@ fn timed_stop(sec: u64, proc_ctl: ProcessControl) {
|
||||
#[ignore]
|
||||
fn playlist_missing() {
|
||||
let mut config = PlayoutConfig::new(None);
|
||||
config.general.skip_validation = true;
|
||||
config.mail.recipient = "".into();
|
||||
config.processing.mode = Playlist;
|
||||
config.ingest.enable = false;
|
||||
@ -59,11 +60,141 @@ fn playlist_missing() {
|
||||
assert_eq!(playlist_date, "2023-02-08");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
#[ignore]
|
||||
fn playlist_next_missing() {
|
||||
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/with_audio.mp4".into();
|
||||
config.logging.log_to_file = false;
|
||||
config.logging.timestamp = false;
|
||||
config.logging.level = LevelFilter::Trace;
|
||||
config.out.mode = Null;
|
||||
config.out.output_count = 1;
|
||||
config.out.output_filter = None;
|
||||
config.out.output_cmd = Some(vec_strings!["-f", "null", "-"]);
|
||||
|
||||
let play_control = PlayerControl::new();
|
||||
let playout_stat = PlayoutStatus::new();
|
||||
let proc_control = ProcessControl::new();
|
||||
let proc_ctl = proc_control.clone();
|
||||
|
||||
let logging = init_logging(&config, None, None);
|
||||
CombinedLogger::init(logging).unwrap_or_default();
|
||||
|
||||
mock_time::set_mock_time("2023-02-09T23:59:45");
|
||||
|
||||
thread::spawn(move || timed_stop(28, proc_ctl));
|
||||
|
||||
player(&config, &play_control, playout_stat.clone(), proc_control);
|
||||
|
||||
let playlist_date = &*playout_stat.current_date.lock().unwrap();
|
||||
|
||||
assert_eq!(playlist_date, "2023-02-10");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
#[ignore]
|
||||
fn playlist_to_short() {
|
||||
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 = "06:00:00".into();
|
||||
config.playlist.start_sec = Some(21600.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/with_audio.mp4".into();
|
||||
config.logging.log_to_file = false;
|
||||
config.logging.timestamp = false;
|
||||
config.logging.level = log::LevelFilter::Trace;
|
||||
config.out.mode = Null;
|
||||
config.out.output_count = 1;
|
||||
config.out.output_filter = None;
|
||||
config.out.output_cmd = Some(vec_strings!["-f", "null", "-"]);
|
||||
|
||||
let play_control = PlayerControl::new();
|
||||
let playout_stat = PlayoutStatus::new();
|
||||
let proc_control = ProcessControl::new();
|
||||
let proc_ctl = proc_control.clone();
|
||||
|
||||
let logging = init_logging(&config, None, None);
|
||||
CombinedLogger::init(logging).unwrap_or_default();
|
||||
|
||||
mock_time::set_mock_time("2024-01-31T05:59:40");
|
||||
|
||||
thread::spawn(move || timed_stop(28, proc_ctl));
|
||||
|
||||
player(&config, &play_control, playout_stat.clone(), proc_control);
|
||||
|
||||
let playlist_date = &*playout_stat.current_date.lock().unwrap();
|
||||
|
||||
assert_eq!(playlist_date, "2024-01-31");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
#[ignore]
|
||||
fn playlist_init_after_list_end() {
|
||||
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 = "06:00:00".into();
|
||||
config.playlist.start_sec = Some(21600.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/with_audio.mp4".into();
|
||||
config.logging.log_to_file = false;
|
||||
config.logging.timestamp = false;
|
||||
config.logging.level = log::LevelFilter::Trace;
|
||||
config.out.mode = Null;
|
||||
config.out.output_count = 1;
|
||||
config.out.output_filter = None;
|
||||
config.out.output_cmd = Some(vec_strings!["-f", "null", "-"]);
|
||||
|
||||
let play_control = PlayerControl::new();
|
||||
let playout_stat = PlayoutStatus::new();
|
||||
let proc_control = ProcessControl::new();
|
||||
let proc_ctl = proc_control.clone();
|
||||
|
||||
let logging = init_logging(&config, None, None);
|
||||
CombinedLogger::init(logging).unwrap_or_default();
|
||||
|
||||
mock_time::set_mock_time("2024-01-31T05:59:47");
|
||||
|
||||
thread::spawn(move || timed_stop(28, proc_ctl));
|
||||
|
||||
player(&config, &play_control, playout_stat.clone(), proc_control);
|
||||
|
||||
let playlist_date = &*playout_stat.current_date.lock().unwrap();
|
||||
|
||||
assert_eq!(playlist_date, "2024-01-31");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
#[ignore]
|
||||
fn playlist_change_at_midnight() {
|
||||
let mut config = PlayoutConfig::new(None);
|
||||
config.general.skip_validation = true;
|
||||
config.mail.recipient = "".into();
|
||||
config.processing.mode = Playlist;
|
||||
config.ingest.enable = false;
|
||||
@ -106,6 +237,7 @@ fn playlist_change_at_midnight() {
|
||||
#[ignore]
|
||||
fn playlist_change_before_midnight() {
|
||||
let mut config = PlayoutConfig::new(None);
|
||||
config.general.skip_validation = true;
|
||||
config.mail.recipient = "".into();
|
||||
config.processing.mode = Playlist;
|
||||
config.ingest.enable = false;
|
||||
@ -148,6 +280,7 @@ fn playlist_change_before_midnight() {
|
||||
#[ignore]
|
||||
fn playlist_change_at_six() {
|
||||
let mut config = PlayoutConfig::new(None);
|
||||
config.general.skip_validation = true;
|
||||
config.mail.recipient = "".into();
|
||||
config.processing.mode = Playlist;
|
||||
config.ingest.enable = false;
|
||||
|
Loading…
Reference in New Issue
Block a user