From f576dedcb9ebac259ec2283a622cd521a2f614b8 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Sun, 13 Nov 2022 17:34:01 +0100 Subject: [PATCH] get program infos --- Cargo.lock | 7 +- README.md | 2 +- ffplayout-api/Cargo.toml | 3 +- ffplayout-api/src/api/routes.rs | 119 ++++++++++++++++++++++++++++++-- ffplayout-api/src/main.rs | 7 +- ffplayout-api/src/utils/mod.rs | 32 ++++++++- ffplayout-engine/Cargo.toml | 2 +- lib/Cargo.toml | 2 +- lib/src/utils/generator.rs | 35 +--------- lib/src/utils/mod.rs | 30 ++++++++ 10 files changed, 190 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d14645cb..f3a082c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -962,7 +962,7 @@ dependencies = [ [[package]] name = "ffplayout" -version = "0.16.3" +version = "0.16.4" dependencies = [ "chrono", "clap", @@ -982,7 +982,7 @@ dependencies = [ [[package]] name = "ffplayout-api" -version = "0.7.1" +version = "0.8.0" dependencies = [ "actix-files", "actix-multipart", @@ -999,6 +999,7 @@ dependencies = [ "jsonwebtoken", "once_cell", "rand", + "regex", "relative-path", "reqwest", "rpassword", @@ -1012,7 +1013,7 @@ dependencies = [ [[package]] name = "ffplayout-lib" -version = "0.16.3" +version = "0.16.4" dependencies = [ "chrono", "crossbeam-channel", diff --git a/README.md b/README.md index f5ddeab1..001e24eb 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Check the [releases](https://github.com/ffplayout/ffplayout/releases/latest) for - send emails with error message - overlay a logo - overlay text, controllable through [ffplayout-frontend](https://github.com/ffplayout/ffplayout-frontend) (needs ffmpeg with libzmq and enabled JSON RPC server) -- EBU R128 loudness normalization (single pass) +- EBU R128 loudness normalization (single pass) (experimental *) - loop playlist infinitely - [remote source](/docs/remote_source.md) - trim and fade the last clip, to get full 24 hours diff --git a/ffplayout-api/Cargo.toml b/ffplayout-api/Cargo.toml index 2b1a11f4..47bc87bc 100644 --- a/ffplayout-api/Cargo.toml +++ b/ffplayout-api/Cargo.toml @@ -4,7 +4,7 @@ description = "Rest API for ffplayout" license = "GPL-3.0" authors = ["Jonathan Baecker jonbae77@gmail.com"] readme = "README.md" -version = "0.7.1" +version = "0.8.0" edition = "2021" [dependencies] @@ -23,6 +23,7 @@ futures-util = { version = "0.3", default-features = false, features = ["std"] } jsonwebtoken = "8" once_cell = "1.10" rand = "0.8" +regex = "1" relative-path = "1.6" reqwest = { version = "0.11", features = ["blocking", "json"] } rpassword = "6.0" diff --git a/ffplayout-api/src/api/routes.rs b/ffplayout-api/src/api/routes.rs index 93d3fc24..56bf923f 100644 --- a/ffplayout-api/src/api/routes.rs +++ b/ffplayout-api/src/api/routes.rs @@ -17,6 +17,8 @@ use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, SaltString}, Argon2, PasswordHasher, PasswordVerifier, }; +use chrono::{DateTime, Duration, Local, NaiveDateTime, TimeZone, Utc}; +use regex::Regex; use serde::{Deserialize, Serialize}; use simplelog::*; @@ -33,10 +35,16 @@ use crate::utils::{ browser, create_directory, remove_file_or_folder, rename_file, upload, MoveObject, PathObject, }, + naive_date_time_from_str, playlist::{delete_playlist, generate_playlist, read_playlist, write_playlist}, playout_config, read_log_file, read_playout_config, Role, }; -use ffplayout_lib::utils::{import::import_file, JsonPlaylist, PlayoutConfig}; +use ffplayout_lib::{ + utils::{ + get_date_range, import::import_file, sec_to_time, time_to_sec, JsonPlaylist, PlayoutConfig, + }, + vec_strings, +}; #[derive(Serialize)] struct ResponseObj { @@ -71,6 +79,24 @@ pub struct ImportObj { date: String, } +#[derive(Debug, Deserialize)] +pub struct ProgramObj { + #[serde(deserialize_with = "naive_date_time_from_str")] + start_after: Option, + #[serde(deserialize_with = "naive_date_time_from_str")] + start_before: Option, +} + +#[derive(Debug, Serialize)] +struct ProgramTtem { + source: String, + start: String, + r#in: f64, + out: f64, + duration: f64, + category: String, +} + /// #### User Handling /// /// **Login** @@ -676,7 +702,7 @@ pub async fn del_playlist( /// **Read Log Life** /// /// ```BASH -/// curl -X Get http://127.0.0.1:8787/api/log/1 +/// curl -X GET http://127.0.0.1:8787/api/log/1 /// -H 'Content-Type: application/json' -H 'Authorization: Bearer ' /// ``` #[get("/log/{id}")] @@ -762,7 +788,7 @@ pub async fn remove( /// **Upload File** /// /// ```BASH -/// curl -X POST http://127.0.0.1:8787/api/file/1/upload/ -H 'Authorization: Bearer ' +/// curl -X PUT http://127.0.0.1:8787/api/file/1/upload/ -H 'Authorization: Bearer ' /// -F "file=@file.mp4" /// ``` #[put("/file/{id}/upload/")] @@ -781,7 +807,7 @@ async fn save_file( /// lines with leading "#" will be ignore /// /// ```BASH -/// curl -X POST http://127.0.0.1:8787/api/file/1/import/ -H 'Authorization: Bearer ' +/// curl -X PUT http://127.0.0.1:8787/api/file/1/import/ -H 'Authorization: Bearer ' /// -F "file=@list.m3u" /// ``` #[put("/file/{id}/import/")] @@ -803,3 +829,88 @@ async fn import_playlist( Ok(HttpResponse::Ok().into()) } + +/// **Program info** +/// +/// Get infos about give program +/// +/// ```BASH +/// curl -X GET http://127.0.0.1:8787/program/1/?start_after=2022-11-13T12:00:00&start_before=2022-11-20T11:59:59 \ +/// -H 'Authorization: Bearer ' +/// ``` +#[get("/program/{id}/")] +#[has_any_role("Role::Admin", "Role::User", type = "Role")] +async fn get_program( + id: web::Path, + obj: web::Query, +) -> Result { + let (config, _) = playout_config(&*id).await?; + let start_sec = config.playlist.start_sec.unwrap(); + let mut days = 0; + let mut program = vec![]; + + let after = match obj.start_after { + Some(d) => d, + None => Utc::now().naive_utc(), + }; + + let before = match obj.start_before { + Some(d) => d, + None => Utc::now().naive_utc(), + }; + + if start_sec > time_to_sec(&after.format("%H:%M:%S").to_string()) { + days = 1; + } + + let date_range = get_date_range(&vec_strings![ + (after - Duration::days(days)).format("%Y-%m-%d"), + "-", + before.format("%Y-%m-%d") + ]); + + for date in date_range { + let mut naive = NaiveDateTime::parse_from_str( + &format!("{date} {}", sec_to_time(start_sec)), + "%Y-%m-%d %H:%M:%S%.3f", + ) + .unwrap(); + + let playlist = match read_playlist(*id, date.clone()).await { + Ok(p) => p, + Err(e) => { + error!("Error in PLaylist from {date}: {e}"); + continue; + } + }; + + for item in playlist.program { + let start: DateTime = Local.from_local_datetime(&naive).unwrap(); + + let source = match Regex::new(&config.text.regex) + .ok() + .and_then(|r| r.captures(&item.source)) + { + Some(t) => t[1].to_string(), + None => item.source, + }; + + let p_item = ProgramTtem { + source, + start: start.format("%Y-%m-%d %H:%M:%S%.3f%:z").to_string(), + r#in: item.seek, + out: item.out, + duration: item.duration, + category: item.category, + }; + + if naive >= after && naive <= before { + program.push(p_item); + } + + naive += Duration::milliseconds(((item.out - item.seek) * 1000.0) as i64); + } + } + + Ok(web::Json(program)) +} diff --git a/ffplayout-api/src/main.rs b/ffplayout-api/src/main.rs index 349d1394..ead42261 100644 --- a/ffplayout-api/src/main.rs +++ b/ffplayout-api/src/main.rs @@ -18,8 +18,8 @@ use api::{ routes::{ add_channel, add_dir, add_preset, add_user, control_playout, del_playlist, delete_preset, file_browser, gen_playlist, get_all_channels, get_channel, get_log, get_playlist, - get_playout_config, get_presets, get_user, import_playlist, login, media_current, - media_last, media_next, move_rename, patch_channel, process_control, remove, + get_playout_config, get_presets, get_program, get_user, import_playlist, login, + media_current, media_last, media_next, move_rename, patch_channel, process_control, remove, remove_channel, save_file, save_playlist, send_text_message, update_playout_config, update_preset, update_user, }, @@ -121,7 +121,8 @@ async fn main() -> std::io::Result<()> { .service(move_rename) .service(remove) .service(save_file) - .service(import_playlist), + .service(import_playlist) + .service(get_program), ) .service(Files::new("/", public_path()).index_file("index.html")) }) diff --git a/ffplayout-api/src/utils/mod.rs b/ffplayout-api/src/utils/mod.rs index df98c697..4580a5ed 100644 --- a/ffplayout-api/src/utils/mod.rs +++ b/ffplayout-api/src/utils/mod.rs @@ -5,10 +5,11 @@ use std::{ path::Path, }; -use chrono::prelude::*; +use chrono::{format::ParseErrorKind, prelude::*}; use faccess::PathExt; use once_cell::sync::OnceCell; use rpassword::read_password; +use serde::{de, Deserialize, Deserializer}; use simplelog::*; pub mod args_parse; @@ -23,7 +24,7 @@ use crate::db::{ models::{Channel, User}, }; use crate::utils::{args_parse::Args, errors::ServiceError}; -use ffplayout_lib::utils::PlayoutConfig; +use ffplayout_lib::utils::{time_to_sec, PlayoutConfig}; #[derive(Clone, Eq, PartialEq)] pub enum Role { @@ -175,7 +176,9 @@ pub async fn run_args(mut args: Args) -> Result<(), i32> { pub fn read_playout_config(path: &str) -> Result> { let file = File::open(path)?; - let config: PlayoutConfig = serde_yaml::from_reader(file)?; + let mut config: PlayoutConfig = serde_yaml::from_reader(file)?; + + config.playlist.start_sec = Some(time_to_sec(&config.playlist.day_start)); Ok(config) } @@ -234,3 +237,26 @@ pub fn local_utc_offset() -> i32 { utc_offset } + +pub fn naive_date_time_from_str<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let s: String = Deserialize::deserialize(deserializer)?; + match NaiveDateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M:%S") { + Ok(date_time) => Ok(Some(date_time)), + Err(e) => { + if e.kind() == ParseErrorKind::TooShort { + match NaiveDateTime::parse_from_str(&format!("{s}T00:00:00"), "%Y-%m-%dT%H:%M:%S") { + Ok(date_time) => Ok(Some(date_time)), + Err(e) => Err(de::Error::custom(e)), + } + } else { + match NaiveDateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M:%S%#z") { + Ok(date_time) => Ok(Some(date_time)), + Err(_) => Err(de::Error::custom(e)), + } + } + } + } +} diff --git a/ffplayout-engine/Cargo.toml b/ffplayout-engine/Cargo.toml index ed774bba..83706838 100644 --- a/ffplayout-engine/Cargo.toml +++ b/ffplayout-engine/Cargo.toml @@ -4,7 +4,7 @@ description = "24/7 playout based on rust and ffmpeg" license = "GPL-3.0" authors = ["Jonathan Baecker jonbae77@gmail.com"] readme = "README.md" -version = "0.16.3" +version = "0.16.4" edition = "2021" default-run = "ffplayout" diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 4c02bcb2..3a04283b 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -4,7 +4,7 @@ description = "Library for ffplayout" license = "GPL-3.0" authors = ["Jonathan Baecker jonbae77@gmail.com"] readme = "README.md" -version = "0.16.3" +version = "0.16.4" edition = "2021" [dependencies] diff --git a/lib/src/utils/generator.rs b/lib/src/utils/generator.rs index 7709b365..a38a1270 100644 --- a/lib/src/utils/generator.rs +++ b/lib/src/utils/generator.rs @@ -14,41 +14,12 @@ use std::{ sync::{atomic::AtomicUsize, Arc, Mutex}, }; -use chrono::{Duration, NaiveDate}; use simplelog::*; use super::folder::FolderSource; -use crate::utils::{json_serializer::JsonPlaylist, time_to_sec, Media, PlayoutConfig}; - -/// Generate a vector with dates, from given range. -fn get_date_range(date_range: &[String]) -> Vec { - let mut range = vec![]; - - let start = match NaiveDate::parse_from_str(&date_range[0], "%Y-%m-%d") { - Ok(s) => s, - Err(_) => { - error!("date format error in: {:?}", date_range[0]); - exit(1); - } - }; - - let end = match NaiveDate::parse_from_str(&date_range[2], "%Y-%m-%d") { - Ok(e) => e, - Err(_) => { - error!("date format error in: {:?}", 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 -} +use crate::utils::{ + get_date_range, json_serializer::JsonPlaylist, time_to_sec, Media, PlayoutConfig, +}; /// Generate playlists pub fn generate_playlist( diff --git a/lib/src/utils/mod.rs b/lib/src/utils/mod.rs index edd2559a..4161b8f6 100644 --- a/lib/src/utils/mod.rs +++ b/lib/src/utils/mod.rs @@ -797,6 +797,36 @@ pub fn test_tcp_port(url: &str) -> bool { false } +/// Generate a vector with dates, from given range. +pub fn get_date_range(date_range: &[String]) -> Vec { + let mut range = vec![]; + + let start = match NaiveDate::parse_from_str(&date_range[0], "%Y-%m-%d") { + Ok(s) => s, + Err(_) => { + error!("date format error in: {:?}", date_range[0]); + exit(1); + } + }; + + let end = match NaiveDate::parse_from_str(&date_range[2], "%Y-%m-%d") { + Ok(e) => e, + Err(_) => { + error!("date format error in: {:?}", 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 home_dir() -> Option { home_dir_inner() }