diff --git a/assets/ffplayout.yml b/assets/ffplayout.yml index 2dece761..675bc305 100644 --- a/assets/ffplayout.yml +++ b/assets/ffplayout.yml @@ -117,6 +117,13 @@ text: style: "x=(w-tw)/2:y=(h-line_h)*0.9:fontsize=24:fontcolor=#ffffff:box=1:boxcolor=#000000:boxborderw=4" regex: ^.+[/\\](.*)(.mp4|.mkv)$ +task: + help_text: Run an external program with a given media object. The media object is in json format + and contains all the information about the current clip. The external program can be a script + or a binary, but should only run for a short time. + enable: false + path: + out: help_text: The final playout compression. Set the settings to your needs. 'mode' has the options 'desktop', 'hls', 'null', 'stream'. Use 'stream' and adjust diff --git a/ffplayout-engine/src/output/mod.rs b/ffplayout-engine/src/output/mod.rs index 96b587e1..ef51a65b 100644 --- a/ffplayout-engine/src/output/mod.rs +++ b/ffplayout-engine/src/output/mod.rs @@ -1,5 +1,6 @@ use std::{ io::{prelude::*, BufReader, BufWriter, Read}, + path::Path, process::{Command, Stdio}, sync::atomic::Ordering, thread::{self, sleep}, @@ -17,6 +18,8 @@ mod stream; pub use hls::write_hls; use crate::input::{ingest_server, source_generator}; +use crate::utils::task_runner; + use ffplayout_lib::utils::{ sec_to_time, stderr_reader, OutputMode::*, PlayerControl, PlayoutConfig, PlayoutStatus, ProcessControl, ProcessUnit::*, @@ -83,11 +86,6 @@ pub fn player( 'source_iter: for node in get_source { *play_control.current_media.lock().unwrap() = Some(node.clone()); - let mut cmd = match node.cmd { - Some(cmd) => cmd, - None => break, - }; - if !node.process.unwrap() { continue; } @@ -99,6 +97,23 @@ pub fn player( node.audio ); + if config.task.enable { + let task_config = config.clone(); + let task_node = node.clone(); + let server_running = proc_control.server_is_running.load(Ordering::SeqCst); + + if Path::new(&config.task.path).is_file() { + thread::spawn(move || task_runner::run(task_config, task_node, server_running)); + } else { + error!("{} executable not exists!", config.task.path); + } + } + + let mut cmd = match node.cmd { + Some(cmd) => cmd, + None => break, + }; + let mut dec_cmd = vec_strings!["-hide_banner", "-nostats", "-v", &ff_log_format]; dec_cmd.append(&mut cmd); diff --git a/ffplayout-engine/src/rpc/server.rs b/ffplayout-engine/src/rpc/server.rs index c3015cc3..0401f784 100644 --- a/ffplayout-engine/src/rpc/server.rs +++ b/ffplayout-engine/src/rpc/server.rs @@ -13,9 +13,10 @@ use std::io::{Cursor, Error as IoError}; 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, get_sec, sec_to_time, write_status, Ingest, Media, OutputMode::*, PlayerControl, - PlayoutConfig, PlayoutStatus, ProcessControl, + get_delta, write_status, Ingest, OutputMode::*, PlayerControl, PlayoutConfig, + PlayoutStatus, ProcessControl, }; #[derive(Default, Deserialize, Clone, Debug)] @@ -129,45 +130,6 @@ fn filter_from_json(raw_text: serde_json::Value) -> String { filter.to_string() } -/// map media struct to json object -fn get_media_map(media: Media) -> Value { - json!({ - "seek": media.seek, - "out": media.out, - "duration": media.duration, - "category": media.category, - "source": media.source, - }) -} - -/// prepare json object for response -fn get_data_map( - config: &PlayoutConfig, - media: Media, - server_is_running: bool, -) -> Map { - let mut data_map = Map::new(); - let begin = media.begin.unwrap_or(0.0); - - data_map.insert("play_mode".to_string(), json!(config.processing.mode)); - data_map.insert("ingest_runs".to_string(), json!(server_is_running)); - data_map.insert("index".to_string(), json!(media.index)); - data_map.insert("start_sec".to_string(), json!(begin)); - - if begin > 0.0 { - let played_time = get_sec() - begin; - let remaining_time = media.out - played_time; - - data_map.insert("start_time".to_string(), json!(sec_to_time(begin))); - data_map.insert("played_sec".to_string(), json!(played_time)); - data_map.insert("remaining_sec".to_string(), json!(remaining_time)); - } - - data_map.insert("current_media".to_string(), get_media_map(media)); - - data_map -} - #[derive(Debug, Serialize, Deserialize)] struct ResponseData { message: String, diff --git a/ffplayout-engine/src/utils/mod.rs b/ffplayout-engine/src/utils/mod.rs index faee87d8..2affad7e 100644 --- a/ffplayout-engine/src/utils/mod.rs +++ b/ffplayout-engine/src/utils/mod.rs @@ -4,14 +4,18 @@ use std::{ }; use regex::Regex; +use serde_json::{json, Map, Value}; use simplelog::*; pub mod arg_parse; +pub mod task_runner; pub use arg_parse::Args; use ffplayout_lib::{ filter::Filters, - utils::{time_to_sec, OutputMode::*, PlayoutConfig, ProcessMode::*}, + utils::{ + get_sec, sec_to_time, time_to_sec, Media, OutputMode::*, PlayoutConfig, ProcessMode::*, + }, vec_strings, }; @@ -207,3 +211,42 @@ pub fn prepare_output_cmd( cmd } + +/// map media struct to json object +pub fn get_media_map(media: Media) -> Value { + json!({ + "seek": media.seek, + "out": media.out, + "duration": media.duration, + "category": media.category, + "source": media.source, + }) +} + +/// prepare json object for response +pub fn get_data_map( + config: &PlayoutConfig, + media: Media, + server_is_running: bool, +) -> Map { + let mut data_map = Map::new(); + let begin = media.begin.unwrap_or(0.0); + + data_map.insert("play_mode".to_string(), json!(config.processing.mode)); + data_map.insert("ingest_runs".to_string(), json!(server_is_running)); + data_map.insert("index".to_string(), json!(media.index)); + data_map.insert("start_sec".to_string(), json!(begin)); + + if begin > 0.0 { + let played_time = get_sec() - begin; + let remaining_time = media.out - played_time; + + data_map.insert("start_time".to_string(), json!(sec_to_time(begin))); + data_map.insert("played_sec".to_string(), json!(played_time)); + data_map.insert("remaining_sec".to_string(), json!(remaining_time)); + } + + data_map.insert("current_media".to_string(), get_media_map(media)); + + data_map +} diff --git a/ffplayout-engine/src/utils/task_runner.rs b/ffplayout-engine/src/utils/task_runner.rs new file mode 100644 index 00000000..63c62d8e --- /dev/null +++ b/ffplayout-engine/src/utils/task_runner.rs @@ -0,0 +1,15 @@ +use std::process::Command; + +use simplelog::*; + +use crate::utils::get_data_map; +use ffplayout_lib::utils::{config::PlayoutConfig, Media}; + +pub fn run(config: PlayoutConfig, node: Media, server_running: bool) { + let obj = serde_json::to_string(&get_data_map(&config, node, server_running)).unwrap(); + trace!("Run task: {obj}"); + + if let Err(e) = Command::new(config.task.path).arg(obj).spawn() { + error!("Couldn't spawn task runner: {e}"); + }; +} diff --git a/lib/src/utils/config.rs b/lib/src/utils/config.rs index a2d1bbcb..1aab2ae5 100644 --- a/lib/src/utils/config.rs +++ b/lib/src/utils/config.rs @@ -138,6 +138,8 @@ pub struct PlayoutConfig { pub playlist: Playlist, pub storage: Storage, pub text: Text, + #[serde(default)] + pub task: Task, pub out: Out, } @@ -292,6 +294,12 @@ pub struct Text { pub regex: String, } +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct Task { + pub enable: bool, + pub path: String, +} + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Out { pub help_text: String, diff --git a/scripts/task-runner.sh b/scripts/task-runner.sh new file mode 100755 index 00000000..1606ad9f --- /dev/null +++ b/scripts/task-runner.sh @@ -0,0 +1,7 @@ +#!/usr/bin/bash + +# media object +mObj=$1 + +# perform a meaningful task +notify-send -u normal "ffplayout" -t 2 -e "Play: $(echo $mObj | jq -r '.current_media.source')"