simplify, cleanup and restructure code around playlist handling, add test for gen_source
This commit is contained in:
parent
8eb3ac7b0b
commit
2dc389c9f9
@ -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, DUMMY_LEN, 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,98 @@ impl CurrentProgram {
|
||||
is_terminated: Arc<AtomicBool>,
|
||||
player_control: &PlayerControl,
|
||||
) -> Self {
|
||||
let json = read_json(
|
||||
config,
|
||||
player_control,
|
||||
None,
|
||||
is_terminated.clone(),
|
||||
true,
|
||||
false,
|
||||
);
|
||||
|
||||
if let Some(file) = &json.current_file {
|
||||
info!("Read Playlist: <b><magenta>{}</></b>", file);
|
||||
}
|
||||
|
||||
*player_control.current_list.lock().unwrap() = json.program;
|
||||
*playout_stat.current_date.lock().unwrap() = json.date.clone();
|
||||
|
||||
if *playout_stat.date.lock().unwrap() != json.date {
|
||||
let data = json!({
|
||||
"time_shift": 0.0,
|
||||
"date": json.date,
|
||||
});
|
||||
|
||||
let json: String = serde_json::to_string(&data).expect("Serialize status data failed");
|
||||
if let Err(e) = fs::write(config.general.stat_file.clone(), json) {
|
||||
error!("Unable to write status file: {e}");
|
||||
};
|
||||
}
|
||||
|
||||
Self {
|
||||
config: config.clone(),
|
||||
start_sec: json.start_sec.unwrap(),
|
||||
json_mod: json.modified,
|
||||
json_path: json.current_file,
|
||||
json_date: json.date,
|
||||
start_sec: config.playlist.start_sec.unwrap(),
|
||||
end_sec: config.playlist.length_sec.unwrap(),
|
||||
json_mod: None,
|
||||
json_path: None,
|
||||
json_date: String::new(),
|
||||
player_control: player_control.clone(),
|
||||
current_node: Media::new(0, "", false),
|
||||
is_terminated,
|
||||
playout_stat,
|
||||
last_json_path: None,
|
||||
last_node_ad: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Check if playlist file got updated, and when yes we reload it and setup everything in place.
|
||||
fn check_update(&mut self, seek: bool) {
|
||||
if self.json_path.is_none() {
|
||||
// If the playlist was missing, we check here to see if it came back.
|
||||
fn get_or_update_playlist(&mut self, seek: bool) {
|
||||
let mut get_current = false;
|
||||
// let mut get_next = false;
|
||||
let mut reload = false;
|
||||
|
||||
if let Some(path) = self.json_path.clone() {
|
||||
if (Path::new(&path).is_file() || is_remote(&path))
|
||||
&& self.json_mod != modified_time(&path)
|
||||
{
|
||||
info!("Reload playlist <b><magenta>{path}</></b>");
|
||||
self.playout_stat.list_init.store(true, Ordering::SeqCst);
|
||||
get_current = true;
|
||||
reload = true;
|
||||
}
|
||||
} else {
|
||||
get_current = true;
|
||||
}
|
||||
|
||||
if get_current {
|
||||
let json = read_json(
|
||||
&self.config,
|
||||
&self.player_control,
|
||||
None,
|
||||
self.json_path.clone(),
|
||||
self.is_terminated.clone(),
|
||||
seek,
|
||||
false,
|
||||
);
|
||||
|
||||
if let Some(file) = &json.current_file {
|
||||
info!("Read Playlist: <b><magenta>{file}</></b>");
|
||||
if !reload {
|
||||
if let Some(file) = &json.current_file {
|
||||
info!("Read Playlist: <b><magenta>{file}</></b>");
|
||||
}
|
||||
}
|
||||
|
||||
self.json_path = json.current_file;
|
||||
self.json_mod = json.modified;
|
||||
*self.player_control.current_list.lock().unwrap() = json.program;
|
||||
} else if Path::new(&self.json_path.clone().unwrap()).is_file()
|
||||
|| is_remote(&self.json_path.clone().unwrap())
|
||||
{
|
||||
// If the playlist exists, we check here if it has been modified.
|
||||
let mod_time = modified_time(&self.json_path.clone().unwrap());
|
||||
|
||||
if self.json_mod != mod_time {
|
||||
// when playlist has changed, reload it
|
||||
info!(
|
||||
"Reload playlist <b><magenta>{}</></b>",
|
||||
self.json_path.clone().unwrap()
|
||||
);
|
||||
|
||||
let json = read_json(
|
||||
&self.config,
|
||||
&self.player_control,
|
||||
self.json_path.clone(),
|
||||
self.is_terminated.clone(),
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
self.json_mod = json.modified;
|
||||
*self.player_control.current_list.lock().unwrap() = json.program;
|
||||
if self.json_path.is_none() {
|
||||
trace!("missing playlist");
|
||||
|
||||
self.current_node = Media::new(0, "", false);
|
||||
self.playout_stat.list_init.store(true, Ordering::SeqCst);
|
||||
self.player_control.current_index.store(0, Ordering::SeqCst);
|
||||
}
|
||||
} else {
|
||||
// If the playlist disappears after normal run, we end up here.
|
||||
trace!("check_update, missing playlist");
|
||||
error!(
|
||||
"Playlist <b><magenta>{}</></b> not exist!",
|
||||
self.json_path.clone().unwrap()
|
||||
);
|
||||
|
||||
let media = Media::new(0, "", false);
|
||||
|
||||
self.json_mod = None;
|
||||
self.json_path = None;
|
||||
self.current_node = media.clone();
|
||||
self.playout_stat.list_init.store(true, Ordering::SeqCst);
|
||||
self.player_control.current_index.store(0, Ordering::SeqCst);
|
||||
*self.player_control.current_list.lock().unwrap() = vec![media];
|
||||
}
|
||||
}
|
||||
|
||||
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 day is past and it is time for a new playlist.
|
||||
fn check_for_next_playlist(&mut self) -> bool {
|
||||
let current_time = get_sec();
|
||||
let start_sec = self.config.playlist.start_sec.unwrap();
|
||||
let target_length = self.config.playlist.length_sec.unwrap();
|
||||
let (delta, total_delta) = get_delta(&self.config, ¤t_time);
|
||||
let mut duration = self.current_node.out;
|
||||
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 +142,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 +150,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,34 +170,21 @@ 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>");
|
||||
|
||||
self.playout_stat.list_init.store(false, Ordering::SeqCst);
|
||||
} else {
|
||||
self.playout_stat.list_init.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
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.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);
|
||||
}
|
||||
}
|
||||
|
||||
next
|
||||
@ -233,21 +196,21 @@ impl CurrentProgram {
|
||||
let current_list = self.player_control.current_list.lock().unwrap();
|
||||
|
||||
if index + 1 < current_list.len() && ¤t_list[index + 1].category == "advertisement" {
|
||||
self.current_node.next_ad = Some(true);
|
||||
self.current_node.next_ad = true;
|
||||
}
|
||||
|
||||
if index > 0
|
||||
&& index < current_list.len()
|
||||
&& ¤t_list[index - 1].category == "advertisement"
|
||||
{
|
||||
self.current_node.last_ad = Some(true);
|
||||
self.current_node.last_ad = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Get current time and when we are before start time,
|
||||
// we add full seconds of a day to it.
|
||||
fn get_current_time(&mut self) -> f64 {
|
||||
let mut time_sec = get_sec();
|
||||
let mut time_sec = time_in_seconds();
|
||||
|
||||
if time_sec < self.start_sec {
|
||||
time_sec += self.config.playlist.length_sec.unwrap()
|
||||
@ -336,9 +299,10 @@ 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));
|
||||
let list_init_seek = self.playout_stat.list_init.load(Ordering::SeqCst);
|
||||
self.get_or_update_playlist(list_init_seek);
|
||||
|
||||
if self.playout_stat.list_init.load(Ordering::SeqCst) {
|
||||
if list_init_seek {
|
||||
trace!("Init playlist, from next iterator");
|
||||
let mut init_clip_is_filler = false;
|
||||
|
||||
@ -412,29 +376,12 @@ impl Iterator for CurrentProgram {
|
||||
|
||||
return Some(self.current_node.clone());
|
||||
} else if !init_clip_is_filler {
|
||||
// TODO: maybe this should be never true
|
||||
|
||||
// fill missing length from playlist
|
||||
let mut current_time = get_sec();
|
||||
let mut current_time = time_in_seconds();
|
||||
let (_, total_delta) = get_delta(&self.config, ¤t_time);
|
||||
|
||||
trace!("Total delta on list init: {total_delta}");
|
||||
|
||||
let out = if DUMMY_LEN > total_delta {
|
||||
total_delta
|
||||
} else {
|
||||
DUMMY_LEN
|
||||
};
|
||||
|
||||
let duration = out + 0.001;
|
||||
|
||||
if self.json_path.is_some() {
|
||||
// When playlist is missing, we always need to init the playlist the next iteration.
|
||||
self.playout_stat.list_init.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
if self.config.playlist.start_sec.unwrap() > current_time {
|
||||
current_time += self.config.playlist.length_sec.unwrap() + 1.0;
|
||||
if self.start_sec > current_time {
|
||||
current_time += self.end_sec + 1.0;
|
||||
}
|
||||
|
||||
let mut nodes = self.player_control.current_list.lock().unwrap();
|
||||
@ -442,8 +389,8 @@ impl Iterator for CurrentProgram {
|
||||
|
||||
let mut media = Media::new(index, "", 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,
|
||||
@ -465,10 +412,13 @@ impl Iterator for CurrentProgram {
|
||||
return Some(self.current_node.clone());
|
||||
}
|
||||
|
||||
self.last_json_path = self.json_path.clone();
|
||||
self.last_node_ad = self.current_node.last_ad;
|
||||
self.check_for_next_playlist();
|
||||
|
||||
if self.player_control.current_index.load(Ordering::SeqCst)
|
||||
< self.player_control.current_list.lock().unwrap().len()
|
||||
{
|
||||
self.check_for_next_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,14 +446,11 @@ 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());
|
||||
|
||||
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}");
|
||||
@ -512,7 +459,7 @@ impl Iterator for CurrentProgram {
|
||||
// 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());
|
||||
self.current_node.begin = Some(time_in_seconds());
|
||||
|
||||
let out = if DUMMY_LEN > total_delta {
|
||||
total_delta
|
||||
@ -538,7 +485,7 @@ impl Iterator for CurrentProgram {
|
||||
.push(self.current_node.clone());
|
||||
self.last_next_ad();
|
||||
|
||||
self.current_node.last_ad = last_ad;
|
||||
self.current_node.last_ad = self.last_node_ad;
|
||||
self.current_node
|
||||
.add_filter(&self.config, &self.playout_stat.chain);
|
||||
|
||||
@ -549,18 +496,22 @@ impl Iterator for CurrentProgram {
|
||||
return Some(self.current_node.clone());
|
||||
}
|
||||
|
||||
let c_list = self.player_control.current_list.lock().unwrap();
|
||||
let first_node = c_list[0].clone();
|
||||
|
||||
drop(c_list);
|
||||
|
||||
// Get first clip from next playlist.
|
||||
self.player_control.current_index.store(0, Ordering::SeqCst);
|
||||
self.current_node = gen_source(
|
||||
&self.config,
|
||||
self.player_control.current_list.lock().unwrap()[0].clone(),
|
||||
first_node,
|
||||
&self.playout_stat,
|
||||
&self.player_control,
|
||||
0,
|
||||
);
|
||||
self.last_next_ad();
|
||||
self.current_node.last_ad = last_ad;
|
||||
|
||||
self.current_node.last_ad = self.last_node_ad;
|
||||
self.player_control.current_index.store(1, Ordering::SeqCst);
|
||||
|
||||
Some(self.current_node.clone())
|
||||
@ -650,26 +601,26 @@ pub fn gen_source(
|
||||
warn!("Clip is less then 1 second long (<yellow>{duration:.3}</>), adjust length.");
|
||||
|
||||
duration = 1.2;
|
||||
|
||||
if node.seek > 1.0 {
|
||||
node.seek -= 1.0;
|
||||
} else {
|
||||
node.out += 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
trace!("Clip out: {duration}, duration: {}", node.duration);
|
||||
trace!("Clip new length: {duration}, duration: {}", node.duration);
|
||||
|
||||
if node.probe.is_none() && !node.source.is_empty() {
|
||||
node.add_probe(true);
|
||||
if let Err(e) = node.add_probe(true) {
|
||||
trace!("{e:?}");
|
||||
};
|
||||
} else {
|
||||
trace!("Node has a probe...")
|
||||
}
|
||||
|
||||
// separate if condition, because of node.add_probe() in last condition
|
||||
if node.probe.is_some() {
|
||||
if node.out - node.seek < 1.0 {
|
||||
if node.seek > 1.0 {
|
||||
node.seek -= 1.0;
|
||||
} else {
|
||||
node.out += 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
if node
|
||||
.source
|
||||
.rsplit_once('.')
|
||||
@ -705,7 +656,9 @@ pub fn gen_source(
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -753,9 +706,7 @@ pub fn gen_source(
|
||||
node.cmd = Some(cmd);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
|
||||
Err(_) => {
|
||||
// Create colored placeholder.
|
||||
let (source, cmd) = gen_dummy(config, duration);
|
||||
node.source = source;
|
||||
|
@ -212,7 +212,10 @@ fn control_back(
|
||||
let mut data_map = Map::new();
|
||||
let mut media = current_list[index - 2].clone();
|
||||
play_control.current_index.fetch_sub(2, Ordering::SeqCst);
|
||||
media.add_probe(false);
|
||||
|
||||
if let Err(e) = media.add_probe(false) {
|
||||
error!("{e:?}");
|
||||
};
|
||||
|
||||
let (delta, _) = get_delta(config, &media.begin.unwrap_or(0.0));
|
||||
*time_shift = delta;
|
||||
@ -258,7 +261,10 @@ fn control_next(
|
||||
|
||||
let mut data_map = Map::new();
|
||||
let mut media = current_list[index].clone();
|
||||
media.add_probe(false);
|
||||
|
||||
if let Err(e) = media.add_probe(false) {
|
||||
error!("{e:?}");
|
||||
};
|
||||
|
||||
let (delta, _) = get_delta(config, &media.begin.unwrap_or(0.0));
|
||||
*time_shift = delta;
|
||||
|
@ -14,7 +14,7 @@ pub use arg_parse::Args;
|
||||
use ffplayout_lib::{
|
||||
filter::Filters,
|
||||
utils::{
|
||||
config::Template, errors::ProcError, get_sec, parse_log_level_filter, sec_to_time,
|
||||
config::Template, errors::ProcError, parse_log_level_filter, sec_to_time, time_in_seconds,
|
||||
time_to_sec, Media, OutputMode::*, PlayoutConfig, PlayoutStatus, ProcessMode::*,
|
||||
},
|
||||
vec_strings,
|
||||
@ -254,7 +254,7 @@ pub fn get_data_map(
|
||||
server_is_running: bool,
|
||||
) -> Map<String, Value> {
|
||||
let mut data_map = Map::new();
|
||||
let current_time = get_sec();
|
||||
let current_time = time_in_seconds();
|
||||
let shift = *playout_stat.time_shift.lock().unwrap();
|
||||
let begin = media.begin.unwrap_or(0.0) - shift;
|
||||
|
||||
|
@ -304,11 +304,11 @@ fn overlay(node: &mut Media, chain: &mut Filters, config: &PlayoutConfig) {
|
||||
config.processing.logo.replace('\\', "/").replace(':', "\\\\:"), config.processing.logo_opacity, config.processing.logo_filter
|
||||
);
|
||||
|
||||
if node.last_ad.unwrap_or(false) {
|
||||
if node.last_ad {
|
||||
logo_chain.push_str(",fade=in:st=0:d=1.0:alpha=1")
|
||||
}
|
||||
|
||||
if node.next_ad.unwrap_or(false) {
|
||||
if node.next_ad {
|
||||
logo_chain.push_str(&format!(
|
||||
",fade=out:st={}:d=1.0:alpha=1",
|
||||
node.out - node.seek - 1.0
|
||||
|
@ -9,7 +9,7 @@ use simplelog::*;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::utils::{
|
||||
controller::PlayerControl, get_sec, include_file_extension, Media, PlayoutConfig,
|
||||
controller::PlayerControl, include_file_extension, time_in_seconds, Media, PlayoutConfig,
|
||||
};
|
||||
|
||||
/// Folder Sources
|
||||
@ -136,10 +136,10 @@ impl Iterator for FolderSource {
|
||||
{
|
||||
let i = self.player_control.current_index.load(Ordering::SeqCst);
|
||||
self.current_node = self.player_control.current_list.lock().unwrap()[i].clone();
|
||||
self.current_node.add_probe(false);
|
||||
let _ = self.current_node.add_probe(false).ok();
|
||||
self.current_node
|
||||
.add_filter(&self.config, &self.filter_chain);
|
||||
self.current_node.begin = Some(get_sec());
|
||||
self.current_node.begin = Some(time_in_seconds());
|
||||
|
||||
self.player_control
|
||||
.current_index
|
||||
@ -162,10 +162,10 @@ impl Iterator for FolderSource {
|
||||
}
|
||||
|
||||
self.current_node = self.player_control.current_list.lock().unwrap()[0].clone();
|
||||
self.current_node.add_probe(false);
|
||||
let _ = self.current_node.add_probe(false).ok();
|
||||
self.current_node
|
||||
.add_filter(&self.config, &self.filter_chain);
|
||||
self.current_node.begin = Some(get_sec());
|
||||
self.current_node.begin = Some(time_in_seconds());
|
||||
|
||||
self.player_control.current_index.store(1, Ordering::SeqCst);
|
||||
|
||||
@ -192,7 +192,9 @@ pub fn fill_filler_list(
|
||||
let mut media = Media::new(index, &entry.path().to_string_lossy(), false);
|
||||
|
||||
if player_control.is_none() {
|
||||
media.add_probe(false);
|
||||
if let Err(e) = media.add_probe(false) {
|
||||
error!("{e:?}");
|
||||
};
|
||||
}
|
||||
|
||||
filler_list.push(media);
|
||||
@ -217,7 +219,9 @@ pub fn fill_filler_list(
|
||||
let mut media = Media::new(0, &config.storage.filler.to_string_lossy(), false);
|
||||
|
||||
if player_control.is_none() {
|
||||
media.add_probe(false);
|
||||
if let Err(e) = media.add_probe(false) {
|
||||
error!("{e:?}");
|
||||
};
|
||||
}
|
||||
|
||||
filler_list.push(media);
|
||||
|
@ -73,8 +73,8 @@ fn set_defaults(
|
||||
for (i, item) in playlist.program.iter_mut().enumerate() {
|
||||
item.begin = Some(start_sec);
|
||||
item.index = Some(i);
|
||||
item.last_ad = Some(false);
|
||||
item.next_ad = Some(false);
|
||||
item.last_ad = false;
|
||||
item.next_ad = false;
|
||||
item.process = Some(true);
|
||||
item.filter = None;
|
||||
|
||||
@ -115,8 +115,8 @@ fn loop_playlist(
|
||||
probe_audio: item.probe_audio.clone(),
|
||||
process: Some(true),
|
||||
unit: Decoder,
|
||||
last_ad: Some(false),
|
||||
next_ad: Some(false),
|
||||
last_ad: false,
|
||||
next_ad: false,
|
||||
filter: None,
|
||||
custom_filter: String::new(),
|
||||
};
|
||||
|
@ -173,9 +173,17 @@ pub fn validate_playlist(
|
||||
let pos = index + 1;
|
||||
|
||||
if item.audio.is_empty() {
|
||||
item.add_probe(false);
|
||||
} else {
|
||||
item.add_probe(true);
|
||||
if let Err(e) = item.add_probe(false) {
|
||||
error!(
|
||||
"[Validation] Error on position <yellow>{pos:0>3}</> <yellow>{}</>: {e}",
|
||||
sec_to_time(begin)
|
||||
);
|
||||
}
|
||||
} else if let Err(e) = item.add_probe(true) {
|
||||
error!(
|
||||
"[Validation] Error on position <yellow>{pos:0>3}</> <yellow>{}</>: {e}",
|
||||
sec_to_time(begin)
|
||||
);
|
||||
}
|
||||
|
||||
if item.probe.is_some() {
|
||||
@ -183,7 +191,7 @@ pub fn validate_playlist(
|
||||
error!("{e}");
|
||||
} else if config.general.validate {
|
||||
debug!(
|
||||
"Source at <yellow>{}</>, seems fine: <b><magenta>{}</></b>",
|
||||
"[Validation] Source at <yellow>{}</>, seems fine: <b><magenta>{}</></b>",
|
||||
sec_to_time(begin),
|
||||
item.source
|
||||
)
|
||||
@ -200,7 +208,7 @@ pub fn validate_playlist(
|
||||
|
||||
if !is_close(o.duration, probe_duration, 1.2) {
|
||||
error!(
|
||||
"File duration (at: <yellow>{}</>) differs from playlist value. File duration: <yellow>{}</>, playlist value: <yellow>{}</>, source <b><magenta>{}</></b>",
|
||||
"[Validation] File duration (at: <yellow>{}</>) differs from playlist value. File duration: <yellow>{}</>, playlist value: <yellow>{}</>, source <b><magenta>{}</></b>",
|
||||
sec_to_time(o.begin.unwrap_or_default()), sec_to_time(probe_duration), sec_to_time(o.duration), o.source
|
||||
);
|
||||
|
||||
@ -214,12 +222,6 @@ pub fn validate_playlist(
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
error!(
|
||||
"Error on position <yellow>{pos:0>3}</> <yellow>{}</>, file: <b><magenta>{}</></b>",
|
||||
sec_to_time(begin),
|
||||
item.source
|
||||
);
|
||||
}
|
||||
|
||||
begin += item.out - item.seek;
|
||||
@ -227,7 +229,7 @@ pub fn validate_playlist(
|
||||
|
||||
if !config.playlist.infinit && length > begin + 1.2 {
|
||||
error!(
|
||||
"Playlist from <yellow>{date}</> not long enough, <yellow>{}</> needed!",
|
||||
"[Validation] Playlist from <yellow>{date}</> not long enough, <yellow>{}</> needed!",
|
||||
sec_to_time(length - begin),
|
||||
);
|
||||
}
|
||||
|
@ -104,10 +104,10 @@ pub struct Media {
|
||||
pub probe_audio: Option<MediaProbe>,
|
||||
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub last_ad: Option<bool>,
|
||||
pub last_ad: bool,
|
||||
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub next_ad: Option<bool>,
|
||||
pub next_ad: bool,
|
||||
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub process: Option<bool>,
|
||||
@ -122,18 +122,15 @@ impl Media {
|
||||
let mut probe = None;
|
||||
|
||||
if do_probe && (is_remote(src) || Path::new(src).is_file()) {
|
||||
match MediaProbe::new(src) {
|
||||
Ok(p) => {
|
||||
probe = Some(p.clone());
|
||||
if let Ok(p) = MediaProbe::new(src) {
|
||||
probe = Some(p.clone());
|
||||
|
||||
duration = p
|
||||
.format
|
||||
.duration
|
||||
.unwrap_or_default()
|
||||
.parse()
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Err(e) => error!("{e}"),
|
||||
duration = p
|
||||
.format
|
||||
.duration
|
||||
.unwrap_or_default()
|
||||
.parse()
|
||||
.unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,14 +149,16 @@ impl Media {
|
||||
custom_filter: String::new(),
|
||||
probe,
|
||||
probe_audio: None,
|
||||
last_ad: Some(false),
|
||||
next_ad: Some(false),
|
||||
last_ad: false,
|
||||
next_ad: false,
|
||||
process: Some(true),
|
||||
unit: Decoder,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_probe(&mut self, check_audio: bool) {
|
||||
pub fn add_probe(&mut self, check_audio: bool) -> Result<(), String> {
|
||||
let mut errors = vec![];
|
||||
|
||||
if self.probe.is_none() {
|
||||
match MediaProbe::new(&self.source) {
|
||||
Ok(probe) => {
|
||||
@ -178,7 +177,7 @@ impl Media {
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => error!("{e}"),
|
||||
Err(e) => errors.push(e.to_string()),
|
||||
};
|
||||
|
||||
if check_audio && Path::new(&self.audio).is_file() {
|
||||
@ -194,10 +193,16 @@ impl Media {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
Err(e) => error!("{e}"),
|
||||
Err(e) => errors.push(e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
return Err(errors.join(", "));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_filter(
|
||||
@ -274,7 +279,9 @@ impl MediaProbe {
|
||||
}
|
||||
Err(e) => {
|
||||
if !Path::new(input).is_file() && !is_remote(input) {
|
||||
Err(ProcError::Custom(format!("File '{input}' not exist!")))
|
||||
Err(ProcError::Custom(format!(
|
||||
"File '<b><magenta>{input}</></b>' not exist!"
|
||||
)))
|
||||
} else {
|
||||
Err(ProcError::Ffprobe(e))
|
||||
}
|
||||
@ -337,7 +344,7 @@ pub fn write_status(config: &PlayoutConfig, date: &str, shift: f64) {
|
||||
// }
|
||||
|
||||
/// Get current time in seconds.
|
||||
pub fn get_sec() -> f64 {
|
||||
pub fn time_in_seconds() -> f64 {
|
||||
let local: DateTime<Local> = time_now();
|
||||
|
||||
(local.hour() * 3600 + local.minute() * 60 + local.second()) as f64
|
||||
@ -351,11 +358,11 @@ pub fn get_sec() -> f64 {
|
||||
pub fn get_date(seek: bool, start: f64, get_next: bool) -> String {
|
||||
let local: DateTime<Local> = time_now();
|
||||
|
||||
if seek && start > get_sec() {
|
||||
if seek && start > time_in_seconds() {
|
||||
return (local - Duration::days(1)).format("%Y-%m-%d").to_string();
|
||||
}
|
||||
|
||||
if start == 0.0 && get_next && get_sec() > 86397.9 {
|
||||
if start == 0.0 && get_next && time_in_seconds() > 86397.9 {
|
||||
return (local + Duration::days(1)).format("%Y-%m-%d").to_string();
|
||||
}
|
||||
|
||||
@ -401,7 +408,7 @@ pub fn modified_time(path: &str) -> Option<String> {
|
||||
/// Convert a formatted time string to seconds.
|
||||
pub fn time_to_sec(time_str: &str) -> f64 {
|
||||
if matches!(time_str, "now" | "" | "none") || !time_str.contains(':') {
|
||||
return get_sec();
|
||||
return time_in_seconds();
|
||||
}
|
||||
|
||||
let t: Vec<&str> = time_str.split(':').collect();
|
||||
@ -452,7 +459,7 @@ pub fn sum_durations(clip_list: &Vec<Media>) -> f64 {
|
||||
///
|
||||
/// We also get here the global delta between clip start and time when a new playlist should start.
|
||||
pub fn get_delta(config: &PlayoutConfig, begin: &f64) -> (f64, f64) {
|
||||
let mut current_time = get_sec();
|
||||
let mut current_time = time_in_seconds();
|
||||
let start = config.playlist.start_sec.unwrap();
|
||||
let length = time_to_sec(&config.playlist.length);
|
||||
let mut target_length = 86400.0;
|
||||
|
@ -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,81 @@ fn timed_stop(sec: u64, proc_ctl: ProcessControl) {
|
||||
proc_ctl.stop_all();
|
||||
}
|
||||
|
||||
#[test]
|
||||
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/with_audio.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/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/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/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/with_audio.mp4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
#[ignore]
|
||||
|
Loading…
Reference in New Issue
Block a user