support filler folder
This commit is contained in:
parent
52d44e7a2a
commit
98d1d5d606
@ -95,11 +95,11 @@ playlist:
|
||||
infinit: false
|
||||
|
||||
storage:
|
||||
help_text: Play ordered or randomly files from path. 'filler_clip' is for fill
|
||||
the end to reach 24 hours, it will loop when is necessary. 'extensions' search
|
||||
help_text: 'filler' is for playing instead of a missing file or fill the end to reach 24
|
||||
hours, can be a file or folder, it will loop when is necessary. 'extensions' search
|
||||
only files with this extension. Set 'shuffle' to 'true' to pick files randomly.
|
||||
path: "/var/lib/ffplayout/tv-media"
|
||||
filler_clip: "/var/lib/ffplayout/tv-media/filler/filler.mp4"
|
||||
filler: "/var/lib/ffplayout/tv-media/filler/filler.mp4"
|
||||
extensions:
|
||||
- "mp4"
|
||||
- "mkv"
|
||||
|
@ -17,7 +17,7 @@ use notify::{
|
||||
use notify_debouncer_full::new_debouncer;
|
||||
use simplelog::*;
|
||||
|
||||
use ffplayout_lib::utils::{include_file, Media, PlayoutConfig};
|
||||
use ffplayout_lib::utils::{include_file_extension, Media, PlayoutConfig};
|
||||
|
||||
/// Create a watcher, which monitor file changes.
|
||||
/// When a change is register, update the current file list.
|
||||
@ -52,7 +52,7 @@ pub fn watchman(
|
||||
Create(CreateKind::File) | Modify(ModifyKind::Name(RenameMode::To)) => {
|
||||
let new_path = &event.paths[0];
|
||||
|
||||
if new_path.is_file() && include_file(config.clone(), new_path) {
|
||||
if new_path.is_file() && include_file_extension(&config, new_path) {
|
||||
let index = sources.lock().unwrap().len();
|
||||
let media = Media::new(index, &new_path.to_string_lossy(), false);
|
||||
|
||||
@ -63,7 +63,7 @@ pub fn watchman(
|
||||
Remove(RemoveKind::File) | Modify(ModifyKind::Name(RenameMode::From)) => {
|
||||
let old_path = &event.paths[0];
|
||||
|
||||
if !old_path.is_file() && include_file(config.clone(), old_path) {
|
||||
if !old_path.is_file() && include_file_extension(&config, old_path) {
|
||||
sources
|
||||
.lock()
|
||||
.unwrap()
|
||||
@ -83,7 +83,7 @@ pub fn watchman(
|
||||
let media = Media::new(index, &new_path.to_string_lossy(), false);
|
||||
media_list[index] = media;
|
||||
info!("Move file: <b><magenta>{old_path:?}</></b> to <b><magenta>{new_path:?}</></b>");
|
||||
} else if include_file(config.clone(), new_path) {
|
||||
} else if include_file_extension(&config, new_path) {
|
||||
let index = media_list.len();
|
||||
let media = Media::new(index, &new_path.to_string_lossy(), false);
|
||||
|
||||
|
@ -1,8 +1,5 @@
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicUsize},
|
||||
Arc, Mutex,
|
||||
},
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
thread,
|
||||
};
|
||||
|
||||
@ -18,13 +15,12 @@ pub use folder::watchman;
|
||||
pub use ingest::ingest_server;
|
||||
pub use playlist::CurrentProgram;
|
||||
|
||||
use ffplayout_lib::utils::folder::FolderSource;
|
||||
use ffplayout_lib::utils::{controller::PlayerControl, folder::FolderSource};
|
||||
|
||||
/// Create a source iterator from playlist, or from folder.
|
||||
pub fn source_generator(
|
||||
config: PlayoutConfig,
|
||||
current_list: Arc<Mutex<Vec<Media>>>,
|
||||
index: Arc<AtomicUsize>,
|
||||
player_control: &PlayerControl,
|
||||
playout_stat: PlayoutStatus,
|
||||
is_terminated: Arc<AtomicBool>,
|
||||
) -> Box<dyn Iterator<Item = Media>> {
|
||||
@ -37,8 +33,8 @@ pub fn source_generator(
|
||||
);
|
||||
|
||||
let config_clone = config.clone();
|
||||
let folder_source = FolderSource::new(&config, playout_stat.chain, current_list, index);
|
||||
let node_clone = folder_source.nodes.clone();
|
||||
let folder_source = FolderSource::new(&config, playout_stat.chain, player_control);
|
||||
let node_clone = folder_source.player_control.current_list.clone();
|
||||
|
||||
// Spawn a thread to monitor folder for file changes.
|
||||
thread::spawn(move || watchman(config_clone, is_terminated.clone(), node_clone));
|
||||
@ -47,8 +43,7 @@ pub fn source_generator(
|
||||
}
|
||||
Playlist => {
|
||||
info!("Playout in playlist mode");
|
||||
let program =
|
||||
CurrentProgram::new(&config, playout_stat, is_terminated, current_list, index);
|
||||
let program = CurrentProgram::new(&config, playout_stat, is_terminated, player_control);
|
||||
|
||||
Box::new(program) as Box<dyn Iterator<Item = Media>>
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use std::{
|
||||
fs,
|
||||
path::Path,
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
};
|
||||
@ -11,9 +11,9 @@ use serde_json::json;
|
||||
use simplelog::*;
|
||||
|
||||
use ffplayout_lib::utils::{
|
||||
check_sync, gen_dummy, get_delta, get_sec, is_close, is_remote, json_serializer::read_json,
|
||||
loop_filler, loop_image, modified_time, seek_and_length, valid_source, Media, MediaProbe,
|
||||
PlayoutConfig, PlayoutStatus, DUMMY_LEN, IMAGE_FORMAT,
|
||||
check_sync, 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,
|
||||
valid_source, Media, MediaProbe, PlayoutConfig, PlayoutStatus, DUMMY_LEN, IMAGE_FORMAT,
|
||||
};
|
||||
|
||||
/// Struct for current playlist.
|
||||
@ -26,9 +26,8 @@ pub struct CurrentProgram {
|
||||
json_mod: Option<String>,
|
||||
json_path: Option<String>,
|
||||
json_date: String,
|
||||
pub nodes: Arc<Mutex<Vec<Media>>>,
|
||||
player_control: PlayerControl,
|
||||
current_node: Media,
|
||||
index: Arc<AtomicUsize>,
|
||||
is_terminated: Arc<AtomicBool>,
|
||||
playout_stat: PlayoutStatus,
|
||||
}
|
||||
@ -38,8 +37,7 @@ impl CurrentProgram {
|
||||
config: &PlayoutConfig,
|
||||
playout_stat: PlayoutStatus,
|
||||
is_terminated: Arc<AtomicBool>,
|
||||
current_list: Arc<Mutex<Vec<Media>>>,
|
||||
global_index: Arc<AtomicUsize>,
|
||||
player_control: &PlayerControl,
|
||||
) -> Self {
|
||||
let json = read_json(config, None, is_terminated.clone(), true, 0.0);
|
||||
|
||||
@ -47,7 +45,7 @@ impl CurrentProgram {
|
||||
info!("Read Playlist: <b><magenta>{}</></b>", file);
|
||||
}
|
||||
|
||||
*current_list.lock().unwrap() = json.program;
|
||||
*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 {
|
||||
@ -68,9 +66,8 @@ impl CurrentProgram {
|
||||
json_mod: json.modified,
|
||||
json_path: json.current_file,
|
||||
json_date: json.date,
|
||||
nodes: current_list,
|
||||
player_control: player_control.clone(),
|
||||
current_node: Media::new(0, "", false),
|
||||
index: global_index,
|
||||
is_terminated,
|
||||
playout_stat,
|
||||
}
|
||||
@ -87,7 +84,7 @@ impl CurrentProgram {
|
||||
|
||||
self.json_path = json.current_file;
|
||||
self.json_mod = json.modified;
|
||||
*self.nodes.lock().unwrap() = json.program;
|
||||
*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())
|
||||
{
|
||||
@ -109,7 +106,7 @@ impl CurrentProgram {
|
||||
);
|
||||
|
||||
self.json_mod = json.modified;
|
||||
*self.nodes.lock().unwrap() = json.program;
|
||||
*self.player_control.current_list.lock().unwrap() = json.program;
|
||||
|
||||
self.playout_stat.list_init.store(true, Ordering::SeqCst);
|
||||
}
|
||||
@ -120,14 +117,15 @@ impl CurrentProgram {
|
||||
);
|
||||
let mut media = Media::new(0, "", false);
|
||||
media.begin = Some(get_sec());
|
||||
// TODO: Works not well with filler folder
|
||||
media.duration = DUMMY_LEN;
|
||||
media.out = DUMMY_LEN;
|
||||
|
||||
self.json_path = None;
|
||||
*self.nodes.lock().unwrap() = vec![media.clone()];
|
||||
*self.player_control.current_list.lock().unwrap() = vec![media.clone()];
|
||||
self.current_node = media;
|
||||
self.playout_stat.list_init.store(true, Ordering::SeqCst);
|
||||
self.index.store(0, Ordering::SeqCst);
|
||||
self.player_control.current_index.store(0, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,7 +143,9 @@ impl CurrentProgram {
|
||||
|
||||
let mut next_start = self.current_node.begin.unwrap() - start_sec + duration + delta;
|
||||
|
||||
if self.index.load(Ordering::SeqCst) == self.nodes.lock().unwrap().len() - 1 {
|
||||
if self.player_control.current_index.load(Ordering::SeqCst)
|
||||
== self.player_control.current_list.lock().unwrap().len() - 1
|
||||
{
|
||||
next_start += self.config.general.stop_threshold;
|
||||
}
|
||||
|
||||
@ -182,8 +182,8 @@ impl CurrentProgram {
|
||||
self.json_path = json.current_file.clone();
|
||||
self.json_mod = json.modified;
|
||||
self.json_date = json.date;
|
||||
*self.nodes.lock().unwrap() = json.program;
|
||||
self.index.store(0, Ordering::SeqCst);
|
||||
*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);
|
||||
@ -193,8 +193,8 @@ impl CurrentProgram {
|
||||
|
||||
// Check if last and/or next clip is a advertisement.
|
||||
fn last_next_ad(&mut self) {
|
||||
let index = self.index.load(Ordering::SeqCst);
|
||||
let current_list = self.nodes.lock().unwrap();
|
||||
let index = self.player_control.current_index.load(Ordering::SeqCst);
|
||||
let current_list = self.player_control.current_list.lock().unwrap();
|
||||
|
||||
if index + 1 < current_list.len() && ¤t_list[index + 1].category == "advertisement" {
|
||||
self.current_node.next_ad = Some(true);
|
||||
@ -233,10 +233,17 @@ impl CurrentProgram {
|
||||
time_sec += *shift;
|
||||
}
|
||||
|
||||
for (i, item) in self.nodes.lock().unwrap().iter_mut().enumerate() {
|
||||
for (i, item) in self
|
||||
.player_control
|
||||
.current_list
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
{
|
||||
if item.begin.unwrap() + item.out - item.seek > time_sec {
|
||||
self.playout_stat.list_init.store(false, Ordering::SeqCst);
|
||||
self.index.store(i, Ordering::SeqCst);
|
||||
self.player_control.current_index.store(i, Ordering::SeqCst);
|
||||
|
||||
break;
|
||||
}
|
||||
@ -249,8 +256,11 @@ impl CurrentProgram {
|
||||
|
||||
if !self.playout_stat.list_init.load(Ordering::SeqCst) {
|
||||
let time_sec = self.get_current_time();
|
||||
let index = self.index.fetch_add(1, Ordering::SeqCst);
|
||||
let nodes = self.nodes.lock().unwrap();
|
||||
let index = self
|
||||
.player_control
|
||||
.current_index
|
||||
.fetch_add(1, Ordering::SeqCst);
|
||||
let nodes = self.player_control.current_list.lock().unwrap();
|
||||
let last_index = nodes.len() - 1;
|
||||
|
||||
// de-instance node to preserve original values in list
|
||||
@ -261,6 +271,7 @@ impl CurrentProgram {
|
||||
&self.config,
|
||||
node_clone,
|
||||
&self.playout_stat.chain,
|
||||
&self.player_control,
|
||||
last_index,
|
||||
);
|
||||
}
|
||||
@ -284,9 +295,10 @@ impl Iterator for CurrentProgram {
|
||||
// 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.nodes.lock().unwrap().len() - 1;
|
||||
self.current_node = self.nodes.lock().unwrap()[last_index].clone();
|
||||
let new_node = self.nodes.lock().unwrap()[last_index].clone();
|
||||
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() + new_node.duration;
|
||||
trace!("Init playlist after playlist end");
|
||||
|
||||
@ -312,7 +324,7 @@ impl Iterator for CurrentProgram {
|
||||
current_time += self.config.playlist.length_sec.unwrap() + 1.0;
|
||||
}
|
||||
|
||||
let mut nodes = self.nodes.lock().unwrap();
|
||||
let mut nodes = self.player_control.current_list.lock().unwrap();
|
||||
let index = nodes.len();
|
||||
|
||||
let mut media = Media::new(index, "", false);
|
||||
@ -320,11 +332,18 @@ impl Iterator for CurrentProgram {
|
||||
media.duration = duration;
|
||||
media.out = duration;
|
||||
|
||||
self.current_node =
|
||||
gen_source(&self.config, media, &self.playout_stat.chain, last_index);
|
||||
self.current_node = gen_source(
|
||||
&self.config,
|
||||
media,
|
||||
&self.playout_stat.chain,
|
||||
&self.player_control,
|
||||
last_index,
|
||||
);
|
||||
|
||||
nodes.push(self.current_node.clone());
|
||||
self.index.store(nodes.len(), Ordering::SeqCst);
|
||||
self.player_control
|
||||
.current_index
|
||||
.store(nodes.len(), Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
@ -333,11 +352,13 @@ impl Iterator for CurrentProgram {
|
||||
return Some(self.current_node.clone());
|
||||
}
|
||||
|
||||
if self.index.load(Ordering::SeqCst) < self.nodes.lock().unwrap().len() {
|
||||
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.index.load(Ordering::SeqCst);
|
||||
let nodes = self.nodes.lock().unwrap();
|
||||
let index = self.player_control.current_index.load(Ordering::SeqCst);
|
||||
let nodes = self.player_control.current_list.lock().unwrap();
|
||||
let last_index = nodes.len() - 1;
|
||||
|
||||
if index == last_index {
|
||||
@ -349,12 +370,15 @@ impl Iterator for CurrentProgram {
|
||||
&self.config,
|
||||
is_last,
|
||||
&self.playout_stat,
|
||||
&self.player_control,
|
||||
last_index,
|
||||
);
|
||||
|
||||
drop(nodes);
|
||||
self.last_next_ad();
|
||||
self.index.fetch_add(1, Ordering::SeqCst);
|
||||
self.player_control
|
||||
.current_index
|
||||
.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
Some(self.current_node.clone())
|
||||
} else {
|
||||
@ -370,7 +394,7 @@ impl Iterator for CurrentProgram {
|
||||
{
|
||||
// Test if playlist is to early finish,
|
||||
// and if we have to fill it with a placeholder.
|
||||
let index = self.index.load(Ordering::SeqCst);
|
||||
let index = self.player_control.current_index.load(Ordering::SeqCst);
|
||||
self.current_node = Media::new(index, "", false);
|
||||
self.current_node.begin = Some(get_sec());
|
||||
let mut duration = total_delta.abs();
|
||||
@ -384,33 +408,40 @@ impl Iterator for CurrentProgram {
|
||||
&self.config,
|
||||
self.current_node.clone(),
|
||||
&self.playout_stat.chain,
|
||||
&self.player_control,
|
||||
0,
|
||||
);
|
||||
self.nodes.lock().unwrap().push(self.current_node.clone());
|
||||
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.index.fetch_add(1, Ordering::SeqCst);
|
||||
self.player_control
|
||||
.current_index
|
||||
.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
return Some(self.current_node.clone());
|
||||
}
|
||||
|
||||
// Get first clip from next playlist.
|
||||
self.index.store(0, Ordering::SeqCst);
|
||||
let last_index = self.nodes.lock().unwrap().len() - 1;
|
||||
self.player_control.current_index.store(0, Ordering::SeqCst);
|
||||
self.current_node = gen_source(
|
||||
&self.config,
|
||||
self.nodes.lock().unwrap()[0].clone(),
|
||||
self.player_control.current_list.lock().unwrap()[0].clone(),
|
||||
&self.playout_stat.chain,
|
||||
last_index,
|
||||
&self.player_control,
|
||||
0,
|
||||
);
|
||||
self.last_next_ad();
|
||||
self.current_node.last_ad = last_ad;
|
||||
|
||||
self.index.store(1, Ordering::SeqCst);
|
||||
self.player_control.current_index.store(1, Ordering::SeqCst);
|
||||
|
||||
Some(self.current_node.clone())
|
||||
}
|
||||
@ -426,6 +457,7 @@ fn timed_source(
|
||||
config: &PlayoutConfig,
|
||||
last: bool,
|
||||
playout_stat: &PlayoutStatus,
|
||||
player_control: &PlayerControl,
|
||||
last_index: usize,
|
||||
) -> Media {
|
||||
let (delta, total_delta) = get_delta(config, &node.begin.unwrap());
|
||||
@ -463,11 +495,24 @@ 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, last_index);
|
||||
new_node = gen_source(
|
||||
config,
|
||||
node,
|
||||
&playout_stat.chain,
|
||||
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 {
|
||||
new_node = handle_list_end(config, node, total_delta, &playout_stat.chain, last_index);
|
||||
new_node = handle_list_end(
|
||||
config,
|
||||
node,
|
||||
total_delta,
|
||||
&playout_stat.chain,
|
||||
player_control,
|
||||
last_index,
|
||||
);
|
||||
}
|
||||
|
||||
new_node
|
||||
@ -478,6 +523,7 @@ pub fn gen_source(
|
||||
config: &PlayoutConfig,
|
||||
mut node: Media,
|
||||
filter_chain: &Option<Arc<Mutex<Vec<String>>>>,
|
||||
player_control: &PlayerControl,
|
||||
last_index: usize,
|
||||
) -> Media {
|
||||
let duration = node.out - node.seek;
|
||||
@ -507,38 +553,63 @@ pub fn gen_source(
|
||||
error!("Source not found: <b><magenta>\"{}\"</></b>", node.source);
|
||||
}
|
||||
|
||||
warn!("Generate filler with <yellow>{duration:.2}</> seconds length!");
|
||||
|
||||
let probe = MediaProbe::new(&config.storage.filler_clip);
|
||||
|
||||
if config
|
||||
.storage
|
||||
.filler_clip
|
||||
.rsplit_once('.')
|
||||
.map(|(_, e)| e.to_lowercase())
|
||||
.filter(|c| IMAGE_FORMAT.contains(&c.as_str()))
|
||||
.is_some()
|
||||
if Path::new(&config.storage.filler).is_dir()
|
||||
&& !player_control.filler_list.lock().unwrap().is_empty()
|
||||
{
|
||||
node.source = config.storage.filler_clip.clone();
|
||||
node.cmd = Some(loop_image(&node));
|
||||
node.probe = Some(probe);
|
||||
} else if let Some(length) = probe
|
||||
.clone()
|
||||
.format
|
||||
.and_then(|f| f.duration)
|
||||
.and_then(|d| d.parse::<f64>().ok())
|
||||
{
|
||||
// create placeholder from config filler.
|
||||
node.source = config.storage.filler_clip.clone();
|
||||
node.duration = length;
|
||||
node.out = duration;
|
||||
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();
|
||||
|
||||
if filler_index == player_control.filler_list.lock().unwrap().len() - 1 {
|
||||
player_control.filler_index.store(0, Ordering::SeqCst)
|
||||
}
|
||||
|
||||
filler_media.add_probe();
|
||||
|
||||
if filler_media.duration > duration {
|
||||
filler_media.out = duration;
|
||||
}
|
||||
|
||||
warn!(
|
||||
"Generate filler with <yellow>{:.2}</> seconds length!",
|
||||
filler_media.out
|
||||
);
|
||||
|
||||
node = filler_media;
|
||||
node.cmd = Some(loop_filler(&node));
|
||||
node.probe = Some(probe);
|
||||
} else {
|
||||
// create colored placeholder.
|
||||
let (source, cmd) = gen_dummy(config, duration);
|
||||
node.source = source;
|
||||
node.cmd = Some(cmd);
|
||||
warn!("Generate filler with <yellow>{duration:.2}</> seconds length!");
|
||||
|
||||
let probe = MediaProbe::new(&config.storage.filler);
|
||||
|
||||
if config
|
||||
.storage
|
||||
.filler
|
||||
.rsplit_once('.')
|
||||
.map(|(_, e)| e.to_lowercase())
|
||||
.filter(|c| IMAGE_FORMAT.contains(&c.as_str()))
|
||||
.is_some()
|
||||
{
|
||||
node.source = config.storage.filler.clone();
|
||||
node.cmd = Some(loop_image(&node));
|
||||
node.probe = Some(probe);
|
||||
} else if let Some(length) = probe
|
||||
.clone()
|
||||
.format
|
||||
.and_then(|f| f.duration)
|
||||
.and_then(|d| d.parse::<f64>().ok())
|
||||
{
|
||||
// Create placeholder from config filler.
|
||||
node.source = config.storage.filler.clone();
|
||||
node.duration = length;
|
||||
node.out = duration;
|
||||
node.cmd = Some(loop_filler(&node));
|
||||
node.probe = Some(probe);
|
||||
} else {
|
||||
// Create colored placeholder.
|
||||
let (source, cmd) = gen_dummy(config, duration);
|
||||
node.source = source;
|
||||
node.cmd = Some(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -562,6 +633,7 @@ fn handle_list_init(
|
||||
config: &PlayoutConfig,
|
||||
mut node: Media,
|
||||
filter_chain: &Option<Arc<Mutex<Vec<String>>>>,
|
||||
player_control: &PlayerControl,
|
||||
last_index: usize,
|
||||
) -> Media {
|
||||
debug!("Playlist init");
|
||||
@ -574,7 +646,7 @@ fn handle_list_init(
|
||||
|
||||
node.out = out;
|
||||
|
||||
gen_source(config, node, filter_chain, last_index)
|
||||
gen_source(config, node, filter_chain, player_control, last_index)
|
||||
}
|
||||
|
||||
/// when we come to last clip in playlist,
|
||||
@ -585,6 +657,7 @@ fn handle_list_end(
|
||||
mut node: Media,
|
||||
total_delta: f64,
|
||||
filter_chain: &Option<Arc<Mutex<Vec<String>>>>,
|
||||
player_control: &PlayerControl,
|
||||
last_index: usize,
|
||||
) -> Media {
|
||||
debug!("Playlist end");
|
||||
@ -613,5 +686,5 @@ fn handle_list_end(
|
||||
|
||||
node.process = Some(true);
|
||||
|
||||
gen_source(config, node, filter_chain, last_index)
|
||||
gen_source(config, node, filter_chain, player_control, last_index)
|
||||
}
|
||||
|
@ -19,9 +19,9 @@ use ffplayout::{
|
||||
};
|
||||
|
||||
use ffplayout_lib::utils::{
|
||||
generate_playlist, get_date, import::import_file, init_logging, is_remote, send_mail,
|
||||
test_tcp_port, validate_ffmpeg, validate_playlist, JsonPlaylist, OutputMode::*, PlayerControl,
|
||||
PlayoutStatus, ProcessControl,
|
||||
folder::fill_filler_list, generate_playlist, get_date, import::import_file, init_logging,
|
||||
is_remote, send_mail, test_tcp_port, validate_ffmpeg, validate_playlist, JsonPlaylist,
|
||||
OutputMode::*, PlayerControl, PlayoutStatus, ProcessControl,
|
||||
};
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
@ -99,7 +99,8 @@ fn main() {
|
||||
let play_control = PlayerControl::new();
|
||||
let playout_stat = PlayoutStatus::new();
|
||||
let proc_control = ProcessControl::new();
|
||||
let play_ctl = play_control.clone();
|
||||
let play_ctl1 = play_control.clone();
|
||||
let play_ctl2 = play_control.clone();
|
||||
let play_stat = playout_stat.clone();
|
||||
let proc_ctl1 = proc_control.clone();
|
||||
let proc_ctl2 = proc_control.clone();
|
||||
@ -122,7 +123,8 @@ fn main() {
|
||||
exit(1);
|
||||
};
|
||||
|
||||
let config_clone = config.clone();
|
||||
let config_clone1 = config.clone();
|
||||
let config_clone2 = config.clone();
|
||||
|
||||
if ![2, 4, 6, 8].contains(&config.processing.audio_channels) {
|
||||
error!(
|
||||
@ -200,7 +202,7 @@ fn main() {
|
||||
exit(1)
|
||||
}
|
||||
|
||||
thread::spawn(move || run_server(config_clone, play_ctl, play_stat, proc_ctl2));
|
||||
thread::spawn(move || run_server(config_clone1, play_ctl1, play_stat, proc_ctl2));
|
||||
}
|
||||
|
||||
status_file(&config.general.stat_file, &playout_stat);
|
||||
@ -210,11 +212,20 @@ fn main() {
|
||||
config.general.config_path
|
||||
);
|
||||
|
||||
if Path::new(&config.storage.filler).is_dir() {
|
||||
debug!(
|
||||
"Fill filler list from: <b><magenta>{}</></b>",
|
||||
config.storage.filler
|
||||
);
|
||||
|
||||
thread::spawn(move || fill_filler_list(config_clone2, play_ctl2));
|
||||
}
|
||||
|
||||
match config.out.mode {
|
||||
// write files/playlist to HLS m3u8 playlist
|
||||
HLS => write_hls(&config, play_control, playout_stat, proc_control),
|
||||
// play on desktop or stream to a remote target
|
||||
_ => player(&config, play_control, playout_stat, proc_control),
|
||||
_ => player(&config, &play_control, playout_stat, proc_control),
|
||||
}
|
||||
|
||||
info!("Playout done...");
|
||||
|
@ -2,9 +2,7 @@ use std::process::{self, Command, Stdio};
|
||||
|
||||
use simplelog::*;
|
||||
|
||||
use ffplayout_lib::filter::v_drawtext;
|
||||
use ffplayout_lib::utils::PlayoutConfig;
|
||||
use ffplayout_lib::vec_strings;
|
||||
use ffplayout_lib::{filter::v_drawtext, utils::PlayoutConfig, vec_strings};
|
||||
|
||||
/// Desktop Output
|
||||
///
|
||||
|
@ -137,7 +137,7 @@ fn ingest_to_hls_server(
|
||||
/// Write with single ffmpeg instance directly to a HLS playlist.
|
||||
pub fn write_hls(
|
||||
config: &PlayoutConfig,
|
||||
play_control: PlayerControl,
|
||||
player_control: PlayerControl,
|
||||
playout_stat: PlayoutStatus,
|
||||
proc_control: ProcessControl,
|
||||
) {
|
||||
@ -148,8 +148,7 @@ pub fn write_hls(
|
||||
|
||||
let get_source = source_generator(
|
||||
config.clone(),
|
||||
play_control.current_list.clone(),
|
||||
play_control.index.clone(),
|
||||
&player_control,
|
||||
playout_stat,
|
||||
proc_control.is_terminated.clone(),
|
||||
);
|
||||
@ -160,7 +159,7 @@ pub fn write_hls(
|
||||
}
|
||||
|
||||
for node in get_source {
|
||||
*play_control.current_media.lock().unwrap() = Some(node.clone());
|
||||
*player_control.current_media.lock().unwrap() = Some(node.clone());
|
||||
|
||||
let mut cmd = match node.cmd {
|
||||
Some(cmd) => cmd,
|
||||
|
@ -37,7 +37,7 @@ use ffplayout_lib::vec_strings;
|
||||
/// When ingest stops, it switch back to playlist/folder mode.
|
||||
pub fn player(
|
||||
config: &PlayoutConfig,
|
||||
play_control: PlayerControl,
|
||||
play_control: &PlayerControl,
|
||||
playout_stat: PlayoutStatus,
|
||||
proc_control: ProcessControl,
|
||||
) {
|
||||
@ -50,8 +50,7 @@ pub fn player(
|
||||
// get source iterator
|
||||
let get_source = source_generator(
|
||||
config.clone(),
|
||||
play_control.current_list.clone(),
|
||||
play_control.index.clone(),
|
||||
play_control,
|
||||
playout_stat,
|
||||
proc_control.is_terminated.clone(),
|
||||
);
|
||||
@ -105,7 +104,10 @@ pub fn player(
|
||||
if Path::new(&config.task.path).is_file() {
|
||||
thread::spawn(move || task_runner::run(task_config, task_node, server_running));
|
||||
} else {
|
||||
error!("<bright-blue>{}</> executable not exists!", config.task.path);
|
||||
error!(
|
||||
"<bright-blue>{}</> executable not exists!",
|
||||
config.task.path
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,8 +15,8 @@ use tiny_http::{Header, Method, Request, Response, Server};
|
||||
use crate::rpc::zmq_send;
|
||||
use crate::utils::{get_data_map, get_media_map};
|
||||
use ffplayout_lib::utils::{
|
||||
get_delta, write_status, Ingest, OutputMode::*, PlayerControl, PlayoutConfig,
|
||||
PlayoutStatus, ProcessControl,
|
||||
get_delta, write_status, Ingest, OutputMode::*, PlayerControl, PlayoutConfig, PlayoutStatus,
|
||||
ProcessControl,
|
||||
};
|
||||
|
||||
#[derive(Default, Deserialize, Clone, Debug)]
|
||||
@ -175,7 +175,7 @@ fn control_back(
|
||||
let current_date = playout_stat.current_date.lock().unwrap().clone();
|
||||
let current_list = play_control.current_list.lock().unwrap();
|
||||
let mut date = playout_stat.date.lock().unwrap();
|
||||
let index = play_control.index.load(Ordering::SeqCst);
|
||||
let index = play_control.current_index.load(Ordering::SeqCst);
|
||||
let mut time_shift = playout_stat.time_shift.lock().unwrap();
|
||||
|
||||
if index > 1 && current_list.len() > 1 {
|
||||
@ -191,7 +191,7 @@ fn control_back(
|
||||
info!("Move to last clip");
|
||||
let mut data_map = Map::new();
|
||||
let mut media = current_list[index - 2].clone();
|
||||
play_control.index.fetch_sub(2, Ordering::SeqCst);
|
||||
play_control.current_index.fetch_sub(2, Ordering::SeqCst);
|
||||
media.add_probe();
|
||||
|
||||
let (delta, _) = get_delta(config, &media.begin.unwrap_or(0.0));
|
||||
@ -221,7 +221,7 @@ fn control_next(
|
||||
let current_date = playout_stat.current_date.lock().unwrap().clone();
|
||||
let current_list = play_control.current_list.lock().unwrap();
|
||||
let mut date = playout_stat.date.lock().unwrap();
|
||||
let index = play_control.index.load(Ordering::SeqCst);
|
||||
let index = play_control.current_index.load(Ordering::SeqCst);
|
||||
let mut time_shift = playout_stat.time_shift.lock().unwrap();
|
||||
|
||||
if index < current_list.len() {
|
||||
@ -370,7 +370,7 @@ fn media_current(
|
||||
|
||||
/// media info: get infos about next clip
|
||||
fn media_next(config: &PlayoutConfig, play_control: &PlayerControl) -> Response<Cursor<Vec<u8>>> {
|
||||
let index = play_control.index.load(Ordering::SeqCst);
|
||||
let index = play_control.current_index.load(Ordering::SeqCst);
|
||||
let current_list = play_control.current_list.lock().unwrap();
|
||||
|
||||
if index < current_list.len() {
|
||||
@ -386,7 +386,7 @@ fn media_next(config: &PlayoutConfig, play_control: &PlayerControl) -> Response<
|
||||
|
||||
/// media info: get infos about last clip
|
||||
fn media_last(config: &PlayoutConfig, play_control: &PlayerControl) -> Response<Cursor<Vec<u8>>> {
|
||||
let index = play_control.index.load(Ordering::SeqCst);
|
||||
let index = play_control.current_index.load(Ordering::SeqCst);
|
||||
let current_list = play_control.current_list.lock().unwrap();
|
||||
|
||||
if index > 1 && index - 2 < current_list.len() {
|
||||
|
@ -269,7 +269,8 @@ pub struct Storage {
|
||||
pub path: String,
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub paths: Vec<String>,
|
||||
pub filler_clip: String,
|
||||
#[serde(alias = "filler_clip")]
|
||||
pub filler: String,
|
||||
pub extensions: Vec<String>,
|
||||
pub shuffle: bool,
|
||||
}
|
||||
|
@ -163,11 +163,13 @@ impl ProcessControl {
|
||||
// }
|
||||
|
||||
/// Global player control, to get infos about current clip etc.
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PlayerControl {
|
||||
pub current_media: Arc<Mutex<Option<Media>>>,
|
||||
pub current_list: Arc<Mutex<Vec<Media>>>,
|
||||
pub index: Arc<AtomicUsize>,
|
||||
pub filler_list: Arc<Mutex<Vec<Media>>>,
|
||||
pub current_index: Arc<AtomicUsize>,
|
||||
pub filler_index: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl PlayerControl {
|
||||
@ -175,7 +177,9 @@ impl PlayerControl {
|
||||
Self {
|
||||
current_media: Arc::new(Mutex::new(None)),
|
||||
current_list: Arc::new(Mutex::new(vec![Media::new(0, "", false)])),
|
||||
index: Arc::new(AtomicUsize::new(0)),
|
||||
filler_list: Arc::new(Mutex::new(vec![Media::new(0, "", false)])),
|
||||
current_index: Arc::new(AtomicUsize::new(0)),
|
||||
filler_index: Arc::new(AtomicUsize::new(0)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::{
|
||||
path::Path,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
atomic::Ordering,
|
||||
{Arc, Mutex},
|
||||
},
|
||||
};
|
||||
@ -10,7 +10,9 @@ use rand::{seq::SliceRandom, thread_rng};
|
||||
use simplelog::*;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::utils::{get_sec, include_file, Media, PlayoutConfig};
|
||||
use crate::utils::{
|
||||
controller::PlayerControl, get_sec, include_file_extension, Media, PlayoutConfig,
|
||||
};
|
||||
|
||||
/// Folder Sources
|
||||
///
|
||||
@ -19,17 +21,15 @@ use crate::utils::{get_sec, include_file, Media, PlayoutConfig};
|
||||
pub struct FolderSource {
|
||||
config: PlayoutConfig,
|
||||
filter_chain: Option<Arc<Mutex<Vec<String>>>>,
|
||||
pub nodes: Arc<Mutex<Vec<Media>>>,
|
||||
pub player_control: PlayerControl,
|
||||
current_node: Media,
|
||||
index: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl FolderSource {
|
||||
pub fn new(
|
||||
config: &PlayoutConfig,
|
||||
filter_chain: Option<Arc<Mutex<Vec<String>>>>,
|
||||
current_list: Arc<Mutex<Vec<Media>>>,
|
||||
global_index: Arc<AtomicUsize>,
|
||||
player_control: &PlayerControl,
|
||||
) -> Self {
|
||||
let mut path_list = vec![];
|
||||
let mut media_list = vec![];
|
||||
@ -52,11 +52,10 @@ impl FolderSource {
|
||||
.into_iter()
|
||||
.flat_map(|e| e.ok())
|
||||
.filter(|f| f.path().is_file())
|
||||
.filter(|f| include_file_extension(config, f.path()))
|
||||
{
|
||||
if include_file(config.clone(), entry.path()) {
|
||||
let media = Media::new(0, &entry.path().to_string_lossy(), false);
|
||||
media_list.push(media);
|
||||
}
|
||||
let media = Media::new(0, &entry.path().to_string_lossy(), false);
|
||||
media_list.push(media);
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,20 +80,19 @@ impl FolderSource {
|
||||
index += 1;
|
||||
}
|
||||
|
||||
*current_list.lock().unwrap() = media_list;
|
||||
*player_control.current_list.lock().unwrap() = media_list;
|
||||
|
||||
Self {
|
||||
config: config.clone(),
|
||||
filter_chain,
|
||||
nodes: current_list,
|
||||
player_control: player_control.clone(),
|
||||
current_node: Media::new(0, "", false),
|
||||
index: global_index,
|
||||
}
|
||||
}
|
||||
|
||||
fn shuffle(&mut self) {
|
||||
let mut rng = thread_rng();
|
||||
let mut nodes = self.nodes.lock().unwrap();
|
||||
let mut nodes = self.player_control.current_list.lock().unwrap();
|
||||
|
||||
nodes.shuffle(&mut rng);
|
||||
|
||||
@ -104,7 +102,7 @@ impl FolderSource {
|
||||
}
|
||||
|
||||
fn sort(&mut self) {
|
||||
let mut nodes = self.nodes.lock().unwrap();
|
||||
let mut nodes = self.player_control.current_list.lock().unwrap();
|
||||
|
||||
nodes.sort_by(|d1, d2| d1.source.cmp(&d2.source));
|
||||
|
||||
@ -119,15 +117,19 @@ impl Iterator for FolderSource {
|
||||
type Item = Media;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.index.load(Ordering::SeqCst) < self.nodes.lock().unwrap().len() {
|
||||
let i = self.index.load(Ordering::SeqCst);
|
||||
self.current_node = self.nodes.lock().unwrap()[i].clone();
|
||||
if self.player_control.current_index.load(Ordering::SeqCst)
|
||||
< self.player_control.current_list.lock().unwrap().len()
|
||||
{
|
||||
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();
|
||||
self.current_node
|
||||
.add_filter(&self.config, &self.filter_chain);
|
||||
self.current_node.begin = Some(get_sec());
|
||||
|
||||
self.index.fetch_add(1, Ordering::SeqCst);
|
||||
self.player_control
|
||||
.current_index
|
||||
.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
Some(self.current_node.clone())
|
||||
} else {
|
||||
@ -145,15 +147,38 @@ impl Iterator for FolderSource {
|
||||
self.sort();
|
||||
}
|
||||
|
||||
self.current_node = self.nodes.lock().unwrap()[0].clone();
|
||||
self.current_node = self.player_control.current_list.lock().unwrap()[0].clone();
|
||||
self.current_node.add_probe();
|
||||
self.current_node
|
||||
.add_filter(&self.config, &self.filter_chain);
|
||||
self.current_node.begin = Some(get_sec());
|
||||
|
||||
self.index.store(1, Ordering::SeqCst);
|
||||
self.player_control.current_index.store(1, Ordering::SeqCst);
|
||||
|
||||
Some(self.current_node.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_filler_list(config: PlayoutConfig, player_control: PlayerControl) {
|
||||
let mut filler_list = vec![];
|
||||
|
||||
for (index, entry) in WalkDir::new(&config.storage.filler)
|
||||
.into_iter()
|
||||
.flat_map(|e| e.ok())
|
||||
.filter(|f| f.path().is_file())
|
||||
.filter(|f| include_file_extension(&config, f.path()))
|
||||
.enumerate()
|
||||
{
|
||||
let media = Media::new(index, &entry.path().to_string_lossy(), false);
|
||||
filler_list.push(media);
|
||||
}
|
||||
|
||||
if config.storage.shuffle {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
filler_list.shuffle(&mut rng);
|
||||
}
|
||||
|
||||
*player_control.filler_list.lock().unwrap() = filler_list;
|
||||
}
|
||||
|
@ -11,12 +11,11 @@ use std::{
|
||||
io::Error,
|
||||
path::Path,
|
||||
process::exit,
|
||||
sync::{atomic::AtomicUsize, Arc, Mutex},
|
||||
};
|
||||
|
||||
use simplelog::*;
|
||||
|
||||
use super::folder::FolderSource;
|
||||
use super::{folder::FolderSource, PlayerControl};
|
||||
use crate::utils::{
|
||||
get_date_range, json_serializer::JsonPlaylist, time_to_sec, Media, PlayoutConfig,
|
||||
};
|
||||
@ -36,8 +35,7 @@ pub fn generate_playlist(
|
||||
}
|
||||
}
|
||||
};
|
||||
let current_list = Arc::new(Mutex::new(vec![Media::new(0, "", false)]));
|
||||
let index = Arc::new(AtomicUsize::new(0));
|
||||
let player_control = PlayerControl::new();
|
||||
let playlist_root = Path::new(&config.playlist.path);
|
||||
let mut playlists = vec![];
|
||||
let mut date_range = vec![];
|
||||
@ -64,8 +62,8 @@ pub fn generate_playlist(
|
||||
date_range = get_date_range(&date_range)
|
||||
}
|
||||
|
||||
let media_list = FolderSource::new(config, None, current_list, index);
|
||||
let list_length = media_list.nodes.lock().unwrap().len();
|
||||
let media_list = FolderSource::new(config, None, &player_control);
|
||||
let list_length = media_list.player_control.current_list.lock().unwrap().len();
|
||||
|
||||
for date in date_range {
|
||||
let d: Vec<&str> = date.split('-').collect();
|
||||
@ -90,7 +88,8 @@ pub fn generate_playlist(
|
||||
playlist_file.display()
|
||||
);
|
||||
|
||||
let mut filler = Media::new(0, &config.storage.filler_clip, true);
|
||||
// TODO: handle filler folder
|
||||
let mut filler = Media::new(0, &config.storage.filler, true);
|
||||
let filler_length = filler.duration;
|
||||
let mut length = 0.0;
|
||||
let mut round = 0;
|
||||
|
@ -586,7 +586,7 @@ pub fn valid_source(source: &str) -> bool {
|
||||
/// Check if file can include or has to exclude.
|
||||
/// For example when a file is on given HLS output path, it should exclude.
|
||||
/// Or when the file extension is set under storage config it can be include.
|
||||
pub fn include_file(config: PlayoutConfig, file_path: &Path) -> bool {
|
||||
pub fn include_file_extension(config: &PlayoutConfig, file_path: &Path) -> bool {
|
||||
let mut include = false;
|
||||
|
||||
if let Some(ext) = file_extension(file_path) {
|
||||
@ -614,6 +614,7 @@ pub fn include_file(config: PlayoutConfig, file_path: &Path) -> bool {
|
||||
if let Some(m3u8_path) = config
|
||||
.out
|
||||
.output_cmd
|
||||
.clone()
|
||||
.unwrap_or_else(|| vec![String::new()])
|
||||
.iter()
|
||||
.find(|s| s.contains(".m3u8") && !s.contains("master.m3u8"))
|
||||
|
@ -2,20 +2,21 @@ use std::fs;
|
||||
|
||||
use ffplayout::{input::playlist::gen_source, utils::prepare_output_cmd};
|
||||
use ffplayout_lib::{
|
||||
utils::{Media, OutputMode::*, PlayoutConfig, ProcessUnit::*},
|
||||
utils::{Media, OutputMode::*, PlayerControl, PlayoutConfig, ProcessUnit::*},
|
||||
vec_strings,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn video_audio_input() {
|
||||
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
|
||||
let player_control = PlayerControl::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, 1);
|
||||
let media = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
|
||||
let test_filter_cmd =
|
||||
vec_strings![
|
||||
@ -36,12 +37,13 @@ fn video_audio_input() {
|
||||
#[test]
|
||||
fn video_audio_custom_filter1_input() {
|
||||
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
|
||||
let player_control = PlayerControl::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, 1);
|
||||
let media = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
|
||||
let test_filter_cmd = vec_strings![
|
||||
"-filter_complex",
|
||||
@ -61,6 +63,7 @@ fn video_audio_custom_filter1_input() {
|
||||
#[test]
|
||||
fn video_audio_custom_filter2_input() {
|
||||
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
|
||||
let player_control = PlayerControl::new();
|
||||
config.out.mode = Stream;
|
||||
config.processing.add_logo = false;
|
||||
config.processing.custom_filter =
|
||||
@ -68,7 +71,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, 1);
|
||||
let media = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
|
||||
let test_filter_cmd = vec_strings![
|
||||
"-filter_complex",
|
||||
@ -88,13 +91,14 @@ fn video_audio_custom_filter2_input() {
|
||||
#[test]
|
||||
fn video_audio_custom_filter3_input() {
|
||||
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
|
||||
let player_control = PlayerControl::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, 1);
|
||||
let media = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
|
||||
let test_filter_cmd = vec_strings![
|
||||
"-filter_complex",
|
||||
@ -114,12 +118,13 @@ fn video_audio_custom_filter3_input() {
|
||||
#[test]
|
||||
fn dual_audio_aevalsrc_input() {
|
||||
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
|
||||
let player_control = PlayerControl::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, 1);
|
||||
let media = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
|
||||
let test_filter_cmd =
|
||||
vec_strings![
|
||||
@ -140,12 +145,13 @@ fn dual_audio_aevalsrc_input() {
|
||||
#[test]
|
||||
fn dual_audio_input() {
|
||||
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
|
||||
let player_control = PlayerControl::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, 1);
|
||||
let media = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
|
||||
let test_filter_cmd = vec_strings![
|
||||
"-filter_complex",
|
||||
@ -165,13 +171,14 @@ fn dual_audio_input() {
|
||||
#[test]
|
||||
fn video_separate_audio_input() {
|
||||
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
|
||||
let player_control = PlayerControl::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, 1);
|
||||
let media = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
|
||||
let test_filter_cmd = vec_strings![
|
||||
"-filter_complex",
|
||||
@ -1305,6 +1312,7 @@ fn video_audio_text_filter_stream() {
|
||||
#[test]
|
||||
fn video_audio_hls() {
|
||||
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
|
||||
let player_control = PlayerControl::new();
|
||||
config.out.mode = HLS;
|
||||
config.processing.add_logo = false;
|
||||
config.text.add_text = false;
|
||||
@ -1333,7 +1341,7 @@ fn video_audio_hls() {
|
||||
]);
|
||||
|
||||
let media_obj = Media::new(0, "./assets/with_audio.mp4", true);
|
||||
let media = gen_source(&config, media_obj, &None, 1);
|
||||
let media = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
|
||||
let enc_prefix = vec_strings![
|
||||
"-hide_banner",
|
||||
@ -1390,6 +1398,7 @@ fn video_audio_hls() {
|
||||
#[test]
|
||||
fn video_audio_sub_meta_hls() {
|
||||
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
|
||||
let player_control = PlayerControl::new();
|
||||
config.out.mode = HLS;
|
||||
config.processing.add_logo = false;
|
||||
config.text.add_text = false;
|
||||
@ -1422,7 +1431,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, 1);
|
||||
let media = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
|
||||
let enc_prefix = vec_strings![
|
||||
"-hide_banner",
|
||||
@ -1483,6 +1492,7 @@ fn video_audio_sub_meta_hls() {
|
||||
#[test]
|
||||
fn video_multi_audio_hls() {
|
||||
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
|
||||
let player_control = PlayerControl::new();
|
||||
config.out.mode = HLS;
|
||||
config.processing.add_logo = false;
|
||||
config.processing.audio_tracks = 2;
|
||||
@ -1512,7 +1522,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, 1);
|
||||
let media = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
|
||||
let enc_prefix = vec_strings![
|
||||
"-hide_banner",
|
||||
@ -1571,6 +1581,7 @@ fn video_multi_audio_hls() {
|
||||
#[test]
|
||||
fn multi_video_audio_hls() {
|
||||
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
|
||||
let player_control = PlayerControl::new();
|
||||
config.out.mode = HLS;
|
||||
config.processing.add_logo = false;
|
||||
config.text.add_text = false;
|
||||
@ -1617,7 +1628,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, 1);
|
||||
let media = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
|
||||
let enc_prefix = vec_strings![
|
||||
"-hide_banner",
|
||||
@ -1684,6 +1695,7 @@ fn multi_video_audio_hls() {
|
||||
#[test]
|
||||
fn multi_video_multi_audio_hls() {
|
||||
let mut config = PlayoutConfig::new(Some("../assets/ffplayout.yml".to_string()));
|
||||
let player_control = PlayerControl::new();
|
||||
config.out.mode = HLS;
|
||||
config.processing.add_logo = false;
|
||||
config.processing.audio_tracks = 2;
|
||||
@ -1733,7 +1745,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, 1);
|
||||
let media = gen_source(&config, media_obj, &None, &player_control, 1);
|
||||
|
||||
let enc_prefix = vec_strings![
|
||||
"-hide_banner",
|
||||
|
@ -31,7 +31,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_clip = "assets/with_audio.mp4".into();
|
||||
config.storage.filler = "assets/with_audio.mp4".into();
|
||||
config.logging.log_to_file = false;
|
||||
config.logging.timestamp = false;
|
||||
config.out.mode = Null;
|
||||
@ -51,7 +51,7 @@ fn playlist_change_at_midnight() {
|
||||
|
||||
thread::spawn(move || timed_stop(28, proc_ctl));
|
||||
|
||||
player(&config, play_control, playout_stat.clone(), proc_control);
|
||||
player(&config, &play_control, playout_stat.clone(), proc_control);
|
||||
|
||||
let playlist_date = &*playout_stat.current_date.lock().unwrap();
|
||||
|
||||
@ -72,7 +72,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_clip = "assets/with_audio.mp4".into();
|
||||
config.storage.filler = "assets/with_audio.mp4".into();
|
||||
config.logging.log_to_file = false;
|
||||
config.logging.timestamp = false;
|
||||
config.out.mode = Null;
|
||||
@ -92,7 +92,7 @@ fn playlist_change_at_six() {
|
||||
|
||||
thread::spawn(move || timed_stop(28, proc_ctl));
|
||||
|
||||
player(&config, play_control, playout_stat.clone(), proc_control);
|
||||
player(&config, &play_control, playout_stat.clone(), proc_control);
|
||||
|
||||
let playlist_date = &*playout_stat.current_date.lock().unwrap();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user