diff --git a/Cargo.lock b/Cargo.lock index dca81cb7..1b296cd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1090,7 +1090,6 @@ dependencies = [ "ffplayout-lib", "ffprobe", "flexi_logger", - "futures", "futures-util", "home", "itertools 0.13.0", diff --git a/assets/11-ffplayout b/assets/11-ffplayout deleted file mode 100644 index 440b44f9..00000000 --- a/assets/11-ffplayout +++ /dev/null @@ -1,5 +0,0 @@ -# give user ffpu permission to control the ffplayout systemd service - -ffpu ALL = NOPASSWD: /usr/bin/systemctl start ffplayout.service, /usr/bin/systemctl stop ffplayout.service, /usr/bin/systemctl restart ffplayout.service, /usr/bin/systemctl status ffplayout.service, /usr/bin/systemctl is-active ffplayout.service, /usr/bin/systemctl enable ffplayout.service, /usr/bin/systemctl disable ffplayout.service - -ffpu ALL = NOPASSWD: /usr/bin/systemctl start ffplayout@*, /usr/bin/systemctl stop ffplayout@*, /usr/bin/systemctl restart ffplayout@*, /usr/bin/systemctl status ffplayout@*, /usr/bin/systemctl is-active ffplayout@*, /usr/bin/systemctl enable ffplayout@*, /usr/bin/systemctl disable ffplayout@* diff --git a/assets/advanced.toml b/assets/advanced.toml deleted file mode 100644 index 24152f40..00000000 --- a/assets/advanced.toml +++ /dev/null @@ -1,37 +0,0 @@ -# Changing these settings is for advanced users only! -# There will be no support or guarantee that it will be stable after changing them. - -[decoder] -input_param = "" -# output_param get also applied to ingest instance. -output_param = "" - -[filters] -deinterlace = "" # yadif=0:-1:0 -pad_scale_w = "" # scale={}:-1 -pad_scale_h = "" # scale=-1:{} -pad_video = "" # pad=max(iw\\,ih*({0}/{1})):ow/({0}/{1}):(ow-iw)/2:(oh-ih)/2 -fps = "" # fps={} -scale = "" # scale={}:{} -set_dar = "" # setdar=dar={} -fade_in = "" # fade=in:st=0:d=0.5 -fade_out = "" # fade=out:st={}:d=1.0 -overlay_logo_scale = "" # scale={} -overlay_logo_fade_in = "" # fade=in:st=0:d=1.0:alpha=1 -overlay_logo_fade_out = "" # fade=out:st={}:d=1.0:alpha=1 -overlay_logo = "" # null[l];[v][l]overlay={}:shortest=1 -tpad = "" # tpad=stop_mode=add:stop_duration={} -drawtext_from_file = "" # drawtext=text='{}':{}{} -drawtext_from_zmq = "" # zmq=b=tcp\\\\://'{}',drawtext@dyntext={} -aevalsrc = "" # aevalsrc=0:channel_layout=stereo:duration={}:sample_rate=48000 -afade_in = "" # afade=in:st=0:d=0.5 -afade_out = "" # afade=out:st={}:d=1.0 -apad = "" # apad=whole_dur={} -volume = "" # volume={} -split = "" # split={}{} - -[encoder] -input_param = "" - -[ingest] -input_param = "" diff --git a/assets/ffpapi.service b/assets/ffpapi.service deleted file mode 100644 index ddf8218c..00000000 --- a/assets/ffpapi.service +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -Description=Rest API for ffplayout -After=network.target remote-fs.target - -[Service] -ExecStart=/usr/bin/ffpapi -l 0.0.0.0:8787 -Restart=always -RestartSec=1 -User=ffpu - -[Install] -WantedBy=multi-user.target diff --git a/assets/ffplayout.toml b/assets/ffplayout.toml deleted file mode 100644 index ed3c5072..00000000 --- a/assets/ffplayout.toml +++ /dev/null @@ -1,168 +0,0 @@ -[general] -help_text = """Sometimes it can happen, that a file is corrupt but still playable, \ - this can produce an streaming error over all following files. The only way \ - in this case is, to stop ffplayout and start it again. Here we only say when \ - it stops, the starting process is in your hand. Best way is a systemd service \ - on linux. - 'stop_threshold' stop ffplayout, if it is async in time above this \ - value. A number below 3 can cause unexpected errors.""" -stop_threshold = 11 -stat_file = ".ffp_status" - -[rpc_server] -help_text = """Run a JSON RPC server, for getting infos about current playing and for some \ - control functions.""" -enable = true -address = "127.0.0.1:7070" -authorization = "av2Kx8g67lF9qj5wEH3ym1bI4cCs" - -[mail] -help_text = """Send error messages to email address, like missing playlist; invalid \ - json format; missing clip path. Leave recipient blank, if you don't need this. - 'mail_level' can be INFO, WARNING or ERROR. - 'interval' means seconds until a new mail will be sended, value must be in increments of 10.""" -subject = "Playout Error" -smtp_server = "mail.example.org" -starttls = true -sender_addr = "ffplayout@example.org" -sender_pass = "abc123" -recipient = "" -mail_level = "ERROR" -interval = 120 - -[logging] -help_text = """If 'log_to_file' is true, log to file, when is false log to console. - 'backup_count' says how long log files will be saved in days. - 'local_time' to false will set log timestamps to UTC. Path to /var/log/ only \ - if you run this program as daemon. - 'level' can be DEBUG, INFO, WARNING, ERROR. - 'ffmpeg_level/ingest_level' can be INFO, WARNING, ERROR. - 'detect_silence' logs an error message if the audio line is silent for 15 \ - seconds during the validation process. - 'ignore_lines' makes logging to ignore strings that contains matched lines, \ - in frontend is a semicolon separated list.""" -log_to_file = true -backup_count = 7 -local_time = true -timestamp = true -path = "/var/log/ffplayout/" -level = "DEBUG" -ffmpeg_level = "ERROR" -ingest_level = "WARNING" -detect_silence = false -ignore_lines = [ - "P sub_mb_type 4 out of range at", - "error while decoding MB", - "negative number of zero coeffs at", - "out of range intra chroma pred mode", - "non-existing SPS 0 referenced in buffering period", -] - -[processing] -help_text = """Default processing for all clips, to have them unique. Mode can be playlist \ - or folder. - 'aspect' must be a float number.'logo' is only used if the path exist. - 'logo_scale' scale the logo to target size, leave it blank when no scaling \ - is needed, format is 'width:height', for example '100:-1' for proportional \ - scaling. With 'logo_opacity' logo can become transparent. - With 'audio_tracks' it is possible to configure how many audio tracks should \ - be processed. 'audio_channels' can be use, if audio has more channels then only stereo. - With 'logo_position' in format 'x:y' you set the logo position. - With 'custom_filter' it is possible, to apply further filters. The filter \ - outputs should end with [c_v_out] for video filter, and [c_a_out] for audio filter.""" -mode = "playlist" -audio_only = false -copy_audio = false -copy_video = false -width = 1024 -height = 576 -aspect = 1.778 -fps = 25 -add_logo = true -logo = "/usr/share/ffplayout/logo.png" -logo_scale = "" -logo_opacity = 0.7 -logo_position = "W-w-12:12" -audio_tracks = 1 -audio_track_index = -1 -audio_channels = 2 -volume = 1 -custom_filter = "" - -[ingest] -help_text = """Run a server for a ingest stream. This stream will override the normal streaming \ - until is done. There is only a very simple authentication mechanism, which check if the \ - stream name is correct. - 'custom_filter' can be used in the same way then the one in the process section.""" -enable = false -input_param = "-f live_flv -listen 1 -i rtmp://127.0.0.1:1936/live/stream" -custom_filter = "" - -[playlist] -help_text = """'path' can be a path to a single file, or a directory. For directory put \ - only the root folder, for example '/playlists', subdirectories are read by the \ - program. Subdirectories needs this structure '/playlists/2018/01'. - 'day_start' means at which time the playlist should start, leave day_start \ - blank when playlist should always start at the begin. 'length' represent the \ - target length from playlist, when is blank real length will not consider. - 'infinit: true' works with single playlist file and loops it infinitely. """ -path = "/var/lib/ffplayout/playlists" -day_start = "05:59:25" -length = "24:00:00" -infinit = false - -[storage] -help_text = """'filler' is for playing instead of a missing file or fill the end to reach 24 \ - hours, can be a file or folder, it will loop when is necessary. - 'extensions' search only files with this extension. Set 'shuffle' to 'true' \ - to pick files randomly.""" -path = "/var/lib/ffplayout/tv-media" -filler = "/var/lib/ffplayout/tv-media/filler/filler.mp4" -extensions = ["mp4", "mkv", "webm"] -shuffle = true - -[text] -help_text = """Overlay text in combination with libzmq for remote text manipulation. \ - On windows fontfile path need to be like this 'C\\:/WINDOWS/fonts/DejaVuSans.ttf'. - 'text_from_filename' activate the extraction from text of a filename. With 'style' \ - you can define the drawtext parameters like position, color, etc. Post Text over \ - API will override this. With 'regex' you can format file names, to get a title from it.""" -add_text = true -text_from_filename = false -fontfile = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" -style = "x=(w-tw)/2:y=(h-line_h)*0.9:fontsize=24:fontcolor=#ffffff:box=1:boxcolor=#000000:boxborderw=4" -regex = "^.+[/\\](.*)(.mp4|.mkv|.webm)$" - -[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 \ - 'output_param:' settings when you want to stream to a rtmp/rtsp/srt/... server. - In production don't serve hls playlist with ffpapi, use nginx or another web server!""" -mode = "hls" -output_param = """\ - -c:v libx264 - -crf 23 - -x264-params keyint=50:min-keyint=25:scenecut=-1 - -maxrate 1300k - -bufsize 2600k - -preset faster - -tune zerolatency - -profile:v Main - -level 3.1 - -c:a aac - -ar 44100 - -b:a 128k - -flags +cgop - -f hls - -hls_time 6 - -hls_list_size 600 - -hls_flags append_list+delete_segments+omit_endlist - -hls_segment_filename /usr/share/ffplayout/public/live/stream-%d.ts - /usr/share/ffplayout/public/live/stream.m3u8""" diff --git a/assets/ffplayout@.service b/assets/ffplayout@.service deleted file mode 100644 index 594f699c..00000000 --- a/assets/ffplayout@.service +++ /dev/null @@ -1,14 +0,0 @@ -[Unit] -Description=Rust and ffmpeg based multi channel playout solution -After=network.target remote-fs.target - -[Service] -ExecStart=/usr/bin/ffplayout %I -Restart=always -StartLimitInterval=20 -RestartSec=1 -KillMode=mixed -User=ffpu - -[Install] -WantedBy=multi-user.target diff --git a/ffplayout/Cargo.toml b/ffplayout/Cargo.toml index 24477a66..3b430e5a 100644 --- a/ffplayout/Cargo.toml +++ b/ffplayout/Cargo.toml @@ -30,7 +30,6 @@ faccess = "0.2" ffprobe = "0.4" flexi_logger = { version = "0.28", features = ["kv", "colors"] } futures-util = { version = "0.3", default-features = false, features = ["std"] } -futures = "0.3" home = "0.5" itertools = "0.13" jsonwebtoken = "9" diff --git a/ffplayout/src/api/routes.rs b/ffplayout/src/api/routes.rs index 719b9a6e..6e72ece9 100644 --- a/ffplayout/src/api/routes.rs +++ b/ffplayout/src/api/routes.rs @@ -32,7 +32,7 @@ use path_clean::PathClean; use regex::Regex; use serde::{Deserialize, Serialize}; use sqlx::{Pool, Sqlite}; -use tokio::{fs, task}; +use tokio::fs; use crate::player::utils::{ get_data_map, get_date_range, import::import_file, sec_to_time, time_to_sec, JsonPlaylist, @@ -160,7 +160,7 @@ pub async fn login(pool: web::Data>, credentials: web::Json) .await .unwrap_or(Role::Guest); - task::spawn_blocking(move || { + web::block(move || { let pass = user.password.clone(); let hash = PasswordHash::new(&pass).unwrap(); user.password = "".into(); @@ -219,7 +219,7 @@ pub async fn login(pool: web::Data>, credentials: web::Json) /// -H 'Authorization: Bearer ' /// ``` #[get("/user")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] async fn get_user( pool: web::Data>, user: web::ReqData, @@ -240,7 +240,7 @@ async fn get_user( /// -H 'Authorization: Bearer ' /// ``` #[get("/user/{name}")] -#[protect("Role::Admin", ty = "Role")] +#[protect("Role::GlobalAdmin", ty = "Role")] async fn get_by_name( pool: web::Data>, name: web::Path, @@ -261,7 +261,7 @@ async fn get_by_name( /// -H 'Authorization: Bearer ' /// ``` #[get("/users")] -#[protect("Role::Admin", ty = "Role")] +#[protect("Role::GlobalAdmin", ty = "Role")] async fn get_users(pool: web::Data>) -> Result { match handles::select_users(&pool.into_inner()).await { Ok(users) => Ok(web::Json(users)), @@ -279,7 +279,7 @@ async fn get_users(pool: web::Data>) -> Result", "password": ""}' -H 'Authorization: Bearer ' /// ``` #[put("/user/{id}")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] async fn update_user( pool: web::Data>, id: web::Path, @@ -287,7 +287,7 @@ async fn update_user( data: web::Json, role: AuthDetails, ) -> Result { - if *id == user.id || role.has_authority(&Role::Admin) { + if *id == user.id || role.has_authority(&Role::GlobalAdmin) { let mut fields = String::new(); if let Some(mail) = data.mail.clone() { @@ -332,7 +332,7 @@ async fn update_user( /// -H 'Authorization: Bearer ' /// ``` #[post("/user/")] -#[protect("Role::Admin", ty = "Role")] +#[protect("Role::GlobalAdmin", ty = "Role")] async fn add_user( pool: web::Data>, data: web::Json, @@ -353,7 +353,7 @@ async fn add_user( /// -H 'Authorization: Bearer ' /// ``` #[delete("/user/{name}")] -#[protect("Role::Admin", ty = "Role")] +#[protect("Role::GlobalAdmin", ty = "Role")] async fn remove_user( pool: web::Data>, name: web::Path, @@ -389,7 +389,7 @@ async fn remove_user( /// } /// ``` #[get("/channel/{id}")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] async fn get_channel( pool: web::Data>, id: web::Path, @@ -407,7 +407,7 @@ async fn get_channel( /// curl -X GET http://127.0.0.1:8787/api/channels -H "Authorization: Bearer " /// ``` #[get("/channels")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] async fn get_all_channels(pool: web::Data>) -> Result { if let Ok(channel) = handles::select_all_channels(&pool.into_inner()).await { return Ok(web::Json(channel)); @@ -424,7 +424,7 @@ async fn get_all_channels(pool: web::Data>) -> Result" /// ``` #[patch("/channel/{id}")] -#[protect("Role::Admin", ty = "Role")] +#[protect("Role::GlobalAdmin", ty = "Role")] async fn patch_channel( pool: web::Data>, id: web::Path, @@ -448,7 +448,7 @@ async fn patch_channel( /// -H "Authorization: Bearer " /// ``` #[post("/channel/")] -#[protect("Role::Admin", ty = "Role")] +#[protect("Role::GlobalAdmin", ty = "Role")] async fn add_channel( pool: web::Data>, data: web::Json, @@ -465,7 +465,7 @@ async fn add_channel( /// curl -X DELETE http://127.0.0.1:8787/api/channel/2 -H "Authorization: Bearer " /// ``` #[delete("/channel/{id}")] -#[protect("Role::Admin", ty = "Role")] +#[protect("Role::GlobalAdmin", ty = "Role")] async fn remove_channel( pool: web::Data>, id: web::Path, @@ -487,19 +487,18 @@ async fn remove_channel( /// /// Response is a JSON object from the ffplayout.toml #[get("/playout/config/{id}")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] async fn get_playout_config( - pool: web::Data>, + _pool: web::Data>, id: web::Path, _details: AuthDetails, + controllers: web::Data>, ) -> Result { - if let Ok(_channel) = handles::select_channel(&pool.into_inner(), &id).await { - // TODO: get config + let manager = controllers.lock().unwrap().get(*id).unwrap(); + let config = manager.config.lock().unwrap().clone(); + // let config = PlayoutConfig::new(&pool.into_inner(), *id).await; - return Ok("Update playout config success."); - }; - - Err(ServiceError::InternalServerError) + Ok(web::Json(config)) } /// **Update Config** @@ -509,7 +508,7 @@ async fn get_playout_config( /// -d { } -H 'Authorization: Bearer ' /// ``` #[put("/playout/config/{id}")] -#[protect("Role::Admin", ty = "Role")] +#[protect("Role::GlobalAdmin", ty = "Role")] async fn update_playout_config( pool: web::Data>, id: web::Path, @@ -535,7 +534,7 @@ async fn update_playout_config( /// -H 'Authorization: Bearer ' /// ``` #[get("/presets/{id}")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] async fn get_presets( pool: web::Data>, id: web::Path, @@ -555,7 +554,7 @@ async fn get_presets( /// -H 'Authorization: Bearer ' /// ``` #[put("/presets/{id}")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] async fn update_preset( pool: web::Data>, id: web::Path, @@ -579,7 +578,7 @@ async fn update_preset( /// -H 'Authorization: Bearer ' /// ``` #[post("/presets/")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] async fn add_preset( pool: web::Data>, data: web::Json, @@ -601,7 +600,7 @@ async fn add_preset( /// -H 'Authorization: Bearer ' /// ``` #[delete("/presets/{id}")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] async fn delete_preset( pool: web::Data>, id: web::Path, @@ -632,7 +631,7 @@ async fn delete_preset( /// -d '{"text": "Hello from ffplayout", "x": "(w-text_w)/2", "y": "(h-text_h)/2", fontsize": "24", "line_spacing": "4", "fontcolor": "#ffffff", "box": "1", "boxcolor": "#000000", "boxborderw": "4", "alpha": "1.0"}' /// ``` #[post("/control/{id}/text/")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] pub async fn send_text_message( id: web::Path, data: web::Json, @@ -657,7 +656,7 @@ pub async fn send_text_message( /// -d '{ "command": "reset" }' -H 'Authorization: Bearer ' /// ``` #[post("/control/{id}/playout/")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] pub async fn control_playout( pool: web::Data>, id: web::Path, @@ -697,7 +696,7 @@ pub async fn control_playout( /// } /// ``` #[get("/control/{id}/media/current")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] pub async fn media_current( id: web::Path, controllers: web::Data>, @@ -722,7 +721,7 @@ pub async fn media_current( /// -d '{"command": "start"}' /// ``` #[post("/control/{id}/process/")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] pub async fn process_control( _id: web::Path, _proc: web::Json, @@ -740,7 +739,7 @@ pub async fn process_control( /// -H 'Content-Type: application/json' -H 'Authorization: Bearer ' /// ``` #[get("/playlist/{id}")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] pub async fn get_playlist( id: web::Path, obj: web::Query, @@ -763,7 +762,7 @@ pub async fn get_playlist( /// --data "{}" /// ``` #[post("/playlist/{id}/")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] pub async fn save_playlist( id: web::Path, data: web::Json, @@ -797,34 +796,39 @@ pub async fn save_playlist( /// {"start": "10:00:00", "duration": "14:00:00", "shuffle": false, "paths": ["path/3", "path/4"]}]}}' /// ``` #[post("/playlist/{id}/generate/{date}")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] pub async fn gen_playlist( params: web::Path<(i32, String)>, data: Option>, controllers: web::Data>, ) -> Result { let manager = controllers.lock().unwrap().get(params.0).unwrap(); - let channel_name = manager.channel.lock().unwrap().name.clone(); - let mut config = manager.config.lock().unwrap(); - config.general.generate = Some(vec![params.1.clone()]); + manager.config.lock().unwrap().general.generate = Some(vec![params.1.clone()]); + let storage_path = manager.config.lock().unwrap().storage.path.clone(); if let Some(obj) = data { if let Some(paths) = &obj.paths { let mut path_list = vec![]; for path in paths { - let (p, _, _) = norm_abs_path(&config.storage.path, path)?; + let (p, _, _) = norm_abs_path(&storage_path, path)?; path_list.push(p); } - config.storage.paths = path_list; + manager.config.lock().unwrap().storage.paths = path_list; } - config.general.template.clone_from(&obj.template); + manager + .config + .lock() + .unwrap() + .general + .template + .clone_from(&obj.template); } - match generate_playlist(config.clone(), channel_name).await { + match generate_playlist(manager).await { Ok(playlist) => Ok(web::Json(playlist)), Err(e) => Err(e), } @@ -837,7 +841,7 @@ pub async fn gen_playlist( /// -H 'Content-Type: application/json' -H 'Authorization: Bearer ' /// ``` #[delete("/playlist/{id}/{date}")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] pub async fn del_playlist( params: web::Path<(i32, String)>, controllers: web::Data>, @@ -860,7 +864,7 @@ pub async fn del_playlist( /// -H 'Content-Type: application/json' -H 'Authorization: Bearer ' /// ``` #[get("/log/{id}")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] pub async fn get_log( id: web::Path, log: web::Query, @@ -877,7 +881,7 @@ pub async fn get_log( /// -d '{ "source": "/" }' -H 'Authorization: Bearer ' /// ``` #[post("/file/{id}/browse/")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] pub async fn file_browser( id: web::Path, data: web::Json, @@ -900,7 +904,7 @@ pub async fn file_browser( /// -d '{"source": ""}' -H 'Authorization: Bearer ' /// ``` #[post("/file/{id}/create-folder/")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] pub async fn add_dir( id: web::Path, data: web::Json, @@ -919,7 +923,7 @@ pub async fn add_dir( /// -d '{"source": "", "target": ""}' -H 'Authorization: Bearer ' /// ``` #[post("/file/{id}/rename/")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] pub async fn move_rename( id: web::Path, data: web::Json, @@ -941,7 +945,7 @@ pub async fn move_rename( /// -d '{"source": ""}' -H 'Authorization: Bearer ' /// ``` #[post("/file/{id}/remove/")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] pub async fn remove( id: web::Path, data: web::Json, @@ -963,7 +967,7 @@ pub async fn remove( /// -F "file=@file.mp4" /// ``` #[put("/file/{id}/upload/")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] async fn save_file( id: web::Path, req: HttpRequest, @@ -1051,7 +1055,7 @@ async fn get_public(public: web::Path) -> Result, req: HttpRequest, @@ -1074,10 +1078,9 @@ async fn import_playlist( upload(&config, size, payload, &path, true).await?; - let response = task::spawn_blocking(move || { - import_file(&config, &obj.date, Some(channel_name), &path_clone) - }) - .await??; + let response = + web::block(move || import_file(&config, &obj.date, Some(channel_name), &path_clone)) + .await??; fs::remove_file(path).await?; @@ -1107,7 +1110,7 @@ async fn import_playlist( /// -H 'Authorization: Bearer ' /// ``` #[get("/program/{id}/")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] async fn get_program( id: web::Path, obj: web::Query, @@ -1195,7 +1198,7 @@ async fn get_program( /// -H 'Content-Type: application/json' -H 'Authorization: Bearer ' /// ``` #[get("/system/{id}")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] pub async fn get_system_stat( id: web::Path, controllers: web::Data>, diff --git a/ffplayout/src/db/handles.rs b/ffplayout/src/db/handles.rs index c8bd30ca..b720176a 100644 --- a/ffplayout/src/db/handles.rs +++ b/ffplayout/src/db/handles.rs @@ -18,7 +18,7 @@ pub async fn db_migrate(conn: &Pool) -> Result<&'static str, Box panic!("{e}"), } - if let Err(_) = select_global(conn).await { + if select_global(conn).await.is_err() { let secret: String = rand::thread_rng() .sample_iter(&Alphanumeric) .take(80) @@ -85,14 +85,14 @@ pub async fn update_channel( pub async fn update_stat( conn: &Pool, id: i32, - current_date: String, + last_date: String, time_shift: f64, ) -> Result { - let query = "UPDATE channels SET current_date = $2, time_shift = $3 WHERE id = $1"; + let query = "UPDATE channels SET last_date = $2, time_shift = $3 WHERE id = $1"; sqlx::query(query) .bind(id) - .bind(current_date) + .bind(last_date) .bind(time_shift) .execute(conn) .await diff --git a/ffplayout/src/db/models.rs b/ffplayout/src/db/models.rs index 6eb31c2b..464071ec 100644 --- a/ffplayout/src/db/models.rs +++ b/ffplayout/src/db/models.rs @@ -110,7 +110,7 @@ pub struct Channel { pub preview_url: String, pub extra_extensions: String, pub active: bool, - pub current_date: Option, + pub last_date: Option, pub time_shift: f64, #[sqlx(default)] @@ -192,7 +192,6 @@ pub struct Configuration { pub text_help: String, pub add_text: bool, - pub fontfile: String, pub text_from_filename: bool, pub style: String, diff --git a/ffplayout/src/player/controller.rs b/ffplayout/src/player/controller.rs index 747d5964..7350f7fd 100644 --- a/ffplayout/src/player/controller.rs +++ b/ffplayout/src/player/controller.rs @@ -56,7 +56,7 @@ pub struct ChannelManager { pub ingest_is_running: Arc, pub is_terminated: Arc, pub is_alive: Arc, - pub chain: Option>>>, + pub filter_chain: Option>>>, pub current_date: Arc>, pub list_init: Arc, pub current_media: Arc>>, @@ -84,13 +84,13 @@ impl ChannelManager { pub fn update_channel(self, other: &Channel) { let mut channel = self.channel.lock().unwrap(); - channel.name = other.name.clone(); - channel.preview_url = other.preview_url.clone(); - channel.extra_extensions = other.extra_extensions.clone(); - channel.active = other.active.clone(); - channel.current_date = other.current_date.clone(); - channel.time_shift = other.time_shift.clone(); - channel.utc_offset = other.utc_offset.clone(); + channel.name.clone_from(&other.name); + channel.preview_url.clone_from(&other.preview_url); + channel.extra_extensions.clone_from(&other.extra_extensions); + channel.active.clone_from(&other.active); + channel.last_date.clone_from(&other.last_date); + channel.time_shift.clone_from(&other.time_shift); + channel.utc_offset.clone_from(&other.utc_offset); } pub fn stop(&self, unit: ProcessUnit) -> Result<(), ProcessError> { diff --git a/ffplayout/src/player/input/mod.rs b/ffplayout/src/player/input/mod.rs index b8d3ea3b..bca585f6 100644 --- a/ffplayout/src/player/input/mod.rs +++ b/ffplayout/src/player/input/mod.rs @@ -24,9 +24,7 @@ pub fn source_generator( ) -> Box> { let config = manager.config.lock().unwrap().clone(); let is_terminated = manager.is_terminated.clone(); - let chain = manager.chain.clone(); let current_list = manager.current_list.clone(); - let current_index = manager.current_index.clone(); match config.processing.mode { Folder => { @@ -37,8 +35,7 @@ pub fn source_generator( ); let config_clone = config.clone(); - let folder_source = - FolderSource::new(&config, chain, current_list.clone(), current_index); + let folder_source = FolderSource::new(&config, manager); let list_clone = current_list.clone(); // Spawn a thread to monitor folder for file changes. diff --git a/ffplayout/src/player/input/playlist.rs b/ffplayout/src/player/input/playlist.rs index 9f8ddd0a..1d4b45de 100644 --- a/ffplayout/src/player/input/playlist.rs +++ b/ffplayout/src/player/input/playlist.rs @@ -2,11 +2,10 @@ use std::{ path::Path, sync::{ atomic::{AtomicBool, Ordering}, - Arc, + Arc, Mutex, }, }; -use futures::executor; use simplelog::*; use sqlx::{Pool, Sqlite}; @@ -101,7 +100,7 @@ impl CurrentProgram { .channel .lock() .unwrap() - .current_date + .last_date .clone() .unwrap_or_default() != self.json_playlist.date @@ -198,7 +197,7 @@ impl CurrentProgram { } fn set_status(&mut self, date: String) { - if self.manager.channel.lock().unwrap().current_date != Some(date.clone()) + if self.manager.channel.lock().unwrap().last_date != Some(date.clone()) && self.manager.channel.lock().unwrap().time_shift != 0.0 { info!("Reset playout status"); @@ -209,16 +208,19 @@ impl CurrentProgram { .channel .lock() .unwrap() - .current_date + .last_date .clone_from(&Some(date.clone())); self.manager.channel.lock().unwrap().time_shift = 0.0; - if let Err(e) = executor::block_on(handles::update_stat( - &self.db_pool, - self.config.general.channel_id, - date, - 0.0, - )) { + if let Err(e) = tokio::runtime::Runtime::new() + .unwrap() + .block_on(handles::update_stat( + &self.db_pool, + self.config.general.channel_id, + date, + 0.0, + )) + { error!("Unable to write status: {e}"); }; } @@ -255,7 +257,7 @@ impl CurrentProgram { // On init or reload we need to seek for the current clip. fn get_current_clip(&mut self) { let mut time_sec = self.get_current_time(); - let shift = self.manager.channel.lock().unwrap().time_shift.clone(); + let shift = self.manager.channel.lock().unwrap().time_shift; if shift != 0.0 { info!("Shift playlist start for {shift:.3} seconds"); @@ -306,13 +308,8 @@ impl CurrentProgram { self.manager.current_index.fetch_add(1, Ordering::SeqCst); - self.current_node = handle_list_init( - &self.config, - node_clone, - &self.playout_stat, - &self.player_control, - last_index, - ); + self.current_node = + handle_list_init(&self.config, node_clone, &self.manager, last_index); if self .current_node @@ -337,15 +334,9 @@ impl CurrentProgram { self.last_next_ad(&mut media); - self.current_node = gen_source( - &self.config, - media, - &self.playout_stat, - &self.player_control, - 0, - ); + self.current_node = gen_source(&self.config, media, &self.manager, 0); - self.player_control + self.manager .current_list .lock() .unwrap() @@ -353,11 +344,9 @@ impl CurrentProgram { self.current_node.last_ad = self.last_node_ad; self.current_node - .add_filter(&self.config, &self.playout_stat.chain); + .add_filter(&self.config, &self.manager.filter_chain); - self.player_control - .current_index - .fetch_add(1, Ordering::SeqCst); + self.manager.current_index.fetch_add(1, Ordering::SeqCst); } fn recalculate_begin(&mut self, extend: bool) { @@ -371,7 +360,7 @@ impl CurrentProgram { self.json_playlist.start_sec = Some(time_sec); set_defaults(&mut self.json_playlist); - self.player_control + self.manager .current_list .lock() .unwrap() @@ -386,9 +375,9 @@ impl Iterator for CurrentProgram { fn next(&mut self) -> Option { self.last_json_path.clone_from(&self.json_playlist.path); self.last_node_ad = self.current_node.last_ad; - self.check_for_playlist(self.playout_stat.list_init.load(Ordering::SeqCst)); + self.check_for_playlist(self.manager.list_init.load(Ordering::SeqCst)); - if self.playout_stat.list_init.load(Ordering::SeqCst) { + if self.manager.list_init.load(Ordering::SeqCst) { trace!("Init playlist, from next iterator"); let mut init_clip_is_filler = false; @@ -396,7 +385,7 @@ impl Iterator for CurrentProgram { init_clip_is_filler = self.init_clip(); } - if self.playout_stat.list_init.load(Ordering::SeqCst) && !init_clip_is_filler { + if self.manager.list_init.load(Ordering::SeqCst) && !init_clip_is_filler { // On init load, playlist could be not long enough, or clips are not found // so we fill the gap with a dummy. trace!("Init clip is no filler"); @@ -409,7 +398,7 @@ impl Iterator for CurrentProgram { } let mut last_index = 0; - let length = self.player_control.current_list.lock().unwrap().len(); + let length = self.manager.current_list.lock().unwrap().len(); if length > 0 { last_index = length - 1; @@ -422,26 +411,20 @@ impl Iterator for CurrentProgram { self.last_next_ad(&mut media); - self.current_node = gen_source( - &self.config, - media, - &self.playout_stat, - &self.player_control, - last_index, - ); + self.current_node = gen_source(&self.config, media, &self.manager, last_index); } return Some(self.current_node.clone()); } - if self.player_control.current_index.load(Ordering::SeqCst) - < self.player_control.current_list.lock().unwrap().len() + if self.manager.current_index.load(Ordering::SeqCst) + < self.manager.current_list.lock().unwrap().len() { // get next clip from current playlist let mut is_last = false; - let index = self.player_control.current_index.load(Ordering::SeqCst); - let node_list = self.player_control.current_list.lock().unwrap(); + let index = self.manager.current_index.load(Ordering::SeqCst); + let node_list = self.manager.current_list.lock().unwrap(); let mut node = node_list[index].clone(); let last_index = node_list.len() - 1; @@ -453,18 +436,10 @@ impl Iterator for CurrentProgram { self.last_next_ad(&mut node); - self.current_node = timed_source( - node, - &self.config, - is_last, - &self.playout_stat, - &self.player_control, - last_index, - ); + self.current_node = + timed_source(node, &self.config, is_last, &self.manager, last_index); - self.player_control - .current_index - .fetch_add(1, Ordering::SeqCst); + self.manager.current_index.fetch_add(1, Ordering::SeqCst); Some(self.current_node.clone()) } else { @@ -484,7 +459,7 @@ impl Iterator for CurrentProgram { } // Get first clip from next playlist. - let c_list = self.player_control.current_list.lock().unwrap(); + let c_list = self.manager.current_list.lock().unwrap(); let mut first_node = c_list[0].clone(); drop(c_list); @@ -493,19 +468,13 @@ impl Iterator for CurrentProgram { self.recalculate_begin(false) } - self.player_control.current_index.store(0, Ordering::SeqCst); + self.manager.current_index.store(0, Ordering::SeqCst); self.last_next_ad(&mut first_node); first_node.last_ad = self.last_node_ad; - self.current_node = gen_source( - &self.config, - first_node, - &self.playout_stat, - &self.player_control, - 0, - ); + self.current_node = gen_source(&self.config, first_node, &self.manager, 0); - self.player_control.current_index.store(1, Ordering::SeqCst); + self.manager.current_index.store(1, Ordering::SeqCst); Some(self.current_node.clone()) } @@ -520,10 +489,12 @@ fn timed_source( node: Media, config: &PlayoutConfig, last: bool, - playout_stat: &PlayoutStatus, - player_control: &PlayerControl, + manager: &ChannelManager, last_index: usize, ) -> Media { + let time_shift = manager.channel.lock().unwrap().time_shift; + let current_date = manager.current_date.lock().unwrap().clone(); + let last_date = manager.channel.lock().unwrap().last_date.clone(); let (delta, total_delta) = get_delta(config, &node.begin.unwrap()); let mut shifted_delta = delta; let mut new_node = node.clone(); @@ -533,12 +504,8 @@ fn timed_source( trace!("timed source is last: {last}"); if config.playlist.length.contains(':') { - let time_shift = playout_stat.time_shift.lock().unwrap(); - - if *playout_stat.current_date.lock().unwrap() == *playout_stat.date.lock().unwrap() - && *time_shift != 0.0 - { - shifted_delta = delta - *time_shift; + if Some(current_date) == last_date && time_shift != 0.0 { + shifted_delta = delta - time_shift; debug!("Delta: {shifted_delta:.3}, shifted: {delta:.3}"); } else { @@ -563,26 +530,19 @@ fn timed_source( { // when we are in the 24 hour range, get the clip new_node.process = Some(true); - new_node = gen_source(config, node, playout_stat, player_control, last_index); + new_node = gen_source(config, node, manager, last_index); } else if total_delta <= 0.0 { info!("Begin is over play time, skip: {}", node.source); } else if total_delta < node.duration - node.seek || last { - new_node = handle_list_end( - config, - node, - total_delta, - playout_stat, - player_control, - last_index, - ); + new_node = handle_list_end(config, node, total_delta, manager, last_index); } new_node } -fn duplicate_for_seek_and_loop(node: &mut Media, player_control: &PlayerControl) { +fn duplicate_for_seek_and_loop(node: &mut Media, current_list: &Arc>>) { warn!("Clip loops and has seek value: duplicate clip to separate loop and seek."); - let mut nodes = player_control.current_list.lock().unwrap(); + let mut nodes = current_list.lock().unwrap(); let index = node.index.unwrap_or_default(); let mut node_duplicate = node.clone(); @@ -617,8 +577,7 @@ fn duplicate_for_seek_and_loop(node: &mut Media, player_control: &PlayerControl) pub fn gen_source( config: &PlayoutConfig, mut node: Media, - playout_stat: &PlayoutStatus, - player_control: &PlayerControl, + manager: &ChannelManager, last_index: usize, ) -> Media { let node_index = node.index.unwrap_or_default(); @@ -658,7 +617,7 @@ pub fn gen_source( node.cmd = Some(loop_image(&node)); } else { if node.seek > 0.0 && node.out > node.duration { - duplicate_for_seek_and_loop(&mut node, player_control); + duplicate_for_seek_and_loop(&mut node, &manager.current_list); } node.cmd = Some(seek_and_length(&mut node)); @@ -671,25 +630,25 @@ pub fn gen_source( error!("Source not found: {}", node.source); } - let mut filler_list = vec![]; + let mut fillers = vec![]; - match player_control.filler_list.try_lock() { - Ok(list) => filler_list = list.to_vec(), + match manager.filler_list.try_lock() { + Ok(list) => fillers = list.to_vec(), Err(e) => error!("Lock filler list error: {e}"), } // Set list_init to true, to stay in sync. - playout_stat.list_init.store(true, Ordering::SeqCst); + manager.list_init.store(true, Ordering::SeqCst); - if config.storage.filler.is_dir() && !filler_list.is_empty() { - let filler_index = player_control.filler_index.fetch_add(1, Ordering::SeqCst); - let mut filler_media = filler_list[filler_index].clone(); + if config.storage.filler.is_dir() && !fillers.is_empty() { + let index = manager.filler_index.fetch_add(1, Ordering::SeqCst); + let mut filler_media = fillers[index].clone(); trace!("take filler: {}", filler_media.source); - if filler_index == filler_list.len() - 1 { + if index == fillers.len() - 1 { // reset index for next round - player_control.filler_index.store(0, Ordering::SeqCst) + manager.filler_index.store(0, Ordering::SeqCst) } if filler_media.probe.is_none() { @@ -776,7 +735,7 @@ pub fn gen_source( ); } - node.add_filter(config, &playout_stat.chain); + node.add_filter(config, &manager.filter_chain.clone()); trace!( "return gen_source: {}, seek: {}, out: {}", @@ -793,8 +752,7 @@ pub fn gen_source( fn handle_list_init( config: &PlayoutConfig, mut node: Media, - playout_stat: &PlayoutStatus, - player_control: &PlayerControl, + manager: &ChannelManager, last_index: usize, ) -> Media { debug!("Playlist init"); @@ -804,7 +762,7 @@ fn handle_list_init( node.out = total_delta + node.seek; } - gen_source(config, node, playout_stat, player_control, last_index) + gen_source(config, node, manager, last_index) } /// when we come to last clip in playlist, @@ -814,8 +772,7 @@ fn handle_list_end( config: &PlayoutConfig, mut node: Media, total_delta: f64, - playout_stat: &PlayoutStatus, - player_control: &PlayerControl, + manager: &ChannelManager, last_index: usize, ) -> Media { debug!("Last clip from day"); @@ -844,5 +801,5 @@ fn handle_list_end( node.process = Some(true); - gen_source(config, node, playout_stat, player_control, last_index) + gen_source(config, node, manager, last_index) } diff --git a/ffplayout/src/player/output/hls.rs b/ffplayout/src/player/output/hls.rs index 1d109cbd..54a12995 100644 --- a/ffplayout/src/player/output/hls.rs +++ b/ffplayout/src/player/output/hls.rs @@ -46,7 +46,7 @@ use crate::{ fn ingest_to_hls_server(manager: ChannelManager) -> Result<(), ProcessError> { let config = manager.config.lock().unwrap(); let playlist_init = manager.list_init.clone(); - let chain = manager.chain.clone(); + let chain = manager.filter_chain.clone(); let mut server_prefix = vec_strings!["-hide_banner", "-nostats", "-v", "level+info"]; let stream_input = config.ingest.input_cmd.clone().unwrap(); diff --git a/ffplayout/src/player/utils/folder.rs b/ffplayout/src/player/utils/folder.rs index f093c87a..b2359683 100644 --- a/ffplayout/src/player/utils/folder.rs +++ b/ffplayout/src/player/utils/folder.rs @@ -1,5 +1,5 @@ use std::sync::{ - atomic::{AtomicUsize, Ordering}, + atomic::Ordering, {Arc, Mutex}, }; @@ -8,27 +8,22 @@ use rand::{seq::SliceRandom, thread_rng}; use simplelog::*; use walkdir::WalkDir; -use crate::player::utils::{include_file_extension, time_in_seconds, Media, PlayoutConfig}; +use crate::player::{ + controller::ChannelManager, + utils::{include_file_extension, time_in_seconds, Media, PlayoutConfig}, +}; /// Folder Sources /// /// Like playlist source, we create here a folder list for iterate over it. #[derive(Debug, Clone)] pub struct FolderSource { - config: PlayoutConfig, - filter_chain: Option>>>, - pub current_list: Arc>>, - pub current_index: Arc, + manager: ChannelManager, current_node: Media, } impl FolderSource { - pub fn new( - config: &PlayoutConfig, - filter_chain: Option>>>, - current_list: Arc>>, - current_index: Arc, - ) -> Self { + pub fn new(config: &PlayoutConfig, manager: ChannelManager) -> Self { let mut path_list = vec![]; let mut media_list = vec![]; let mut index: usize = 0; @@ -78,38 +73,26 @@ impl FolderSource { index += 1; } - *current_list.lock().unwrap() = media_list; + *manager.current_list.lock().unwrap() = media_list; Self { - config: config.clone(), - filter_chain, - current_list, - current_index, + manager, current_node: Media::new(0, "", false), } } - pub fn from_list( - config: &PlayoutConfig, - filter_chain: Option>>>, - current_list: Arc>>, - current_index: Arc, - list: Vec, - ) -> Self { - *current_list.lock().unwrap() = list; + pub fn from_list(manager: &ChannelManager, list: Vec) -> Self { + *manager.current_list.lock().unwrap() = list; Self { - config: config.clone(), - filter_chain, - current_list, - current_index, + manager: manager.clone(), current_node: Media::new(0, "", false), } } fn shuffle(&mut self) { let mut rng = thread_rng(); - let mut nodes = self.current_list.lock().unwrap(); + let mut nodes = self.manager.current_list.lock().unwrap(); nodes.shuffle(&mut rng); @@ -119,7 +102,7 @@ impl FolderSource { } fn sort(&mut self) { - let mut nodes = self.current_list.lock().unwrap(); + let mut nodes = self.manager.current_list.lock().unwrap(); nodes.sort_by(|d1, d2| d1.source.cmp(&d2.source)); @@ -134,39 +117,43 @@ impl Iterator for FolderSource { type Item = Media; fn next(&mut self) -> Option { - if self.current_index.load(Ordering::SeqCst) < self.current_list.lock().unwrap().len() { - let i = self.current_index.load(Ordering::SeqCst); - self.current_node = self.current_list.lock().unwrap()[i].clone(); + let config = self.manager.config.lock().unwrap().clone(); + + if self.manager.current_index.load(Ordering::SeqCst) + < self.manager.current_list.lock().unwrap().len() + { + let i = self.manager.current_index.load(Ordering::SeqCst); + self.current_node = self.manager.current_list.lock().unwrap()[i].clone(); let _ = self.current_node.add_probe(false).ok(); self.current_node - .add_filter(&self.config, &self.filter_chain); + .add_filter(&config, &self.manager.filter_chain); self.current_node.begin = Some(time_in_seconds()); - self.current_index.fetch_add(1, Ordering::SeqCst); + self.manager.current_index.fetch_add(1, Ordering::SeqCst); Some(self.current_node.clone()) } else { - if self.config.storage.shuffle { - if self.config.general.generate.is_none() { + if config.storage.shuffle { + if config.general.generate.is_none() { info!("Shuffle files"); } self.shuffle(); } else { - if self.config.general.generate.is_none() { + if config.general.generate.is_none() { info!("Sort files"); } self.sort(); } - self.current_node = self.current_list.lock().unwrap()[0].clone(); + self.current_node = self.manager.current_list.lock().unwrap()[0].clone(); let _ = self.current_node.add_probe(false).ok(); self.current_node - .add_filter(&self.config, &self.filter_chain); + .add_filter(&config, &self.manager.filter_chain); self.current_node.begin = Some(time_in_seconds()); - self.current_index.store(1, Ordering::SeqCst); + self.manager.current_index.store(1, Ordering::SeqCst); Some(self.current_node.clone()) } diff --git a/ffplayout/src/sse/routes.rs b/ffplayout/src/sse/routes.rs index 406080c5..aa06409f 100644 --- a/ffplayout/src/sse/routes.rs +++ b/ffplayout/src/sse/routes.rs @@ -28,7 +28,7 @@ impl User { /// curl -X GET 'http://127.0.0.1:8787/api/generate-uuid' -H 'Authorization: Bearer ' /// ``` #[post("/generate-uuid")] -#[protect(any("Role::Admin", "Role::User"), ty = "Role")] +#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")] async fn generate_uuid(data: web::Data) -> Result { let mut uuids = data.uuids.lock().await; let new_uuid = UuidData::new(); diff --git a/ffplayout/src/utils/config.rs b/ffplayout/src/utils/config.rs index 88359d87..37afaa32 100644 --- a/ffplayout/src/utils/config.rs +++ b/ffplayout/src/utils/config.rs @@ -83,10 +83,11 @@ impl FromStr for OutputMode { } } -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +#[derive(Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq)] #[serde(rename_all = "lowercase")] pub enum ProcessMode { Folder, + #[default] Playlist, } @@ -99,12 +100,6 @@ impl ProcessMode { } } -impl Default for ProcessMode { - fn default() -> Self { - ProcessMode::Playlist - } -} - impl fmt::Display for ProcessMode { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { @@ -144,6 +139,7 @@ pub struct Source { /// This we init ones, when ffplayout is starting and use them globally in the hole program. #[derive(Debug, Default, Clone, Deserialize, Serialize)] pub struct PlayoutConfig { + #[serde(skip_serializing)] pub advanced: AdvancedConfig, pub general: General, pub mail: Mail, @@ -160,13 +156,20 @@ pub struct PlayoutConfig { #[derive(Debug, Default, Clone, Deserialize, Serialize)] pub struct General { pub help_text: String, + #[serde(skip_serializing)] pub channel_id: i32, pub stop_threshold: f64, + #[serde(skip_serializing)] pub generate: Option>, + #[serde(skip_serializing)] pub ffmpeg_filters: Vec, + #[serde(skip_serializing)] pub ffmpeg_libs: Vec, + #[serde(skip_serializing)] pub template: Option