diff --git a/src/filter/mod.rs b/src/filter/mod.rs index 26ec10c8..deff3a8e 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -4,7 +4,7 @@ use simplelog::*; pub mod v_drawtext; -use crate::utils::{get_delta, is_close, GlobalConfig, Media}; +use crate::utils::{get_delta, is_close, GlobalConfig, Media, PlayoutStatus}; #[derive(Debug, Clone)] struct Filters { @@ -290,9 +290,9 @@ fn realtime_filter( chain: &mut Filters, config: &GlobalConfig, codec_type: String, - json_date: &String + playout_stat: &PlayoutStatus, ) { - //this realtime filter is important for HLS output to stay in sync + // this realtime filter is important for HLS output to stay in sync let mut t = "".to_string(); @@ -302,7 +302,7 @@ fn realtime_filter( if config.out.mode.to_lowercase() == "hls".to_string() { let mut speed_filter = format!("{t}realtime=speed=1"); - let (delta, _) = get_delta(&node.begin.unwrap(), &json_date, true); + let (delta, _) = get_delta(&node.begin.unwrap(), &playout_stat, true); let duration = node.out - node.seek; if delta < 0.0 { @@ -317,7 +317,7 @@ fn realtime_filter( } } -pub fn filter_chains(node: &mut Media, json_date: &String) -> Vec { +pub fn filter_chains(node: &mut Media, playout_stat: &PlayoutStatus) -> Vec { let config = GlobalConfig::global(); let mut filters = Filters::new(); @@ -355,12 +355,12 @@ pub fn filter_chains(node: &mut Media, json_date: &String) -> Vec { add_text(node, &mut filters, &config); fade(node, &mut filters, "video".into()); overlay(node, &mut filters, &config); - realtime_filter(node, &mut filters, &config, "video".into(), &json_date); + realtime_filter(node, &mut filters, &config, "video".into(), &playout_stat); add_loudnorm(node, &mut filters, &config); fade(node, &mut filters, "audio".into()); audio_volume(&mut filters, &config); - realtime_filter(node, &mut filters, &config, "audio".into(), &json_date); + realtime_filter(node, &mut filters, &config, "audio".into(), &playout_stat); let mut filter_cmd = vec![]; let mut filter_str: String = "".to_string(); diff --git a/src/input/folder.rs b/src/input/folder.rs index b7614b53..38cbbaf5 100644 --- a/src/input/folder.rs +++ b/src/input/folder.rs @@ -12,7 +12,7 @@ use std::{ use walkdir::WalkDir; -use crate::utils::{get_sec, GlobalConfig, Media}; +use crate::utils::{get_sec, GlobalConfig, Media, PlayoutStatus}; #[derive(Debug, Clone)] pub struct Source { @@ -20,10 +20,15 @@ pub struct Source { pub nodes: Arc>>, current_node: Media, index: Arc>, + playout_stat: PlayoutStatus, } impl Source { - pub fn new(current_list: Arc>>, global_index: Arc>) -> Self { + pub fn new( + current_list: Arc>>, + global_index: Arc>, + playout_stat: PlayoutStatus, + ) -> Self { let config = GlobalConfig::global(); let mut media_list = vec![]; let mut index: usize = 0; @@ -69,6 +74,7 @@ impl Source { nodes: current_list, current_node: Media::new(0, "".to_string(), false), index: global_index, + playout_stat, } } @@ -85,7 +91,10 @@ impl Source { } fn sort(&mut self) { - self.nodes.lock().unwrap().sort_by(|d1, d2| d1.source.cmp(&d2.source)); + self.nodes + .lock() + .unwrap() + .sort_by(|d1, d2| d1.source.cmp(&d2.source)); let mut index: usize = 0; for item in self.nodes.lock().unwrap().iter_mut() { @@ -104,7 +113,7 @@ impl Iterator for Source { let i = *self.index.lock().unwrap(); self.current_node = self.nodes.lock().unwrap()[i].clone(); self.current_node.add_probe(); - self.current_node.add_filter(&"".to_string()); + self.current_node.add_filter(&self.playout_stat); self.current_node.begin = Some(get_sec()); *self.index.lock().unwrap() += 1; @@ -121,7 +130,7 @@ impl Iterator for Source { self.current_node = self.nodes.lock().unwrap()[0].clone(); self.current_node.add_probe(); - self.current_node.add_filter(&"".to_string()); + self.current_node.add_filter(&self.playout_stat); self.current_node.begin = Some(get_sec()); *self.index.lock().unwrap() = 1; diff --git a/src/input/playlist.rs b/src/input/playlist.rs index e99ff835..edf299f4 100644 --- a/src/input/playlist.rs +++ b/src/input/playlist.rs @@ -1,8 +1,10 @@ use std::{ + fs, path::Path, sync::{Arc, Mutex}, }; +use serde_json::json; use simplelog::*; use tokio::runtime::Handle; @@ -24,23 +26,31 @@ pub struct CurrentProgram { index: Arc>, rt_handle: Handle, is_terminated: Arc>, + playout_stat: PlayoutStatus, } impl CurrentProgram { pub fn new( rt_handle: Handle, + playout_stat: PlayoutStatus, is_terminated: Arc>, current_list: Arc>>, global_index: Arc>, ) -> Self { let config = GlobalConfig::global(); - let status = PlayoutStatus::global(); let json = read_json(None, rt_handle.clone(), is_terminated.clone(), true, 0.0); *current_list.lock().unwrap() = json.program; + *playout_stat.current_date.lock().unwrap() = json.date.clone(); - if status.date != json.date { - status.clone().write(json.date.clone(), 0.0) + if *playout_stat.date.lock().unwrap() != json.date { + let data = json!({ + "time_shift": 0.0, + "date": json.date, + }); + + let json: String = serde_json::to_string(&data).expect("Serialize status data failed"); + fs::write(config.general.stat_file.clone(), &json).expect("Unable to write file"); } Self { @@ -55,6 +65,7 @@ impl CurrentProgram { index: global_index, rt_handle, is_terminated, + playout_stat, } } @@ -121,7 +132,7 @@ impl CurrentProgram { let current_time = get_sec(); let start_sec = self.config.playlist.start_sec.unwrap(); let target_length = self.config.playlist.length_sec.unwrap(); - let (delta, total_delta) = get_delta(¤t_time, &self.json_date, true); + let (delta, total_delta) = get_delta(¤t_time, &self.playout_stat, true); let mut duration = self.current_node.out.clone(); if self.current_node.duration > self.current_node.out { @@ -142,8 +153,14 @@ impl CurrentProgram { next_start, ); - let status = PlayoutStatus::global(); - status.clone().write(json.date.clone(), 0.0); + let data = json!({ + "time_shift": 0.0, + "date": json.date, + }); + + *self.playout_stat.current_date.lock().unwrap() = json.date.clone(); + let status_data: String = serde_json::to_string(&data).expect("Serialize status data failed"); + fs::write(self.config.general.stat_file.clone(), &status_data).expect("Unable to write file"); self.json_path = json.current_file.clone(); self.json_mod = json.modified; @@ -210,7 +227,7 @@ impl CurrentProgram { *self.index.lock().unwrap() += 1; node_clone.seek = time_sec - node_clone.begin.unwrap(); - self.current_node = handle_list_init(node_clone, &self.json_date); + self.current_node = handle_list_init(node_clone, &self.playout_stat); } } } @@ -245,7 +262,7 @@ impl Iterator for CurrentProgram { self.init_clip(); } else { let mut current_time = get_sec(); - let (_, total_delta) = get_delta(¤t_time, &self.json_date, true); + let (_, total_delta) = get_delta(¤t_time, &self.playout_stat, true); let mut duration = DUMMY_LEN; if DUMMY_LEN > total_delta { @@ -261,7 +278,7 @@ impl Iterator for CurrentProgram { media.duration = duration; media.out = duration; - self.current_node = gen_source(media, &self.json_date); + self.current_node = gen_source(media, &self.playout_stat); self.nodes.lock().unwrap().push(self.current_node.clone()); *self.index.lock().unwrap() = self.nodes.lock().unwrap().len(); } @@ -285,7 +302,7 @@ impl Iterator for CurrentProgram { self.nodes.lock().unwrap()[index].clone(), &self.config, is_last, - &self.json_date, + &self.playout_stat, ); self.last_next_ad(); *self.index.lock().unwrap() += 1; @@ -300,7 +317,7 @@ impl Iterator for CurrentProgram { self.check_for_next_playlist(); let (_, total_delta) = get_delta( &self.config.playlist.start_sec.unwrap(), - &self.json_date, + &self.playout_stat, true, ); @@ -319,12 +336,12 @@ impl Iterator for CurrentProgram { } self.current_node.duration = duration; self.current_node.out = duration; - self.current_node = gen_source(self.current_node.clone(), &self.json_date); + self.current_node = gen_source(self.current_node.clone(), &self.playout_stat); self.nodes.lock().unwrap().push(self.current_node.clone()); self.last_next_ad(); self.current_node.last_ad = last_ad; - self.current_node.add_filter(&self.json_date); + self.current_node.add_filter(&self.playout_stat); *self.index.lock().unwrap() += 1; @@ -332,7 +349,8 @@ impl Iterator for CurrentProgram { } *self.index.lock().unwrap() = 0; - self.current_node = gen_source(self.nodes.lock().unwrap()[0].clone(), &self.json_date); + self.current_node = + gen_source(self.nodes.lock().unwrap()[0].clone(), &self.playout_stat); self.last_next_ad(); self.current_node.last_ad = last_ad; @@ -343,12 +361,17 @@ impl Iterator for CurrentProgram { } } -fn timed_source(node: Media, config: &GlobalConfig, last: bool, json_date: &String) -> Media { +fn timed_source( + node: Media, + config: &GlobalConfig, + last: bool, + playout_stat: &PlayoutStatus, +) -> Media { // prepare input clip // check begin and length from clip // return clip only if we are in 24 hours time range - let (delta, total_delta) = get_delta(&node.begin.unwrap(), &json_date, true); + let (delta, total_delta) = get_delta(&node.begin.unwrap(), &playout_stat, true); let mut new_node = node.clone(); new_node.process = Some(false); @@ -369,7 +392,7 @@ fn timed_source(node: Media, config: &GlobalConfig, last: bool, json_date: &Stri || !config.playlist.length.contains(":") { // when we are in the 24 hour range, get the clip - new_node = gen_source(node, &json_date); + new_node = gen_source(node, &playout_stat); new_node.process = Some(true); } else if total_delta <= 0.0 { info!("Begin is over play time, skip: {}", node.source); @@ -380,7 +403,7 @@ fn timed_source(node: Media, config: &GlobalConfig, last: bool, json_date: &Stri new_node } -fn gen_source(mut node: Media, json_date: &String) -> Media { +fn gen_source(mut node: Media, playout_stat: &PlayoutStatus) -> Media { if Path::new(&node.source).is_file() { node.add_probe(); node.cmd = Some(seek_and_length( @@ -389,7 +412,7 @@ fn gen_source(mut node: Media, json_date: &String) -> Media { node.out, node.duration, )); - node.add_filter(&json_date); + node.add_filter(&playout_stat); } else { if node.source.chars().count() == 0 { warn!( @@ -402,17 +425,17 @@ fn gen_source(mut node: Media, json_date: &String) -> Media { let (source, cmd) = gen_dummy(node.out - node.seek); node.source = source; node.cmd = Some(cmd); - node.add_filter(&json_date); + node.add_filter(&playout_stat); } node } -fn handle_list_init(mut node: Media, json_date: &String) -> Media { +fn handle_list_init(mut node: Media, playout_stat: &PlayoutStatus) -> Media { // handle init clip, but this clip can be the last one in playlist, // this we have to figure out and calculate the right length - let (_, total_delta) = get_delta(&node.begin.unwrap(), &json_date, true); + let (_, total_delta) = get_delta(&node.begin.unwrap(), &playout_stat, true); let mut out = node.out; if node.out - node.seek > total_delta { @@ -421,7 +444,7 @@ fn handle_list_init(mut node: Media, json_date: &String) -> Media { node.out = out; - let new_node = gen_source(node, &json_date); + let new_node = gen_source(node, &playout_stat); new_node } diff --git a/src/main.rs b/src/main.rs index 938fb9de..0676bdc7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,13 @@ +use std::{ + path::PathBuf, + {fs, fs::File}, +}; + extern crate log; extern crate simplelog; +use serde::{Deserialize, Serialize}; +use serde_json::json; use simplelog::*; use tokio::runtime::Builder; @@ -11,34 +18,65 @@ mod utils; use crate::output::{player, write_hls}; use crate::utils::{ - init_config, init_logging, init_status, run_rpc, validate_ffmpeg, GlobalConfig, PlayerControl, + init_config, init_logging, run_rpc, validate_ffmpeg, GlobalConfig, PlayerControl, PlayoutStatus, ProcessControl, }; +#[derive(Serialize, Deserialize)] +struct StatusData { + time_shift: f64, + date: String, +} + fn main() { init_config(); let config = GlobalConfig::global(); let play_control = PlayerControl::new(); - let _ = PlayoutStatus::new(); + let playout_stat = PlayoutStatus::new(); let proc_control = ProcessControl::new(); + if !PathBuf::from(config.general.stat_file.clone()).exists() { + let data = json!({ + "time_shift": 0.0, + "date": "".to_string(), + }); + + 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"); + } else { + let stat_file = File::options() + .read(true) + .write(false) + .open(&config.general.stat_file) + .expect("Could not open status file"); + + let data: StatusData = + serde_json::from_reader(stat_file).expect("Could not read status file."); + + *playout_stat.time_shift.lock().unwrap() = data.time_shift; + *playout_stat.date.lock().unwrap() = data.date; + } + let runtime = Builder::new_multi_thread().enable_all().build().unwrap(); let rt_handle = runtime.handle(); let logging = init_logging(rt_handle.clone(), proc_control.is_terminated.clone()); CombinedLogger::init(logging).unwrap(); - init_status(); validate_ffmpeg(); if config.rpc_server.enable { - rt_handle.spawn(run_rpc(play_control.clone(), proc_control.clone())); + rt_handle.spawn(run_rpc( + play_control.clone(), + playout_stat.clone(), + proc_control.clone(), + )); } if config.out.mode.to_lowercase() == "hls".to_string() { - write_hls(rt_handle, play_control, proc_control); + write_hls(rt_handle, play_control, playout_stat, proc_control); } else { - player(rt_handle, play_control, proc_control); + player(rt_handle, play_control, playout_stat, proc_control); } info!("Playout done..."); diff --git a/src/output/hls.rs b/src/output/hls.rs index 515ff393..6156158e 100644 --- a/src/output/hls.rs +++ b/src/output/hls.rs @@ -24,12 +24,13 @@ use tokio::runtime::Handle; use crate::output::source_generator; use crate::utils::{ - sec_to_time, stderr_reader, GlobalConfig, PlayerControl, ProcessControl, + sec_to_time, stderr_reader, GlobalConfig, PlayerControl, PlayoutStatus, ProcessControl, }; pub fn write_hls( rt_handle: &Handle, play_control: PlayerControl, + playout_stat: PlayoutStatus, proc_control: ProcessControl, ) { let config = GlobalConfig::global(); @@ -41,6 +42,7 @@ pub fn write_hls( config.clone(), play_control.current_list.clone(), play_control.index.clone(), + playout_stat, proc_control.is_terminated.clone(), ); diff --git a/src/output/mod.rs b/src/output/mod.rs index a774c989..b98a46cd 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -24,7 +24,7 @@ pub use hls::write_hls; use crate::input::{file_worker, ingest_server, CurrentProgram, Source}; use crate::utils::{ - sec_to_time, stderr_reader, GlobalConfig, Media, PlayerControl, ProcessControl, + sec_to_time, stderr_reader, GlobalConfig, Media, PlayerControl, PlayoutStatus, ProcessControl, }; pub fn source_generator( @@ -32,6 +32,7 @@ pub fn source_generator( config: GlobalConfig, current_list: Arc>>, index: Arc>, + playout_stat: PlayoutStatus, is_terminated: Arc>, ) -> (Box>, Arc>) { let mut init_playlist: Arc> = Arc::new(Mutex::new(false)); @@ -46,7 +47,7 @@ pub fn source_generator( info!("Playout in folder mode."); - let folder_source = Source::new(current_list, index); + let folder_source = Source::new(current_list, index, playout_stat); let (sender, receiver) = channel(); let mut watchman = watcher(sender, Duration::from_secs(2)).unwrap(); @@ -64,6 +65,7 @@ pub fn source_generator( info!("Playout in playlist mode"); let program = CurrentProgram::new( rt_handle.clone(), + playout_stat, is_terminated.clone(), current_list, index, @@ -84,6 +86,7 @@ pub fn source_generator( pub fn player( rt_handle: &Handle, play_control: PlayerControl, + playout_stat: PlayoutStatus, proc_control: ProcessControl, ) { let config = GlobalConfig::global(); @@ -99,6 +102,7 @@ pub fn player( config.clone(), play_control.current_list.clone(), play_control.index.clone(), + playout_stat, proc_control.is_terminated.clone(), ); diff --git a/src/utils/config.rs b/src/utils/config.rs index 0ef61529..af56df6e 100644 --- a/src/utils/config.rs +++ b/src/utils/config.rs @@ -29,6 +29,9 @@ pub struct GlobalConfig { #[derive(Debug, Serialize, Deserialize, Clone)] pub struct General { pub stop_threshold: f64, + + #[serde(skip_serializing, skip_deserializing)] + pub stat_file: String, } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -146,7 +149,8 @@ impl GlobalConfig { Err(err) => { println!( "{:?} doesn't exists!\n{}\n\nSystem error: {err}", - config_path, "Put \"ffplayout.yml\" in \"/etc/playout/\" or beside the executable!" + config_path, + "Put \"ffplayout.yml\" in \"/etc/playout/\" or beside the executable!" ); process::exit(0x0100); } @@ -154,6 +158,10 @@ impl GlobalConfig { let mut config: GlobalConfig = serde_yaml::from_reader(f).expect("Could not read config file."); + config.general.stat_file = env::temp_dir() + .join("ffplayout_status.json") + .display() + .to_string(); let fps = config.processing.fps.to_string(); let bitrate = config.processing.width * config.processing.height / 10; config.playlist.start_sec = Some(time_to_sec(&config.playlist.day_start)); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 410d2a7f..d8e6e4f0 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -3,9 +3,7 @@ use chrono::Duration; use ffprobe::{ffprobe, Format, Stream}; use serde::{Deserialize, Serialize}; use std::{ - env::temp_dir, - fs, - fs::{metadata, File}, + fs::metadata, io::{BufRead, BufReader, Error}, path::Path, process::exit, @@ -14,7 +12,6 @@ use std::{ time, time::UNIX_EPOCH, }; -use once_cell::sync::OnceCell; use jsonrpc_http_server::CloseHandle; use process_control::Terminator; @@ -106,59 +103,21 @@ impl Drop for ProcessControl { } } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug)] pub struct PlayoutStatus { - pub time_shift: f64, - pub date: String, + pub time_shift: Arc>, + pub date: Arc>, + pub current_date: Arc>, } impl PlayoutStatus { pub fn new() -> Self { - let stat_file = temp_dir().join("ffplayout_status.json"); - - let mut data: PlayoutStatus = Self { - time_shift: 0.0, - date: "".to_string(), - }; - - if !stat_file.exists() { - - } else { - let file = File::options() - .read(true) - .write(false) - .open(&stat_file.display().to_string()) - .expect("Could not open status file"); - - data = serde_json::from_reader(file).expect("Could not read status file."); + Self { + time_shift: Arc::new(Mutex::new(0.0)), + date: Arc::new(Mutex::new(String::new())), + current_date: Arc::new(Mutex::new(String::new())), } - - data } - - pub fn write(mut self, date: String, time_shift: f64) { - let stat_file = temp_dir().join("ffplayout_status.json"); - - self.date = date.clone(); - self.time_shift = time_shift.clone(); - - if let Ok (json) = serde_json::to_string(&self) { - if let Err(e) = fs::write(stat_file, &json) { - error!("Unable to write status file: {e}") - }; - }; - } - - pub fn global() -> &'static PlayoutStatus { - STATUS_CELL.get().expect("Config is not initialized") - } -} - -static STATUS_CELL: OnceCell = OnceCell::new(); - -pub fn init_status() { - let status = PlayoutStatus::new(); - STATUS_CELL.set(status).unwrap(); } #[derive(Clone)] @@ -242,9 +201,9 @@ impl Media { } } - pub fn add_filter(&mut self, json_date: &String) { + pub fn add_filter(&mut self, playout_stat: &PlayoutStatus) { let mut node = self.clone(); - self.filter = Some(filter_chains(&mut node, &json_date)) + self.filter = Some(filter_chains(&mut node, &playout_stat)) } } @@ -378,10 +337,8 @@ pub fn is_close(a: f64, b: f64, to: f64) -> bool { false } -pub fn get_delta(begin: &f64, json_date: &String, shift: bool) -> (f64, f64) { +pub fn get_delta(begin: &f64, playout_stat: &PlayoutStatus, shift: bool) -> (f64, f64) { let config = GlobalConfig::global(); - let status = PlayoutStatus::global(); - let mut current_time = get_sec(); let start = config.playlist.start_sec.unwrap(); let length = time_to_sec(&config.playlist.length); @@ -409,9 +366,15 @@ pub fn get_delta(begin: &f64, json_date: &String, shift: bool) -> (f64, f64) { total_delta = target_length + start - current_time; } - if shift && json_date == &status.date && status.time_shift != 0.0 { - current_delta -= status.time_shift; - total_delta -= status.time_shift; + println!("current_delta: {current_delta}"); + println!("shift: {}", *playout_stat.time_shift.lock().unwrap()); + + if shift + && *playout_stat.current_date.lock().unwrap() == *playout_stat.date.lock().unwrap() + && *playout_stat.time_shift.lock().unwrap() != 0.0 + { + current_delta -= *playout_stat.time_shift.lock().unwrap(); + total_delta -= *playout_stat.time_shift.lock().unwrap(); } (current_delta, total_delta) diff --git a/src/utils/rpc_server.rs b/src/utils/rpc_server.rs index b4e57581..3af9eef4 100644 --- a/src/utils/rpc_server.rs +++ b/src/utils/rpc_server.rs @@ -1,3 +1,4 @@ +use std::fs; use serde_json::{json, Map}; use jsonrpc_http_server::jsonrpc_core::{IoHandler, Params, Value}; @@ -7,7 +8,8 @@ use jsonrpc_http_server::{ use simplelog::*; use crate::utils::{ - get_delta, get_sec, sec_to_time, GlobalConfig, Media, PlayerControl, PlayoutStatus, ProcessControl, + get_delta, get_sec, sec_to_time, GlobalConfig, Media, PlayerControl, PlayoutStatus, + ProcessControl, }; fn get_media_map(media: Media) -> Value { @@ -42,13 +44,20 @@ fn get_data_map(config: &GlobalConfig, media: Media) -> Map { data_map } -pub async fn run_rpc(play_control: PlayerControl, proc_control: ProcessControl) { +pub async fn run_rpc( + play_control: PlayerControl, + playout_stat: PlayoutStatus, + proc_control: ProcessControl, +) { let config = GlobalConfig::global(); let mut io = IoHandler::default(); let play = play_control.clone(); + let stat = playout_stat.clone(); let proc = proc_control.clone(); io.add_sync_method("player", move |params: Params| { + let stat_file = config.general.stat_file.clone(); + match params { Params::Map(map) => { if map.contains_key("control") && map["control"] == "next".to_string() { @@ -57,7 +66,6 @@ pub async fn run_rpc(play_control: PlayerControl, proc_control: ProcessControl) if let Ok(_) = decoder.terminate() { info!("Move to next clip"); let index = *play.index.lock().unwrap(); - let status = PlayoutStatus::global(); if index < play.current_list.lock().unwrap().len() { let mut data_map = Map::new(); @@ -65,8 +73,18 @@ pub async fn run_rpc(play_control: PlayerControl, proc_control: ProcessControl) play.current_list.lock().unwrap()[index].clone(); media.add_probe(); - let (delta, _) = get_delta(&media.begin.unwrap_or(0.0), &status.date, false); - status.clone().write(status.date.clone(), delta); + let (delta, _) = + get_delta(&media.begin.unwrap_or(0.0), &stat, false); + + let data = json!({ + "time_shift": delta, + "date": *stat.current_date.lock().unwrap(), + }); + + let status_data: String = serde_json::to_string(&data) + .expect("Serialize status data failed"); + fs::write(stat_file, &status_data) + .expect("Unable to write file"); data_map.insert( "operation".to_string(),