work on playlist generator
This commit is contained in:
parent
b5a991c9ca
commit
614aa7851c
@ -44,7 +44,7 @@ lto = true
|
||||
name = "ffplayout-engine"
|
||||
priority = "optional"
|
||||
section = "net"
|
||||
license-file = ["LICENSE", "3"]
|
||||
license-file = ["LICENSE", "0"]
|
||||
depends = ""
|
||||
suggests = "ffmpeg"
|
||||
maintainer-scripts = "package/debian/"
|
||||
@ -54,9 +54,8 @@ assets = [
|
||||
["assets/ffplayout.yml", "/etc/ffplayout/ffplayout.yml", "644"],
|
||||
["assets/logo.png", "/usr/share/ffplayout/logo.png", "644"],
|
||||
["README.md", "/usr/share/doc/ffplayout-engine/README", "644"],
|
||||
["package/common/ffplayout-engine.service.preset", "/lib/systemd/system-preset/50-ffplayout-engine.preset", "644"],
|
||||
]
|
||||
systemd-units = { unit-name = "ffplayout-engine", unit-scripts = "package/common", enable = false }
|
||||
systemd-units = { unit-name = "ffplayout-engine", unit-scripts = "assets", enable = false }
|
||||
|
||||
# REHL RPM PACKAGE
|
||||
[package.metadata.generate-rpm]
|
||||
@ -65,7 +64,7 @@ license = "GPL-3.0"
|
||||
assets = [
|
||||
{ source = "target/x86_64-unknown-linux-musl/release/ffplayout", dest = "/usr/bin/ffplayout", mode = "755" },
|
||||
{ source = "assets/ffplayout.yml", dest = "/etc/ffplayout/ffplayout.yml", mode = "644" },
|
||||
{ source = "package/common/ffplayout-engine.service", dest = "/lib/systemd/system/ffplayout-engine.service", mode = "644" },
|
||||
{ source = "assets/ffplayout-engine.service", dest = "/lib/systemd/system/ffplayout-engine.service", mode = "644" },
|
||||
{ source = "README.md", dest = "/usr/share/doc/ffplayout-engine/README", mode = "644", doc = true },
|
||||
{ source = "LICENSE", dest = "/usr/share/doc/ffplayout-engine/LICENSE", mode = "644" },
|
||||
{ source = "assets/logo.png", dest = "/usr/share/ffplayout/logo.png", mode = "644" },
|
||||
|
@ -44,12 +44,10 @@ echo "Create debian package"
|
||||
echo ""
|
||||
|
||||
cargo deb --target=x86_64-unknown-linux-musl
|
||||
|
||||
mv ./target/x86_64-unknown-linux-musl/debian/ffplayout-engine_${version}_amd64.deb .
|
||||
|
||||
echo "Create rhel package"
|
||||
echo ""
|
||||
|
||||
cargo generate-rpm --target=x86_64-unknown-linux-musl
|
||||
|
||||
mv ./target/x86_64-unknown-linux-musl/generate-rpm/ffplayout-engine-${version}-1.x86_64.rpm .
|
||||
|
@ -1 +0,0 @@
|
||||
disable ffplayout-engine.service
|
@ -174,10 +174,8 @@ fn overlay(node: &mut Media, chain: &mut Filters, config: &GlobalConfig) {
|
||||
fn extend_video(node: &mut Media, chain: &mut Filters) {
|
||||
let video_streams = node.probe.clone().unwrap().video_streams.unwrap();
|
||||
if video_streams.len() > 0 {
|
||||
let video_duration = &video_streams[0].duration;
|
||||
|
||||
if video_duration.is_some() {
|
||||
let duration_float = video_duration.clone().unwrap().parse::<f64>().unwrap();
|
||||
if let Some(duration) = &video_streams[0].duration {
|
||||
let duration_float = duration.clone().parse::<f64>().unwrap();
|
||||
|
||||
if node.out - node.seek > duration_float - node.seek + 0.1 {
|
||||
chain.add_filter(
|
||||
@ -226,10 +224,8 @@ fn add_audio(node: &mut Media, chain: &mut Filters) {
|
||||
fn extend_audio(node: &mut Media, chain: &mut Filters) {
|
||||
let audio_streams = node.probe.clone().unwrap().audio_streams.unwrap();
|
||||
if audio_streams.len() > 0 {
|
||||
let audio_duration = &audio_streams[0].duration;
|
||||
|
||||
if audio_duration.is_some() {
|
||||
let duration_float = audio_duration.clone().unwrap().parse::<f64>().unwrap();
|
||||
if let Some(duration) = &audio_streams[0].duration {
|
||||
let duration_float = duration.clone().parse::<f64>().unwrap();
|
||||
|
||||
if node.out - node.seek > duration_float - node.seek + 0.1 {
|
||||
chain.add_filter(
|
||||
@ -322,15 +318,13 @@ pub fn filter_chains(node: &mut Media) -> Vec<String> {
|
||||
let mut audio_map = "1:a".to_string();
|
||||
filters.audio_map = Some(audio_map);
|
||||
|
||||
if node.probe.is_some() {
|
||||
let probe = node.probe.clone();
|
||||
|
||||
if node.probe.as_ref().unwrap().audio_streams.is_some() {
|
||||
if let Some(probe) = node.probe.clone() {
|
||||
if probe.audio_streams.is_some() {
|
||||
audio_map = "0:a".to_string();
|
||||
filters.audio_map = Some(audio_map);
|
||||
}
|
||||
|
||||
let v_stream = &probe.unwrap().video_streams.unwrap()[0];
|
||||
let v_stream = &probe.video_streams.unwrap()[0];
|
||||
let aspect = aspect_calc(v_stream.display_aspect_ratio.clone().unwrap());
|
||||
let frame_per_sec = fps_calc(v_stream.r_frame_rate.clone());
|
||||
|
||||
@ -364,19 +358,19 @@ pub fn filter_chains(node: &mut Media) -> Vec<String> {
|
||||
let mut filter_str: String = String::new();
|
||||
let mut filter_map: Vec<String> = vec![];
|
||||
|
||||
if filters.video_chain.is_some() {
|
||||
filter_str.push_str(filters.video_chain.unwrap().as_str());
|
||||
if let Some(v_filters) = filters.video_chain {
|
||||
filter_str.push_str(v_filters.as_str());
|
||||
filter_str.push_str(filters.video_map.clone().unwrap().as_str());
|
||||
filter_map.append(&mut vec!["-map".to_string(), filters.video_map.unwrap()]);
|
||||
} else {
|
||||
filter_map.append(&mut vec!["-map".to_string(), "0:v".to_string()]);
|
||||
}
|
||||
|
||||
if filters.audio_chain.is_some() {
|
||||
if let Some(a_filters) = filters.audio_chain {
|
||||
if filter_str.len() > 10 {
|
||||
filter_str.push_str(";")
|
||||
}
|
||||
filter_str.push_str(filters.audio_chain.unwrap().as_str());
|
||||
filter_str.push_str(a_filters.as_str());
|
||||
filter_str.push_str(filters.audio_map.clone().unwrap().as_str());
|
||||
filter_map.append(&mut vec!["-map".to_string(), filters.audio_map.unwrap()]);
|
||||
} else {
|
||||
|
@ -121,10 +121,16 @@ impl Iterator for Source {
|
||||
Some(self.current_node.clone())
|
||||
} else {
|
||||
if self.config.storage.shuffle {
|
||||
info!("Shuffle files");
|
||||
if self.config.general.generate.is_none() {
|
||||
info!("Shuffle files");
|
||||
}
|
||||
|
||||
self.shuffle();
|
||||
} else {
|
||||
info!("Sort files");
|
||||
if self.config.general.generate.is_none() {
|
||||
info!("Sort files");
|
||||
}
|
||||
|
||||
self.sort();
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ use simplelog::*;
|
||||
use tokio::runtime::Handle;
|
||||
|
||||
use crate::utils::{
|
||||
check_sync, gen_dummy, get_delta, get_sec, is_close, json_reader::read_json, modified_time,
|
||||
check_sync, gen_dummy, get_delta, get_sec, is_close, json_serializer::read_json, modified_time,
|
||||
seek_and_length, GlobalConfig, Media, PlayoutStatus, DUMMY_LEN,
|
||||
};
|
||||
|
||||
|
12
src/main.rs
12
src/main.rs
@ -3,6 +3,7 @@ extern crate simplelog;
|
||||
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
process::exit,
|
||||
{fs, fs::File},
|
||||
};
|
||||
|
||||
@ -19,8 +20,8 @@ mod utils;
|
||||
|
||||
use crate::output::{player, write_hls};
|
||||
use crate::utils::{
|
||||
init_config, init_logging, validate_ffmpeg, GlobalConfig, PlayerControl, PlayoutStatus,
|
||||
ProcessControl,
|
||||
generate_playlist, init_config, init_logging, validate_ffmpeg, GlobalConfig, PlayerControl,
|
||||
PlayoutStatus, ProcessControl,
|
||||
};
|
||||
use rpc::json_rpc_server;
|
||||
|
||||
@ -67,6 +68,13 @@ fn main() {
|
||||
|
||||
validate_ffmpeg();
|
||||
|
||||
if config.general.generate.is_some() {
|
||||
// generate playlist from given dates
|
||||
generate_playlist();
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if config.rpc_server.enable {
|
||||
rt_handle.spawn(json_rpc_server(
|
||||
play_control.clone(),
|
||||
|
@ -5,42 +5,50 @@ use clap::Parser;
|
||||
about = "ffplayout, Rust based 24/7 playout solution",
|
||||
long_about = None)]
|
||||
pub struct Args {
|
||||
#[clap(short, long, help = "file path to ffplayout.conf")]
|
||||
#[clap(short, long, help = "File path to ffplayout.conf")]
|
||||
pub config: Option<String>,
|
||||
|
||||
#[clap(short, long, help = "file path for logging")]
|
||||
#[clap(short, long, help = "File path for logging")]
|
||||
pub log: Option<String>,
|
||||
|
||||
#[clap(short = 'm', long, help = "playing mode: folder, playlist")]
|
||||
#[clap(
|
||||
short,
|
||||
long,
|
||||
help = "Generate playlist for given date. Date needs format: YYYY-MM-DD. Date-range is possible to, like: 2022-01-01 - 2022-01-10.",
|
||||
multiple_values=true
|
||||
)]
|
||||
pub generate: Option<Vec<String>>,
|
||||
|
||||
#[clap(short = 'm', long, help = "Playing mode: folder, playlist")]
|
||||
pub play_mode: Option<String>,
|
||||
|
||||
#[clap(short, long, help = "play folder content")]
|
||||
#[clap(short, long, help = "Play folder content")]
|
||||
pub folder: Option<String>,
|
||||
|
||||
#[clap(short, long, help = "path from playlist")]
|
||||
#[clap(short, long, help = "Path from playlist")]
|
||||
pub playlist: Option<String>,
|
||||
|
||||
#[clap(
|
||||
short,
|
||||
long,
|
||||
help = "start time in 'hh:mm:ss', 'now' for start with first"
|
||||
help = "Start time in 'hh:mm:ss', 'now' for start with first"
|
||||
)]
|
||||
pub start: Option<String>,
|
||||
|
||||
#[clap(
|
||||
short = 't',
|
||||
long,
|
||||
help = "set length in 'hh:mm:ss', 'none' for no length check"
|
||||
help = "Set length in 'hh:mm:ss', 'none' for no length check"
|
||||
)]
|
||||
pub length: Option<String>,
|
||||
|
||||
#[clap(short, long, help = "loop playlist infinitely")]
|
||||
#[clap(short, long, help = "Loop playlist infinitely")]
|
||||
pub infinit: bool,
|
||||
|
||||
#[clap(short, long, help = "set output mode: desktop, hls, stream")]
|
||||
#[clap(short, long, help = "Set output mode: desktop, hls, stream")]
|
||||
pub output: Option<String>,
|
||||
|
||||
#[clap(short, long, help = "set audio volume")]
|
||||
#[clap(short, long, help = "Set audio volume")]
|
||||
pub volume: Option<f64>,
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,7 @@ pub struct GlobalConfig {
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct General {
|
||||
pub stop_threshold: f64,
|
||||
pub generate: Option<Vec<String>>,
|
||||
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub stat_file: String,
|
||||
@ -138,8 +139,8 @@ impl GlobalConfig {
|
||||
Err(_) => PathBuf::from("./ffplayout.yml"),
|
||||
};
|
||||
|
||||
if args.config.is_some() {
|
||||
config_path = PathBuf::from(args.config.unwrap());
|
||||
if let Some(cfg) = args.config {
|
||||
config_path = PathBuf::from(cfg);
|
||||
} else if Path::new("/etc/ffplayout/ffplayout.yml").is_file() {
|
||||
config_path = PathBuf::from("/etc/ffplayout/ffplayout.yml");
|
||||
}
|
||||
@ -157,6 +158,7 @@ impl GlobalConfig {
|
||||
|
||||
let mut config: GlobalConfig =
|
||||
serde_yaml::from_reader(f).expect("Could not read config file.");
|
||||
config.general.generate = None;
|
||||
config.general.stat_file = env::temp_dir()
|
||||
.join("ffplayout_status.json")
|
||||
.display()
|
||||
@ -207,32 +209,36 @@ impl GlobalConfig {
|
||||
config.out.preview_cmd = split(config.out.preview_param.as_str());
|
||||
config.out.output_cmd = split(config.out.output_param.as_str());
|
||||
|
||||
if args.log.is_some() {
|
||||
config.logging.log_path = args.log.unwrap();
|
||||
if let Some(gen) = args.generate {
|
||||
config.general.generate = Some(gen);
|
||||
}
|
||||
|
||||
if args.playlist.is_some() {
|
||||
config.playlist.path = args.playlist.unwrap();
|
||||
if let Some(log_path) = args.log {
|
||||
config.logging.log_path = log_path;
|
||||
}
|
||||
|
||||
if args.play_mode.is_some() {
|
||||
config.processing.mode = args.play_mode.unwrap();
|
||||
if let Some(playlist) = args.playlist {
|
||||
config.playlist.path = playlist;
|
||||
}
|
||||
|
||||
if args.folder.is_some() {
|
||||
config.storage.path = args.folder.unwrap();
|
||||
if let Some(mode) = args.play_mode {
|
||||
config.processing.mode = mode;
|
||||
}
|
||||
|
||||
if args.start.is_some() {
|
||||
config.playlist.day_start = args.start.clone().unwrap();
|
||||
config.playlist.start_sec = Some(time_to_sec(&args.start.unwrap()));
|
||||
if let Some(folder) = args.folder {
|
||||
config.storage.path = folder;
|
||||
}
|
||||
|
||||
if args.length.is_some() {
|
||||
config.playlist.length = args.length.clone().unwrap();
|
||||
if let Some(start) = args.start {
|
||||
config.playlist.day_start = start.clone();
|
||||
config.playlist.start_sec = Some(time_to_sec(&start));
|
||||
}
|
||||
|
||||
if config.playlist.length.contains(":") {
|
||||
config.playlist.length_sec = Some(time_to_sec(&config.playlist.length));
|
||||
if let Some(length) = args.length {
|
||||
config.playlist.length = length.clone();
|
||||
|
||||
if length.contains(":") {
|
||||
config.playlist.length_sec = Some(time_to_sec(&length));
|
||||
} else {
|
||||
config.playlist.length_sec = Some(86400.0);
|
||||
}
|
||||
@ -242,12 +248,12 @@ impl GlobalConfig {
|
||||
config.playlist.infinit = args.infinit;
|
||||
}
|
||||
|
||||
if args.output.is_some() {
|
||||
config.out.mode = args.output.unwrap();
|
||||
if let Some(output) = args.output {
|
||||
config.out.mode = output;
|
||||
}
|
||||
|
||||
if args.volume.is_some() {
|
||||
config.processing.volume = args.volume.unwrap();
|
||||
if let Some(volume) = args.volume {
|
||||
config.processing.volume = volume;
|
||||
}
|
||||
|
||||
config
|
||||
|
104
src/utils/generator.rs
Normal file
104
src/utils/generator.rs
Normal file
@ -0,0 +1,104 @@
|
||||
use std::{
|
||||
process::exit,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use chrono::{Duration, NaiveDate};
|
||||
use simplelog::*;
|
||||
|
||||
use crate::input::Source;
|
||||
use crate::utils::{json_serializer::Playlist, GlobalConfig, Media};
|
||||
|
||||
fn get_date_range(date_range: &Vec<String>) -> Vec<String> {
|
||||
let mut range = vec![];
|
||||
let start;
|
||||
let end;
|
||||
|
||||
match NaiveDate::parse_from_str(&date_range[0], "%Y-%m-%d") {
|
||||
Ok(s) => {
|
||||
start = s;
|
||||
}
|
||||
Err(_) => {
|
||||
error!("date format error in: <yellow>{:?}</>", date_range[0]);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
match NaiveDate::parse_from_str(&date_range[2], "%Y-%m-%d") {
|
||||
Ok(e) => {
|
||||
end = e;
|
||||
}
|
||||
Err(_) => {
|
||||
error!("date format error in: <yellow>{:?}</>", date_range[2]);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
let duration = end.signed_duration_since(start);
|
||||
let days = duration.num_days() + 1;
|
||||
|
||||
for day in 0..days {
|
||||
range.push((start + Duration::days(day)).format("%Y-%m-%d").to_string());
|
||||
}
|
||||
|
||||
range
|
||||
}
|
||||
|
||||
pub fn generate_playlist() {
|
||||
let config = GlobalConfig::global();
|
||||
let mut date_range = config.general.generate.clone().unwrap();
|
||||
let total_length = config.playlist.length_sec.unwrap().clone();
|
||||
let current_list = Arc::new(Mutex::new(vec![Media::new(0, "".to_string(), false)]));
|
||||
let index = Arc::new(Mutex::new(0));
|
||||
|
||||
if date_range.contains(&"-".to_string()) && date_range.len() == 3 {
|
||||
date_range = get_date_range(&date_range)
|
||||
}
|
||||
|
||||
let media_list = Source::new(current_list, index);
|
||||
|
||||
for date in date_range {
|
||||
info!("Generate playlist for {date}");
|
||||
|
||||
let mut filler = Media::new(0, config.storage.filler_clip.clone(), true);
|
||||
let filler_length = filler.duration.clone();
|
||||
let mut length = 0.0;
|
||||
|
||||
let mut playlist = Playlist {
|
||||
date,
|
||||
current_file: None,
|
||||
start_sec: None,
|
||||
modified: None,
|
||||
program: vec![],
|
||||
};
|
||||
|
||||
let mut round = 0;
|
||||
|
||||
for item in media_list.clone() {
|
||||
let duration = item.duration.clone();
|
||||
|
||||
if total_length > length + filler_length {
|
||||
playlist.program.push(item);
|
||||
|
||||
length += duration;
|
||||
} else if filler_length > 0.0 && filler_length > total_length - length {
|
||||
println!("{filler_length}");
|
||||
filler.out = filler_length - (total_length - length);
|
||||
println!("{}", total_length - length);
|
||||
playlist.program.push(filler);
|
||||
|
||||
break;
|
||||
} else if round == 3 {
|
||||
println!("break");
|
||||
println!("length {length}");
|
||||
println!("total_length {total_length}");
|
||||
break;
|
||||
} else {
|
||||
round += 1;
|
||||
}
|
||||
}
|
||||
|
||||
println!("{length:?}");
|
||||
println!("{:?}", playlist.program[playlist.program.len() - 1]);
|
||||
}
|
||||
}
|
@ -15,9 +15,16 @@ pub const DUMMY_LEN: f64 = 60.0;
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Playlist {
|
||||
pub date: String,
|
||||
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub start_sec: Option<f64>,
|
||||
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub current_file: Option<String>,
|
||||
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub modified: Option<String>,
|
||||
|
||||
pub program: Vec<Media>,
|
||||
}
|
||||
|
||||
@ -86,8 +93,8 @@ pub fn read_json(
|
||||
playlist.start_sec = Some(start_sec.clone());
|
||||
let modify = modified_time(¤t_file);
|
||||
|
||||
if modify.is_some() {
|
||||
playlist.modified = Some(modify.unwrap().to_string());
|
||||
if let Some(modi) = modify {
|
||||
playlist.modified = Some(modi.to_string());
|
||||
}
|
||||
|
||||
for (i, item) in playlist.program.iter_mut().enumerate() {
|
@ -20,14 +20,16 @@ use simplelog::*;
|
||||
mod arg_parse;
|
||||
mod config;
|
||||
pub mod controller;
|
||||
pub mod json_reader;
|
||||
mod generator;
|
||||
pub mod json_serializer;
|
||||
mod json_validate;
|
||||
mod logging;
|
||||
|
||||
pub use arg_parse::get_args;
|
||||
pub use config::{init_config, GlobalConfig};
|
||||
pub use controller::{PlayerControl, PlayoutStatus, ProcessControl, ProcessUnit::*};
|
||||
pub use json_reader::{read_json, Playlist, DUMMY_LEN};
|
||||
pub use generator::generate_playlist;
|
||||
pub use json_serializer::{read_json, Playlist, DUMMY_LEN};
|
||||
pub use json_validate::validate_playlist;
|
||||
pub use logging::init_logging;
|
||||
|
||||
@ -35,19 +37,36 @@ use crate::filter::filter_chains;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Media {
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub begin: Option<f64>,
|
||||
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub index: Option<usize>,
|
||||
#[serde(rename = "in")]
|
||||
pub seek: f64,
|
||||
pub out: f64,
|
||||
pub duration: f64,
|
||||
|
||||
#[serde(skip_deserializing)]
|
||||
pub category: Option<String>,
|
||||
pub source: String,
|
||||
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub cmd: Option<Vec<String>>,
|
||||
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub filter: Option<Vec<String>>,
|
||||
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub probe: Option<MediaProbe>,
|
||||
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub last_ad: Option<bool>,
|
||||
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub next_ad: Option<bool>,
|
||||
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub process: Option<bool>,
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user