diff --git a/.vscode/settings.json b/.vscode/settings.json index d89a1f00..ed2e7ef1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,6 +20,7 @@ "cSpell.words": [ "actix", "rsplit", + "starttls", "tokio", "uuids" ] diff --git a/Cargo.lock b/Cargo.lock index ac231a67..9cfc5c66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1346,13 +1346,13 @@ dependencies = [ "sanitize-filename", "serde", "serde_json", - "serde_yaml", "simplelog", "sqlx", "static-files", "sysinfo", "tokio", "tokio-stream", + "toml_edit", "uuid", ] @@ -1375,11 +1375,11 @@ dependencies = [ "reqwest", "serde", "serde_json", - "serde_yaml", "shlex", "signal-child", "simplelog", "time", + "toml_edit", "walkdir", "winapi", ] @@ -3068,6 +3068,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3080,19 +3089,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - [[package]] name = "serial_test" version = "3.1.0" @@ -3593,11 +3589,11 @@ dependencies = [ "reqwest", "serde", "serde_json", - "serde_yaml", "serial_test", "shlex", "simplelog", "time", + "toml_edit", "walkdir", ] @@ -3747,6 +3743,28 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower" version = "0.4.13" @@ -3861,12 +3879,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - [[package]] name = "untrusted" version = "0.9.0" @@ -4250,6 +4262,15 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +[[package]] +name = "winnow" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.52.0" diff --git a/assets/advanced.toml b/assets/advanced.toml new file mode 100644 index 00000000..24152f40 --- /dev/null +++ b/assets/advanced.toml @@ -0,0 +1,37 @@ +# 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/advanced.yml b/assets/advanced.yml deleted file mode 100644 index 6a744b00..00000000 --- a/assets/advanced.yml +++ /dev/null @@ -1,32 +0,0 @@ -help: 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/ffplayout.toml b/assets/ffplayout.toml new file mode 100644 index 00000000..db516622 --- /dev/null +++ b/assets/ffplayout.toml @@ -0,0 +1,168 @@ +[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.""" +subject = "Playout Error" +smtp_server = "mail.example.org" +starttls = true +sender_addr = "ffplayout@example.org" +sender_pass = "abc123" +recipient = "" +mail_level = "ERROR" +interval = 30 + +[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"] +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)$" + +[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.yml b/assets/ffplayout.yml deleted file mode 100644 index ea18c09d..00000000 --- a/assets/ffplayout.yml +++ /dev/null @@ -1,167 +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. - subject: Playout Error - smtp_server: mail.example.org - starttls: true - sender_addr: ffplayout@example.org - sender_pass: "abc123" - recipient: - mail_level: ERROR - interval: 30 - -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" - 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)$ - -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/ffplayout-api/Cargo.toml b/ffplayout-api/Cargo.toml index 10328234..96d1b1ab 100644 --- a/ffplayout-api/Cargo.toml +++ b/ffplayout-api/Cargo.toml @@ -42,13 +42,13 @@ rpassword = "7.2" sanitize-filename = "0.5" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -serde_yaml = "0.9" simplelog = { version = "0.12", features = ["paris"] } static-files = "0.2" sysinfo ={ version = "0.30", features = ["linux-netdevs"] } sqlx = { version = "0.7", features = ["runtime-tokio", "sqlite"] } tokio = { version = "1.29", features = ["full"] } tokio-stream = "0.1" +toml_edit = {version ="0.22", features = ["serde"]} uuid = "1.8" [build-dependencies] diff --git a/ffplayout-api/src/api/routes.rs b/ffplayout-api/src/api/routes.rs index 17806f3f..fc736554 100644 --- a/ffplayout-api/src/api/routes.rs +++ b/ffplayout-api/src/api/routes.rs @@ -387,7 +387,7 @@ async fn remove_user( /// "id": 1, /// "name": "Channel 1", /// "preview_url": "http://localhost/live/preview.m3u8", -/// "config_path": "/etc/ffplayout/ffplayout.yml", +/// "config_path": "/etc/ffplayout/ffplayout.toml", /// "extra_extensions": "jpg,jpeg,png", /// "service": "ffplayout.service", /// "utc_offset": "+120" @@ -425,7 +425,7 @@ async fn get_all_channels(pool: web::Data>) -> Result" /// ``` #[patch("/channel/{id}")] @@ -449,7 +449,7 @@ async fn patch_channel( /// /// ```BASH /// curl -X POST http://127.0.0.1:8787/api/channel/ -H "Content-Type: application/json" \ -/// -d '{ "name": "Channel 2", "preview_url": "http://localhost/live/channel2.m3u8", "config_path": "/etc/ffplayout/channel2.yml", "extra_extensions": "jpg,jpeg,png", "service": "ffplayout@channel2.service" }' \ +/// -d '{ "name": "Channel 2", "preview_url": "http://localhost/live/channel2.m3u8", "config_path": "/etc/ffplayout/channel2.toml", "extra_extensions": "jpg,jpeg,png", "service": "ffplayout@channel2.service" }' \ /// -H "Authorization: Bearer " /// ``` #[post("/channel/")] @@ -490,7 +490,7 @@ async fn remove_channel( /// curl -X GET http://127.0.0.1:8787/api/playout/config/1 -H 'Authorization: Bearer ' /// ``` /// -/// Response is a JSON object from the ffplayout.yml +/// Response is a JSON object from the ffplayout.toml #[get("/playout/config/{id}")] #[protect(any("Role::Admin", "Role::User"), ty = "Role")] async fn get_playout_config( @@ -521,17 +521,10 @@ async fn update_playout_config( data: web::Json, ) -> Result { if let Ok(channel) = handles::select_channel(&pool.into_inner(), &id).await { - if let Ok(f) = std::fs::OpenOptions::new() - .write(true) - .truncate(true) - .open(channel.config_path) - { - serde_yaml::to_writer(f, &data).unwrap(); + let toml_string = toml_edit::ser::to_string_pretty(&data)?; + fs::write(&channel.config_path, toml_string).await?; - return Ok("Update playout config success."); - } else { - return Err(ServiceError::InternalServerError); - }; + return Ok("Update playout config success."); }; Err(ServiceError::InternalServerError) diff --git a/ffplayout-api/src/db/handles.rs b/ffplayout-api/src/db/handles.rs index 7bbf6846..cb35f5a7 100644 --- a/ffplayout-api/src/db/handles.rs +++ b/ffplayout-api/src/db/handles.rs @@ -99,9 +99,9 @@ pub async fn db_init(domain: Option) -> Result<&'static str, Box for ServiceError { } } +impl From for ServiceError { + fn from(err: toml_edit::ser::Error) -> ServiceError { + ServiceError::BadRequest(err.to_string()) + } +} + impl From for ServiceError { fn from(err: uuid::Error) -> ServiceError { ServiceError::BadRequest(err.to_string()) diff --git a/ffplayout-api/src/utils/mod.rs b/ffplayout-api/src/utils/mod.rs index ca94fbbd..1c52c6a4 100644 --- a/ffplayout-api/src/utils/mod.rs +++ b/ffplayout-api/src/utils/mod.rs @@ -3,7 +3,7 @@ use std::{ error::Error, fmt, fs::{self, metadata, File}, - io::{stdin, stdout, Write}, + io::{stdin, stdout, Read, Write}, path::{Path, PathBuf}, str::FromStr, }; @@ -274,8 +274,11 @@ pub async fn run_args() -> Result<(), i32> { } pub fn read_playout_config(path: &str) -> Result> { - let file = File::open(path)?; - let mut config: PlayoutConfig = serde_yaml::from_reader(file)?; + let mut file = File::open(path)?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + + let mut config: PlayoutConfig = toml_edit::de::from_str(&contents)?; config.playlist.start_sec = Some(time_to_sec(&config.playlist.day_start)); config.playlist.length_sec = Some(time_to_sec(&config.playlist.length)); @@ -288,8 +291,9 @@ pub async fn playout_config( channel_id: &i32, ) -> Result<(PlayoutConfig, Channel), ServiceError> { if let Ok(channel) = select_channel(conn, channel_id).await { - if let Ok(config) = read_playout_config(&channel.config_path.clone()) { - return Ok((config, channel)); + match read_playout_config(&channel.config_path.clone()) { + Ok(config) => return Ok((config, channel)), + Err(e) => error!("{e}"), } } diff --git a/ffplayout-engine/Cargo.toml b/ffplayout-engine/Cargo.toml index 384b6e1e..f96a307a 100644 --- a/ffplayout-engine/Cargo.toml +++ b/ffplayout-engine/Cargo.toml @@ -45,7 +45,7 @@ depends = "" recommends = "sudo" suggests = "ffmpeg" copyright = "Copyright (c) 2022, Jonathan Baecker. All rights reserved." -conf-files = ["/etc/ffplayout/ffplayout.yml"] +conf-files = ["/etc/ffplayout/ffplayout.toml"] assets = [ [ "../target/x86_64-unknown-linux-musl/release/ffpapi", @@ -78,12 +78,12 @@ assets = [ "644", ], [ - "../assets/advanced.yml", + "../assets/advanced.toml", "/etc/ffplayout/", "644", ], [ - "../assets/ffplayout.yml", + "../assets/ffplayout.toml", "/etc/ffplayout/", "644", ], @@ -93,8 +93,8 @@ assets = [ "644", ], [ - "../assets/ffplayout.yml", - "/usr/share/ffplayout/ffplayout.yml.orig", + "../assets/ffplayout.toml", + "/usr/share/ffplayout/ffplayout.toml.orig", "644", ], [ @@ -154,12 +154,12 @@ assets = [ "644", ], [ - "../assets/ffplayout.yml", + "../assets/ffplayout.toml", "/etc/ffplayout/", "644", ], [ - "../assets/advanced.yml", + "../assets/advanced.toml", "/etc/ffplayout/", "644", ], @@ -169,8 +169,8 @@ assets = [ "644", ], [ - "../assets/ffplayout.yml", - "/usr/share/ffplayout/ffplayout.yml.orig", + "../assets/ffplayout.toml", + "/usr/share/ffplayout/ffplayout.toml.orig", "644", ], [ @@ -202,8 +202,8 @@ license = "GPL-3.0" assets = [ { source = "../target/x86_64-unknown-linux-musl/release/ffpapi", dest = "/usr/bin/ffpapi", mode = "755" }, { source = "../target/x86_64-unknown-linux-musl/release/ffplayout", dest = "/usr/bin/ffplayout", mode = "755" }, - { source = "../assets/advanced.yml", dest = "/etc/ffplayout/advanced.yml", mode = "644", config = true }, - { source = "../assets/ffplayout.yml", dest = "/etc/ffplayout/ffplayout.yml", mode = "644", config = true }, + { source = "../assets/advanced.toml", dest = "/etc/ffplayout/advanced.toml", mode = "644", config = true }, + { source = "../assets/ffplayout.toml", dest = "/etc/ffplayout/ffplayout.toml", mode = "644", config = true }, { source = "../assets/ffpapi.service", dest = "/lib/systemd/system/ffpapi.service", mode = "644" }, { source = "../assets/ffplayout.service", dest = "/lib/systemd/system/ffplayout.service", mode = "644" }, { source = "../assets/ffplayout@.service", dest = "/lib/systemd/system/ffplayout@.service", mode = "644" }, @@ -213,7 +213,7 @@ assets = [ { source = "../assets/ffplayout.1.gz", dest = "/usr/share/man/man1/ffplayout.1.gz", mode = "644", doc = true }, { source = "../LICENSE", dest = "/usr/share/doc/ffplayout/LICENSE", mode = "644" }, { source = "../assets/logo.png", dest = "/usr/share/ffplayout/logo.png", mode = "644" }, - { source = "../assets/ffplayout.yml", dest = "/usr/share/ffplayout/ffplayout.yml.orig", mode = "644" }, + { source = "../assets/ffplayout.toml", dest = "/usr/share/ffplayout/ffplayout.toml.orig", mode = "644" }, { source = "../assets/ffplayout.conf", dest = "/usr/share/ffplayout/ffplayout.conf.example", mode = "644" }, { source = "../debian/postinst", dest = "/usr/share/ffplayout/postinst", mode = "755" }, ] diff --git a/ffplayout-engine/src/utils/arg_parse.rs b/ffplayout-engine/src/utils/arg_parse.rs index 975e39dd..5276aac1 100644 --- a/ffplayout-engine/src/utils/arg_parse.rs +++ b/ffplayout-engine/src/utils/arg_parse.rs @@ -11,13 +11,13 @@ use ffplayout_lib::utils::{OutputMode, ProcessMode}; \n ffplayout (ARGS) [OPTIONS]\n\n Pass channel name only in multi channel environment!", long_about = None)] pub struct Args { - #[clap(long, help = "File path to advanced.yml")] + #[clap(long, help = "File path to advanced.toml")] pub advanced_config: Option, #[clap(index = 1, value_parser, help = "Channel name")] pub channel: Option, - #[clap(short, long, help = "File path to ffplayout.yml")] + #[clap(short, long, help = "File path to ffplayout.toml")] pub config: Option, #[clap(short, long, help = "File path for logging")] diff --git a/ffplayout-engine/src/utils/mod.rs b/ffplayout-engine/src/utils/mod.rs index 678e1d8e..7d0a319a 100644 --- a/ffplayout-engine/src/utils/mod.rs +++ b/ffplayout-engine/src/utils/mod.rs @@ -25,7 +25,7 @@ use ffplayout_lib::{ pub fn get_config(args: Args) -> Result { let cfg_path = match args.channel { Some(c) => { - let path = PathBuf::from(format!("/etc/ffplayout/{c}.yml")); + let path = PathBuf::from(format!("/etc/ffplayout/{c}.toml")); if !path.is_file() { return Err(ProcError::Custom(format!( @@ -38,15 +38,15 @@ pub fn get_config(args: Args) -> Result { None => args.config, }; - let mut adv_config_path = PathBuf::from("/etc/ffplayout/advanced.yml"); + let mut adv_config_path = PathBuf::from("/etc/ffplayout/advanced.toml"); if let Some(adv_path) = args.advanced_config { adv_config_path = adv_path; } else if !adv_config_path.is_file() { - if Path::new("./assets/advanced.yml").is_file() { - adv_config_path = PathBuf::from("./assets/advanced.yml") + if Path::new("./assets/advanced.toml").is_file() { + adv_config_path = PathBuf::from("./assets/advanced.toml") } else if let Some(p) = env::current_exe().ok().as_ref().and_then(|op| op.parent()) { - adv_config_path = p.join("advanced.yml") + adv_config_path = p.join("advanced.toml") }; } diff --git a/lib/Cargo.toml b/lib/Cargo.toml index f2d1e1b2..e862fd82 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -24,10 +24,10 @@ regex = "1" reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -serde_yaml = "0.9" shlex = "1.1" simplelog = { version = "0.12", features = ["paris"] } time = { version = "0.3", features = ["formatting", "macros"] } +toml_edit = {version ="0.22", features = ["serde"]} walkdir = "2" [target."cfg(windows)".dependencies.winapi] diff --git a/lib/src/utils/advanced_config.rs b/lib/src/utils/advanced_config.rs index ad81991c..fd5833cd 100644 --- a/lib/src/utils/advanced_config.rs +++ b/lib/src/utils/advanced_config.rs @@ -1,11 +1,10 @@ -use std::{fs::File, path::PathBuf}; +use std::{fs::File, io::Read, path::PathBuf}; use serde::{Deserialize, Serialize}; use shlex::split; #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct AdvancedConfig { - pub help: Option, pub decoder: DecoderConfig, pub encoder: EncoderConfig, pub ingest: IngestConfig, @@ -66,10 +65,15 @@ impl AdvancedConfig { pub fn new(cfg_path: PathBuf) -> Self { let mut config: AdvancedConfig = Default::default(); - if let Ok(f) = File::open(cfg_path) { - config = match serde_yaml::from_reader(f) { - Ok(yaml) => yaml, - Err(_) => AdvancedConfig::default(), + if let Ok(mut file) = File::open(cfg_path) { + let mut contents = String::new(); + + if let Err(e) = file.read_to_string(&mut contents) { + eprintln!("Read advanced config file: {e}") + }; + + if let Ok(tm) = toml_edit::de::from_str(&contents) { + config = tm }; if let Some(input_parm) = &config.decoder.input_param { diff --git a/lib/src/utils/config.rs b/lib/src/utils/config.rs index f7a587ce..1e62a96d 100644 --- a/lib/src/utils/config.rs +++ b/lib/src/utils/config.rs @@ -1,6 +1,7 @@ use std::{ env, fmt, fs::File, + io::Read, path::{Path, PathBuf}, process, str::FromStr, @@ -365,32 +366,37 @@ fn default_channels() -> u8 { impl PlayoutConfig { /// Read config from YAML file, and set some extra config values. pub fn new(cfg_path: Option, advanced_path: Option) -> Self { - let mut config_path = PathBuf::from("/etc/ffplayout/ffplayout.yml"); + let mut config_path = PathBuf::from("/etc/ffplayout/ffplayout.toml"); if let Some(cfg) = cfg_path { config_path = cfg; } if !config_path.is_file() { - if Path::new("./assets/ffplayout.yml").is_file() { - config_path = PathBuf::from("./assets/ffplayout.yml") + if Path::new("./assets/ffplayout.toml").is_file() { + config_path = PathBuf::from("./assets/ffplayout.toml") } else if let Some(p) = env::current_exe().ok().as_ref().and_then(|op| op.parent()) { - config_path = p.join("ffplayout.yml") + config_path = p.join("ffplayout.toml") }; } - let f = match File::open(&config_path) { + let mut file = match File::open(&config_path) { Ok(file) => file, Err(_) => { eprintln!( - "ffplayout.yml not found!\nPut \"ffplayout.yml\" in \"/etc/playout/\" or beside the executable!" + "ffplayout.toml not found!\nPut \"ffplayout.toml\" in \"/etc/playout/\" or beside the executable!" ); process::exit(1); } }; - let mut config: PlayoutConfig = - serde_yaml::from_reader(f).expect("Could not read config file."); + let mut contents = String::new(); + + if let Err(e) = file.read_to_string(&mut contents) { + eprintln!("Read config file: {e}") + }; + + let mut config: PlayoutConfig = toml_edit::de::from_str(&contents).unwrap(); if let Some(adv_path) = advanced_path { config.advanced = Some(AdvancedConfig::new(adv_path)) diff --git a/tests/Cargo.toml b/tests/Cargo.toml index f7b35ed5..39904956 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -23,11 +23,11 @@ regex = "1" reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -serde_yaml = "0.9" serial_test = "3.0" shlex = "1.1" simplelog = { version = "^0.12", features = ["paris"] } time = { version = "0.3", features = ["formatting", "macros"] } +toml_edit = {version ="0.22", features = ["serde"]} walkdir = "2" [[test]] diff --git a/tests/src/engine_cmd.rs b/tests/src/engine_cmd.rs index 582fa38f..9236b4ea 100644 --- a/tests/src/engine_cmd.rs +++ b/tests/src/engine_cmd.rs @@ -8,7 +8,7 @@ use ffplayout_lib::{ #[test] fn video_audio_input() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); let player_control = PlayerControl::new(); let playout_stat = PlayoutStatus::new(); config.out.mode = Stream; @@ -37,7 +37,7 @@ fn video_audio_input() { #[test] fn video_audio_custom_filter1_input() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); let player_control = PlayerControl::new(); let playout_stat = PlayoutStatus::new(); config.out.mode = Stream; @@ -64,7 +64,7 @@ fn video_audio_custom_filter1_input() { #[test] fn video_audio_custom_filter2_input() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); let player_control = PlayerControl::new(); let playout_stat = PlayoutStatus::new(); config.out.mode = Stream; @@ -93,7 +93,7 @@ fn video_audio_custom_filter2_input() { #[test] fn video_audio_custom_filter3_input() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); let player_control = PlayerControl::new(); let playout_stat = PlayoutStatus::new(); config.out.mode = Stream; @@ -121,7 +121,7 @@ fn video_audio_custom_filter3_input() { #[test] fn dual_audio_aevalsrc_input() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); let player_control = PlayerControl::new(); let playout_stat = PlayoutStatus::new(); config.out.mode = Stream; @@ -149,7 +149,7 @@ fn dual_audio_aevalsrc_input() { #[test] fn dual_audio_input() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); let player_control = PlayerControl::new(); let playout_stat = PlayoutStatus::new(); config.out.mode = Stream; @@ -176,7 +176,7 @@ fn dual_audio_input() { #[test] fn video_separate_audio_input() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); let player_control = PlayerControl::new(); let playout_stat = PlayoutStatus::new(); config.out.mode = Stream; @@ -213,7 +213,7 @@ fn video_separate_audio_input() { #[test] fn video_audio_stream() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); config.out.mode = Stream; config.processing.add_logo = false; config.out.output_cmd = Some(vec_strings![ @@ -272,7 +272,7 @@ fn video_audio_stream() { #[test] fn video_audio_filter1_stream() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); config.out.mode = Stream; config.processing.add_logo = false; config.text.add_text = false; @@ -347,7 +347,7 @@ fn video_audio_filter1_stream() { #[test] fn video_audio_filter2_stream() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); config.out.mode = Stream; config.processing.add_logo = false; config.text.add_text = true; @@ -430,7 +430,7 @@ fn video_audio_filter2_stream() { #[test] fn video_audio_filter3_stream() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); config.out.mode = Stream; config.processing.add_logo = false; config.text.add_text = true; @@ -516,7 +516,7 @@ fn video_audio_filter3_stream() { #[test] fn video_audio_filter4_stream() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); config.out.mode = Stream; config.processing.add_logo = false; config.text.add_text = true; @@ -602,7 +602,7 @@ fn video_audio_filter4_stream() { #[test] fn video_dual_audio_stream() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); config.out.mode = Stream; config.processing.add_logo = false; config.processing.audio_tracks = 2; @@ -673,7 +673,7 @@ fn video_dual_audio_stream() { #[test] fn video_dual_audio_filter_stream() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); config.out.mode = Stream; config.processing.add_logo = false; config.processing.audio_tracks = 2; @@ -753,7 +753,7 @@ fn video_dual_audio_filter_stream() { #[test] fn video_audio_multi_stream() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); config.out.mode = Stream; config.processing.add_logo = false; config.out.output_cmd = Some(vec_strings![ @@ -842,7 +842,7 @@ fn video_audio_multi_stream() { #[test] fn video_dual_audio_multi_stream() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); config.out.mode = Stream; config.processing.add_logo = false; config.processing.audio_tracks = 2; @@ -956,7 +956,7 @@ fn video_dual_audio_multi_stream() { #[test] fn video_audio_text_multi_stream() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); config.out.mode = Stream; config.processing.add_logo = false; config.text.add_text = true; @@ -1069,7 +1069,7 @@ fn video_audio_text_multi_stream() { #[test] fn video_dual_audio_multi_filter_stream() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); config.out.mode = Stream; config.processing.add_logo = false; config.processing.audio_tracks = 2; @@ -1198,7 +1198,7 @@ fn video_dual_audio_multi_filter_stream() { #[test] fn video_audio_text_filter_stream() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); config.out.mode = Stream; config.processing.add_logo = false; config.processing.audio_tracks = 1; @@ -1320,7 +1320,7 @@ fn video_audio_text_filter_stream() { #[test] fn video_audio_hls() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); let player_control = PlayerControl::new(); let playout_stat = PlayoutStatus::new(); config.out.mode = HLS; @@ -1407,7 +1407,7 @@ fn video_audio_hls() { #[test] fn video_audio_sub_meta_hls() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); let player_control = PlayerControl::new(); let playout_stat = PlayoutStatus::new(); config.out.mode = HLS; @@ -1502,7 +1502,7 @@ fn video_audio_sub_meta_hls() { #[test] fn video_multi_audio_hls() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); let player_control = PlayerControl::new(); let playout_stat = PlayoutStatus::new(); config.out.mode = HLS; @@ -1592,7 +1592,7 @@ fn video_multi_audio_hls() { #[test] fn multi_video_audio_hls() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); let player_control = PlayerControl::new(); let playout_stat = PlayoutStatus::new(); config.out.mode = HLS; @@ -1707,7 +1707,7 @@ fn multi_video_audio_hls() { #[test] fn multi_video_multi_audio_hls() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); let player_control = PlayerControl::new(); let playout_stat = PlayoutStatus::new(); config.out.mode = HLS; diff --git a/tests/src/lib_utils.rs b/tests/src/lib_utils.rs index 139d5521..d3eb6e90 100644 --- a/tests/src/lib_utils.rs +++ b/tests/src/lib_utils.rs @@ -40,7 +40,7 @@ fn get_date_tomorrow() { #[test] fn test_delta() { - let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.yml")), None); + let mut config = PlayoutConfig::new(Some(PathBuf::from("../assets/ffplayout.toml")), None); config.mail.recipient = "".into(); config.processing.mode = Playlist; config.playlist.day_start = "00:00:00".into();