Merge pull request #629 from jb-alvarado/master

switch to toml, update frontend, make title optional
This commit is contained in:
jb-alvarado 2024-05-02 05:57:36 +00:00 committed by GitHub
commit d41a485f40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 359 additions and 311 deletions

View File

@ -20,6 +20,7 @@
"cSpell.words": [
"actix",
"rsplit",
"starttls",
"tokio",
"uuids"
]

65
Cargo.lock generated
View File

@ -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"

37
assets/advanced.toml Normal file
View File

@ -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 = ""

View File

@ -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:

168
assets/ffplayout.toml Normal file
View File

@ -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"""

View File

@ -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

View File

@ -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]

View File

@ -388,7 +388,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"
@ -426,7 +426,7 @@ async fn get_all_channels(pool: web::Data<Pool<Sqlite>>) -> Result<impl Responde
///
/// ```BASH
/// curl -X PATCH http://127.0.0.1:8787/api/channel/1 -H "Content-Type: application/json" \
/// -d '{ "id": 1, "name": "Channel 1", "preview_url": "http://localhost/live/stream.m3u8", "config_path": "/etc/ffplayout/ffplayout.yml", "extra_extensions": "jpg,jpeg,png"}' \
/// -d '{ "id": 1, "name": "Channel 1", "preview_url": "http://localhost/live/stream.m3u8", "config_path": "/etc/ffplayout/ffplayout.toml", "extra_extensions": "jpg,jpeg,png"}' \
/// -H "Authorization: Bearer <TOKEN>"
/// ```
#[patch("/channel/{id}")]
@ -450,7 +450,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 <TOKEN>"
/// ```
#[post("/channel/")]
@ -491,7 +491,7 @@ async fn remove_channel(
/// curl -X GET http://127.0.0.1:8787/api/playout/config/1 -H 'Authorization: Bearer <TOKEN>'
/// ```
///
/// 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(
@ -522,17 +522,10 @@ async fn update_playout_config(
data: web::Json<PlayoutConfig>,
) -> Result<impl Responder, ServiceError> {
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)

View File

@ -99,9 +99,9 @@ pub async fn db_init(domain: Option<String>) -> Result<&'static str, Box<dyn std
};
let config_path = if env::consts::OS == "linux" {
"/etc/ffplayout/ffplayout.yml"
"/etc/ffplayout/ffplayout.toml"
} else {
"./assets/ffplayout.yml"
"./assets/ffplayout.toml"
};
let query = "CREATE TRIGGER global_row_count

View File

@ -33,7 +33,7 @@ pub async fn create_channel(
};
let mut config = PlayoutConfig::new(
Some(PathBuf::from("/usr/share/ffplayout/ffplayout.yml.orig")),
Some(PathBuf::from("/usr/share/ffplayout/ffplayout.toml.orig")),
None,
);
@ -48,8 +48,8 @@ pub async fn create_channel(
.replace("stream.m3u8", &format!("stream{channel_num}.m3u8"))
.replace("stream-%d.ts", &format!("stream{channel_num}-%d.ts"));
let file = fs::File::create(&target_channel.config_path)?;
serde_yaml::to_writer(file, &config).unwrap();
let toml_string = toml_edit::ser::to_string(&config)?;
fs::write(&target_channel.config_path, toml_string)?;
let new_channel = handles::insert_channel(conn, target_channel).await?;
control_service(conn, &config, new_channel.id, &ServiceCmd::Enable, None).await?;

View File

@ -88,6 +88,12 @@ impl From<tokio::task::JoinError> for ServiceError {
}
}
impl From<toml_edit::ser::Error> for ServiceError {
fn from(err: toml_edit::ser::Error) -> ServiceError {
ServiceError::BadRequest(err.to_string())
}
}
impl From<uuid::Error> for ServiceError {
fn from(err: uuid::Error) -> ServiceError {
ServiceError::BadRequest(err.to_string())

View File

@ -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<PlayoutConfig, Box<dyn Error>> {
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}"),
}
}

View File

@ -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" },
]

View File

@ -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<PathBuf>,
#[clap(index = 1, value_parser, help = "Channel name")]
pub channel: Option<String>,
#[clap(short, long, help = "File path to ffplayout.yml")]
#[clap(short, long, help = "File path to ffplayout.toml")]
pub config: Option<PathBuf>,
#[clap(short, long, help = "File path for logging")]

View File

@ -25,7 +25,7 @@ use ffplayout_lib::{
pub fn get_config(args: Args) -> Result<PlayoutConfig, ProcError> {
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<PlayoutConfig, ProcError> {
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")
};
}
@ -251,14 +251,21 @@ pub fn prepare_output_cmd(
/// map media struct to json object
pub fn get_media_map(media: Media) -> Value {
json!({
"title": media.title,
let mut obj = json!({
"in": media.seek,
"out": media.out,
"duration": media.duration,
"category": media.category,
"source": media.source,
})
});
if let Some(title) = media.title {
obj.as_object_mut()
.unwrap()
.insert("title".to_string(), Value::String(title));
}
obj
}
/// prepare json object for response

@ -1 +1 @@
Subproject commit 6111c2686d14b3bf33a4c0b29c85672f7e4f4399
Subproject commit 05d0ada8e155a9bf858a26fcda219fb39782cf05

View File

@ -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]

View File

@ -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<String>,
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 {

View File

@ -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<PathBuf>, advanced_path: Option<PathBuf>) -> 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))

View File

@ -65,7 +65,7 @@ pub struct Media {
#[serde(skip_serializing, skip_deserializing)]
pub index: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(rename = "in")]
pub seek: f64,

View File

@ -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]]

View File

@ -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;

View File

@ -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();