start with documentations
This commit is contained in:
parent
03bb49c0b8
commit
17ee86afc1
43
src/main.rs
43
src/main.rs
@ -2,11 +2,10 @@ extern crate log;
|
|||||||
extern crate simplelog;
|
extern crate simplelog;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
{fs, fs::File},
|
fs::{self, File},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process::exit,
|
process::exit,
|
||||||
thread,
|
thread,
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -32,26 +31,26 @@ struct StatusData {
|
|||||||
date: String,
|
date: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
/// Here we create a status file in temp folder.
|
||||||
init_config();
|
/// We need ths for reading/saving program status.
|
||||||
let config = GlobalConfig::global();
|
/// For example when we skip a playing file,
|
||||||
let play_control = PlayerControl::new();
|
/// we save the time difference, so we stay in sync.
|
||||||
let playout_stat = PlayoutStatus::new();
|
///
|
||||||
let proc_control = ProcessControl::new();
|
/// When file not exists we create it, and when it exists we get its values.
|
||||||
|
fn status_file(stat_file: &str, playout_stat: &PlayoutStatus) {
|
||||||
if !PathBuf::from(config.general.stat_file.clone()).exists() {
|
if !PathBuf::from(stat_file).exists() {
|
||||||
let data = json!({
|
let data = json!({
|
||||||
"time_shift": 0.0,
|
"time_shift": 0.0,
|
||||||
"date": String::new(),
|
"date": String::new(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let json: String = serde_json::to_string(&data).expect("Serialize status data failed");
|
let json: String = serde_json::to_string(&data).expect("Serialize status data failed");
|
||||||
fs::write(config.general.stat_file.clone(), &json).expect("Unable to write file");
|
fs::write(stat_file, &json).expect("Unable to write file");
|
||||||
} else {
|
} else {
|
||||||
let stat_file = File::options()
|
let stat_file = File::options()
|
||||||
.read(true)
|
.read(true)
|
||||||
.write(false)
|
.write(false)
|
||||||
.open(&config.general.stat_file)
|
.open(&stat_file)
|
||||||
.expect("Could not open status file");
|
.expect("Could not open status file");
|
||||||
|
|
||||||
let data: StatusData =
|
let data: StatusData =
|
||||||
@ -60,13 +59,24 @@ fn main() {
|
|||||||
*playout_stat.time_shift.lock().unwrap() = data.time_shift;
|
*playout_stat.time_shift.lock().unwrap() = data.time_shift;
|
||||||
*playout_stat.date.lock().unwrap() = data.date;
|
*playout_stat.date.lock().unwrap() = data.date;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Init the config, set process controller, create logging.
|
||||||
|
init_config();
|
||||||
|
let config = GlobalConfig::global();
|
||||||
|
let play_control = PlayerControl::new();
|
||||||
|
let playout_stat = PlayoutStatus::new();
|
||||||
|
let proc_control = ProcessControl::new();
|
||||||
|
|
||||||
let logging = init_logging();
|
let logging = init_logging();
|
||||||
CombinedLogger::init(logging).unwrap();
|
CombinedLogger::init(logging).unwrap();
|
||||||
|
|
||||||
validate_ffmpeg();
|
validate_ffmpeg();
|
||||||
|
status_file(&config.general.stat_file, &playout_stat);
|
||||||
|
|
||||||
if let Some(range) = config.general.generate.clone() {
|
if let Some(range) = config.general.generate.clone() {
|
||||||
|
// run a simple playlist generator and save them to disk
|
||||||
generate_playlist(range);
|
generate_playlist(range);
|
||||||
|
|
||||||
exit(0);
|
exit(0);
|
||||||
@ -77,16 +87,15 @@ fn main() {
|
|||||||
let proc_ctl = proc_control.clone();
|
let proc_ctl = proc_control.clone();
|
||||||
|
|
||||||
if config.rpc_server.enable {
|
if config.rpc_server.enable {
|
||||||
thread::spawn( move || json_rpc_server(
|
// If RPC server is enable we also fire up a JSON RPC server.
|
||||||
play_ctl,
|
thread::spawn(move || json_rpc_server(play_ctl, play_stat, proc_ctl));
|
||||||
play_stat,
|
|
||||||
proc_ctl,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if &config.out.mode.to_lowercase() == "hls" {
|
if &config.out.mode.to_lowercase() == "hls" {
|
||||||
|
// write files/playlist to HLS m3u8 playlist
|
||||||
write_hls(play_control, playout_stat, proc_control);
|
write_hls(play_control, playout_stat, proc_control);
|
||||||
} else {
|
} else {
|
||||||
|
// play on desktop or stream to a remote target
|
||||||
player(play_control, playout_stat, proc_control);
|
player(play_control, playout_stat, proc_control);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +54,7 @@ pub struct Args {
|
|||||||
pub volume: Option<f64>,
|
pub volume: Option<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get arguments from command line, and return them.
|
||||||
pub fn get_args() -> Args {
|
pub fn get_args() -> Args {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
|
@ -12,6 +12,9 @@ use shlex::split;
|
|||||||
|
|
||||||
use crate::utils::{get_args, time_to_sec};
|
use crate::utils::{get_args, time_to_sec};
|
||||||
|
|
||||||
|
/// Global Config
|
||||||
|
///
|
||||||
|
/// This we init ones, when ffplayout is starting and use them globally in the hole program.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct GlobalConfig {
|
pub struct GlobalConfig {
|
||||||
pub general: General,
|
pub general: General,
|
||||||
@ -132,6 +135,7 @@ pub struct Out {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalConfig {
|
impl GlobalConfig {
|
||||||
|
/// Read config from YAML file, and set some extra config values.
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
let args = get_args();
|
let args = get_args();
|
||||||
let mut config_path = match env::current_exe() {
|
let mut config_path = match env::current_exe() {
|
||||||
@ -173,6 +177,7 @@ impl GlobalConfig {
|
|||||||
config.playlist.length_sec = Some(86400.0);
|
config.playlist.length_sec = Some(86400.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We set the decoder settings here, so we only define them ones.
|
||||||
let mut settings: Vec<String> = vec![
|
let mut settings: Vec<String> = vec![
|
||||||
"-pix_fmt",
|
"-pix_fmt",
|
||||||
"yuv420p",
|
"yuv420p",
|
||||||
@ -209,6 +214,8 @@ impl GlobalConfig {
|
|||||||
config.out.preview_cmd = split(config.out.preview_param.as_str());
|
config.out.preview_cmd = split(config.out.preview_param.as_str());
|
||||||
config.out.output_cmd = split(config.out.output_param.as_str());
|
config.out.output_cmd = split(config.out.output_param.as_str());
|
||||||
|
|
||||||
|
// Read command line arguments, and override the config with them.
|
||||||
|
|
||||||
if let Some(gen) = args.generate {
|
if let Some(gen) = args.generate {
|
||||||
config.general.generate = Some(gen);
|
config.general.generate = Some(gen);
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ use simplelog::*;
|
|||||||
|
|
||||||
use crate::utils::Media;
|
use crate::utils::Media;
|
||||||
|
|
||||||
|
/// Defined process units.
|
||||||
pub enum ProcessUnit {
|
pub enum ProcessUnit {
|
||||||
Decoder,
|
Decoder,
|
||||||
Encoder,
|
Encoder,
|
||||||
@ -30,6 +31,10 @@ impl fmt::Display for ProcessUnit {
|
|||||||
|
|
||||||
use ProcessUnit::*;
|
use ProcessUnit::*;
|
||||||
|
|
||||||
|
/// Process Controller
|
||||||
|
///
|
||||||
|
/// We save here some global states, about what is running and which processes are alive.
|
||||||
|
/// This we need for process termination, skipping clip decoder etc.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ProcessControl {
|
pub struct ProcessControl {
|
||||||
pub decoder_term: Arc<Mutex<Option<Child>>>,
|
pub decoder_term: Arc<Mutex<Option<Child>>>,
|
||||||
@ -88,6 +93,8 @@ impl ProcessControl {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wait for process to proper close.
|
||||||
|
/// This prevents orphaned/zombi processes in system
|
||||||
pub fn wait(&mut self, proc: ProcessUnit) -> Result<(), String> {
|
pub fn wait(&mut self, proc: ProcessUnit) -> Result<(), String> {
|
||||||
match proc {
|
match proc {
|
||||||
Decoder => {
|
Decoder => {
|
||||||
@ -116,6 +123,7 @@ impl ProcessControl {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// No matter what is running, terminate them all.
|
||||||
pub fn kill_all(&mut self) {
|
pub fn kill_all(&mut self) {
|
||||||
self.is_terminated.store(true, Ordering::SeqCst);
|
self.is_terminated.store(true, Ordering::SeqCst);
|
||||||
|
|
||||||
@ -141,6 +149,7 @@ impl Drop for ProcessControl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Global player control, to get infos about current clip etc.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PlayerControl {
|
pub struct PlayerControl {
|
||||||
pub current_media: Arc<Mutex<Option<Media>>>,
|
pub current_media: Arc<Mutex<Option<Media>>>,
|
||||||
@ -158,6 +167,7 @@ impl PlayerControl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Global playout control, for move forward/backward clip, or resetting playlist/state.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct PlayoutStatus {
|
pub struct PlayoutStatus {
|
||||||
pub time_shift: Arc<Mutex<f64>>,
|
pub time_shift: Arc<Mutex<f64>>,
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
/// Simple Playlist Generator
|
||||||
|
///
|
||||||
|
/// You can call ffplayout[.exe] -g YYYY-mm-dd - YYYY-mm-dd to generate JSON playlists.
|
||||||
|
///
|
||||||
|
/// The generator takes the files from storage, which are set in config.
|
||||||
|
/// It also respect the shuffle/sort mode.
|
||||||
|
///
|
||||||
|
/// Beside that it is really very basic, without any logic.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
fs::{create_dir_all, write},
|
fs::{create_dir_all, write},
|
||||||
path::Path,
|
path::Path,
|
||||||
@ -11,6 +20,8 @@ use simplelog::*;
|
|||||||
use crate::input::Source;
|
use crate::input::Source;
|
||||||
use crate::utils::{json_serializer::Playlist, GlobalConfig, Media};
|
use crate::utils::{json_serializer::Playlist, GlobalConfig, Media};
|
||||||
|
|
||||||
|
|
||||||
|
/// Generate a vector with dates, from given range.
|
||||||
fn get_date_range(date_range: &Vec<String>) -> Vec<String> {
|
fn get_date_range(date_range: &Vec<String>) -> Vec<String> {
|
||||||
let mut range = vec![];
|
let mut range = vec![];
|
||||||
let start;
|
let start;
|
||||||
@ -46,6 +57,7 @@ fn get_date_range(date_range: &Vec<String>) -> Vec<String> {
|
|||||||
range
|
range
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate playlists
|
||||||
pub fn generate_playlist(mut date_range: Vec<String>) {
|
pub fn generate_playlist(mut date_range: Vec<String>) {
|
||||||
let config = GlobalConfig::global();
|
let config = GlobalConfig::global();
|
||||||
let total_length = config.playlist.length_sec.unwrap().clone();
|
let total_length = config.playlist.length_sec.unwrap().clone();
|
||||||
|
@ -12,6 +12,7 @@ use crate::utils::{get_date, modified_time, validate_playlist, GlobalConfig, Med
|
|||||||
|
|
||||||
pub const DUMMY_LEN: f64 = 60.0;
|
pub const DUMMY_LEN: f64 = 60.0;
|
||||||
|
|
||||||
|
/// This is our main playlist object, it holds all necessary information for the current day.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Playlist {
|
pub struct Playlist {
|
||||||
pub date: String,
|
pub date: String,
|
||||||
@ -44,6 +45,8 @@ impl Playlist {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read json playlist file, fills Playlist struct and set some extra values,
|
||||||
|
/// which we need to process.
|
||||||
pub fn read_json(
|
pub fn read_json(
|
||||||
path: Option<String>,
|
path: Option<String>,
|
||||||
is_terminated: Arc<AtomicBool>,
|
is_terminated: Arc<AtomicBool>,
|
||||||
@ -96,6 +99,7 @@ pub fn read_json(
|
|||||||
playlist.modified = Some(modi.to_string());
|
playlist.modified = Some(modi.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add extra values to every media clip
|
||||||
for (i, item) in playlist.program.iter_mut().enumerate() {
|
for (i, item) in playlist.program.iter_mut().enumerate() {
|
||||||
item.begin = Some(start_sec);
|
item.begin = Some(start_sec);
|
||||||
item.index = Some(i);
|
item.index = Some(i);
|
||||||
|
@ -4,6 +4,14 @@ use simplelog::*;
|
|||||||
|
|
||||||
use crate::utils::{sec_to_time, GlobalConfig, MediaProbe, Playlist};
|
use crate::utils::{sec_to_time, GlobalConfig, MediaProbe, Playlist};
|
||||||
|
|
||||||
|
|
||||||
|
/// Validate a given playlist, to check if:
|
||||||
|
///
|
||||||
|
/// - the source files are existing
|
||||||
|
/// - file can be read by ffprobe and metadata exists
|
||||||
|
/// - total playtime fits target length from config
|
||||||
|
///
|
||||||
|
/// This function we run in a thread, to don't block the main function.
|
||||||
pub fn validate_playlist(playlist: Playlist, is_terminated: Arc<AtomicBool>, config: GlobalConfig) {
|
pub fn validate_playlist(playlist: Playlist, is_terminated: Arc<AtomicBool>, config: GlobalConfig) {
|
||||||
let date = playlist.date;
|
let date = playlist.date;
|
||||||
let mut length = config.playlist.length_sec.unwrap();
|
let mut length = config.playlist.length_sec.unwrap();
|
||||||
|
@ -21,6 +21,7 @@ use simplelog::*;
|
|||||||
|
|
||||||
use crate::utils::GlobalConfig;
|
use crate::utils::GlobalConfig;
|
||||||
|
|
||||||
|
/// send log messages to mail recipient
|
||||||
fn send_mail(msg: String) {
|
fn send_mail(msg: String) {
|
||||||
let config = GlobalConfig::global();
|
let config = GlobalConfig::global();
|
||||||
|
|
||||||
@ -52,9 +53,10 @@ fn send_mail(msg: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Basic Mail Queue
|
||||||
|
///
|
||||||
|
/// Check every give seconds for messages and send them.
|
||||||
fn mail_queue(messages: Arc<Mutex<Vec<String>>>, interval: u64) {
|
fn mail_queue(messages: Arc<Mutex<Vec<String>>>, interval: u64) {
|
||||||
// check every give seconds for messages and send them
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if messages.lock().unwrap().len() > 0 {
|
if messages.lock().unwrap().len() > 0 {
|
||||||
let msg = messages.lock().unwrap().join("\n");
|
let msg = messages.lock().unwrap().join("\n");
|
||||||
@ -67,6 +69,7 @@ fn mail_queue(messages: Arc<Mutex<Vec<String>>>, interval: u64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Self made Mail Log struct, to extend simplelog.
|
||||||
pub struct LogMailer {
|
pub struct LogMailer {
|
||||||
level: LevelFilter,
|
level: LevelFilter,
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
@ -121,12 +124,20 @@ impl SharedLogger for LogMailer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Workaround to remove color information from log
|
||||||
|
///
|
||||||
|
/// ToDo: maybe in next version from simplelog this is not necessary anymore.
|
||||||
fn clean_string(text: &str) -> String {
|
fn clean_string(text: &str) -> String {
|
||||||
let regex: Regex = Regex::new(r"\x1b\[[0-9;]*[mGKF]").unwrap();
|
let regex: Regex = Regex::new(r"\x1b\[[0-9;]*[mGKF]").unwrap();
|
||||||
|
|
||||||
regex.replace_all(text, "").to_string()
|
regex.replace_all(text, "").to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initialize our logging, to have:
|
||||||
|
///
|
||||||
|
/// - console logger
|
||||||
|
/// - file logger
|
||||||
|
/// - mail logger
|
||||||
pub fn init_logging() -> Vec<Box<dyn SharedLogger>> {
|
pub fn init_logging() -> Vec<Box<dyn SharedLogger>> {
|
||||||
let config = GlobalConfig::global();
|
let config = GlobalConfig::global();
|
||||||
let app_config = config.logging.clone();
|
let app_config = config.logging.clone();
|
||||||
@ -191,6 +202,7 @@ pub fn init_logging() -> Vec<Box<dyn SharedLogger>> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set mail logger only the recipient is set in config
|
||||||
if config.mail.recipient.contains("@") && config.mail.recipient.contains(".") {
|
if config.mail.recipient.contains("@") && config.mail.recipient.contains(".") {
|
||||||
let messages: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
|
let messages: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
|
||||||
let messages_clone = messages.clone();
|
let messages_clone = messages.clone();
|
||||||
|
@ -35,6 +35,7 @@ pub use logging::init_logging;
|
|||||||
|
|
||||||
use crate::filter::filter_chains;
|
use crate::filter::filter_chains;
|
||||||
|
|
||||||
|
/// Video clip struct to hold some important states and comments for current media.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Media {
|
pub struct Media {
|
||||||
#[serde(skip_serializing, skip_deserializing)]
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
@ -124,6 +125,8 @@ impl Media {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// We use the ffprobe crate, but we map the metadata to our needs.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct MediaProbe {
|
pub struct MediaProbe {
|
||||||
pub format: Option<Format>,
|
pub format: Option<Format>,
|
||||||
@ -185,6 +188,9 @@ impl MediaProbe {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write current status to status file in temp folder.
|
||||||
|
///
|
||||||
|
/// The status file is init in main function and mostly modified in RPC server.
|
||||||
pub fn write_status(date: &str, shift: f64) {
|
pub fn write_status(date: &str, shift: f64) {
|
||||||
let config = GlobalConfig::global();
|
let config = GlobalConfig::global();
|
||||||
let stat_file = config.general.stat_file.clone();
|
let stat_file = config.general.stat_file.clone();
|
||||||
@ -206,6 +212,7 @@ pub fn write_status(date: &str, shift: f64) {
|
|||||||
// local.timestamp_millis() as i64
|
// local.timestamp_millis() as i64
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
/// Get current time in seconds.
|
||||||
pub fn get_sec() -> f64 {
|
pub fn get_sec() -> f64 {
|
||||||
let local: DateTime<Local> = Local::now();
|
let local: DateTime<Local> = Local::now();
|
||||||
|
|
||||||
@ -213,6 +220,10 @@ pub fn get_sec() -> f64 {
|
|||||||
+ (local.nanosecond() as f64 / 1000000000.0)
|
+ (local.nanosecond() as f64 / 1000000000.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get current date for playlist, but check time with conditions:
|
||||||
|
///
|
||||||
|
/// - When time is before playlist start, get date from yesterday.
|
||||||
|
/// - When given next_start is over target length (normally a full day), get date from tomorrow.
|
||||||
pub fn get_date(seek: bool, start: f64, next_start: f64) -> String {
|
pub fn get_date(seek: bool, start: f64, next_start: f64) -> String {
|
||||||
let local: DateTime<Local> = Local::now();
|
let local: DateTime<Local> = Local::now();
|
||||||
|
|
||||||
@ -227,6 +238,7 @@ pub fn get_date(seek: bool, start: f64, next_start: f64) -> String {
|
|||||||
local.format("%Y-%m-%d").to_string()
|
local.format("%Y-%m-%d").to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get file modification time.
|
||||||
pub fn modified_time(path: &str) -> Option<DateTime<Local>> {
|
pub fn modified_time(path: &str) -> Option<DateTime<Local>> {
|
||||||
let metadata = metadata(path).unwrap();
|
let metadata = metadata(path).unwrap();
|
||||||
|
|
||||||
@ -238,6 +250,7 @@ pub fn modified_time(path: &str) -> Option<DateTime<Local>> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert a formatted time string to seconds.
|
||||||
pub fn time_to_sec(time_str: &str) -> f64 {
|
pub fn time_to_sec(time_str: &str) -> f64 {
|
||||||
if ["now", "", "none"].contains(&time_str) || !time_str.contains(":") {
|
if ["now", "", "none"].contains(&time_str) || !time_str.contains(":") {
|
||||||
return get_sec();
|
return get_sec();
|
||||||
@ -251,6 +264,7 @@ pub fn time_to_sec(time_str: &str) -> f64 {
|
|||||||
h * 3600.0 + m * 60.0 + s
|
h * 3600.0 + m * 60.0 + s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert floating number (seconds) to a formatted time string.
|
||||||
pub fn sec_to_time(sec: f64) -> String {
|
pub fn sec_to_time(sec: f64) -> String {
|
||||||
let d = UNIX_EPOCH + time::Duration::from_millis((sec * 1000.0) as u64);
|
let d = UNIX_EPOCH + time::Duration::from_millis((sec * 1000.0) as u64);
|
||||||
// Create DateTime from SystemTime
|
// Create DateTime from SystemTime
|
||||||
@ -259,6 +273,8 @@ pub fn sec_to_time(sec: f64) -> String {
|
|||||||
date_time.format("%H:%M:%S%.3f").to_string()
|
date_time.format("%H:%M:%S%.3f").to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test if given numbers are close to each other,
|
||||||
|
/// with a third number for setting the maximum range.
|
||||||
pub fn is_close(a: f64, b: f64, to: f64) -> bool {
|
pub fn is_close(a: f64, b: f64, to: f64) -> bool {
|
||||||
if (a - b).abs() < to {
|
if (a - b).abs() < to {
|
||||||
return true;
|
return true;
|
||||||
@ -267,6 +283,10 @@ pub fn is_close(a: f64, b: f64, to: f64) -> bool {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get delta between clip start and current time. This value we need to check,
|
||||||
|
/// if we still in sync.
|
||||||
|
///
|
||||||
|
/// We also get here the global delta between clip start and time when a new playlist should start.
|
||||||
pub fn get_delta(begin: &f64) -> (f64, f64) {
|
pub fn get_delta(begin: &f64) -> (f64, f64) {
|
||||||
let config = GlobalConfig::global();
|
let config = GlobalConfig::global();
|
||||||
let mut current_time = get_sec();
|
let mut current_time = get_sec();
|
||||||
@ -299,6 +319,7 @@ pub fn get_delta(begin: &f64) -> (f64, f64) {
|
|||||||
(current_delta, total_delta)
|
(current_delta, total_delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if clip in playlist is in sync with global time.
|
||||||
pub fn check_sync(delta: f64) -> bool {
|
pub fn check_sync(delta: f64) -> bool {
|
||||||
let config = GlobalConfig::global();
|
let config = GlobalConfig::global();
|
||||||
|
|
||||||
@ -310,6 +331,7 @@ pub fn check_sync(delta: f64) -> bool {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a dummy clip as a placeholder for missing video files.
|
||||||
pub fn gen_dummy(duration: f64) -> (String, Vec<String>) {
|
pub fn gen_dummy(duration: f64) -> (String, Vec<String>) {
|
||||||
let config = GlobalConfig::global();
|
let config = GlobalConfig::global();
|
||||||
let color = "#121212";
|
let color = "#121212";
|
||||||
@ -334,6 +356,7 @@ pub fn gen_dummy(duration: f64) -> (String, Vec<String>) {
|
|||||||
(source, cmd)
|
(source, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set clip seek in and length value.
|
||||||
pub fn seek_and_length(src: String, seek: f64, out: f64, duration: f64) -> Vec<String> {
|
pub fn seek_and_length(src: String, seek: f64, out: f64, duration: f64) -> Vec<String> {
|
||||||
let mut source_cmd: Vec<String> = vec![];
|
let mut source_cmd: Vec<String> = vec![];
|
||||||
|
|
||||||
@ -353,16 +376,13 @@ pub fn seek_and_length(src: String, seek: f64, out: f64, duration: f64) -> Vec<S
|
|||||||
source_cmd
|
source_cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read ffmpeg stderr decoder, encoder and server instance
|
||||||
|
/// and log the output.
|
||||||
pub fn stderr_reader(buffer: BufReader<ChildStderr>, suffix: &str) -> Result<(), Error> {
|
pub fn stderr_reader(buffer: BufReader<ChildStderr>, suffix: &str) -> Result<(), Error> {
|
||||||
// read ffmpeg stderr decoder, encoder and server instance
|
|
||||||
// and log the output
|
|
||||||
|
|
||||||
fn format_line(line: String, level: &str) -> String {
|
fn format_line(line: String, level: &str) -> String {
|
||||||
line.replace(&format!("[{level: >5}] "), "")
|
line.replace(&format!("[{level: >5}] "), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// let buffer = BufReader::new(std_errors);
|
|
||||||
|
|
||||||
for line in buffer.lines() {
|
for line in buffer.lines() {
|
||||||
let line = line?;
|
let line = line?;
|
||||||
|
|
||||||
@ -389,6 +409,7 @@ pub fn stderr_reader(buffer: BufReader<ChildStderr>, suffix: &str) -> Result<(),
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Run program to test if it is in system.
|
||||||
fn is_in_system(name: &str) {
|
fn is_in_system(name: &str) {
|
||||||
if let Ok(mut proc) = Command::new(name)
|
if let Ok(mut proc) = Command::new(name)
|
||||||
.stderr(Stdio::null())
|
.stderr(Stdio::null())
|
||||||
@ -407,6 +428,8 @@ fn is_in_system(name: &str) {
|
|||||||
fn ffmpeg_libs_and_filter() -> (Vec<String>, Vec<String>) {
|
fn ffmpeg_libs_and_filter() -> (Vec<String>, Vec<String>) {
|
||||||
let mut libs: Vec<String> = vec![];
|
let mut libs: Vec<String> = vec![];
|
||||||
let mut filters: Vec<String> = vec![];
|
let mut filters: Vec<String> = vec![];
|
||||||
|
|
||||||
|
// filter lines which contains filter
|
||||||
let re: Regex = Regex::new(r"^( ?) [TSC.]+").unwrap();
|
let re: Regex = Regex::new(r"^( ?) [TSC.]+").unwrap();
|
||||||
|
|
||||||
let mut ff_proc = match Command::new("ffmpeg")
|
let mut ff_proc = match Command::new("ffmpeg")
|
||||||
@ -425,6 +448,8 @@ fn ffmpeg_libs_and_filter() -> (Vec<String>, Vec<String>) {
|
|||||||
let err_buffer = BufReader::new(ff_proc.stderr.take().unwrap());
|
let err_buffer = BufReader::new(ff_proc.stderr.take().unwrap());
|
||||||
let out_buffer = BufReader::new(ff_proc.stdout.take().unwrap());
|
let out_buffer = BufReader::new(ff_proc.stdout.take().unwrap());
|
||||||
|
|
||||||
|
// stderr shows only the ffmpeg configuration
|
||||||
|
// get codec library's
|
||||||
for line in err_buffer.lines() {
|
for line in err_buffer.lines() {
|
||||||
if let Ok(line) = line {
|
if let Ok(line) = line {
|
||||||
if line.contains("configuration:") {
|
if line.contains("configuration:") {
|
||||||
@ -439,6 +464,8 @@ fn ffmpeg_libs_and_filter() -> (Vec<String>, Vec<String>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stdout shows filter help text
|
||||||
|
// get filters
|
||||||
for line in out_buffer.lines() {
|
for line in out_buffer.lines() {
|
||||||
if let Ok(line) = line {
|
if let Ok(line) = line {
|
||||||
if let Some(_) = re.captures(line.as_str()) {
|
if let Some(_) = re.captures(line.as_str()) {
|
||||||
@ -455,6 +482,10 @@ fn ffmpeg_libs_and_filter() -> (Vec<String>, Vec<String>) {
|
|||||||
|
|
||||||
(libs, filters)
|
(libs, filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validate ffmpeg/ffprobe/ffplay.
|
||||||
|
///
|
||||||
|
/// Check if they are in system and has all filters and codecs we need.
|
||||||
pub fn validate_ffmpeg() {
|
pub fn validate_ffmpeg() {
|
||||||
let config = GlobalConfig::global();
|
let config = GlobalConfig::global();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user