0.24.0-beta5

- reorganize help
- switch back to beta version
- add docs for closed cations
- unify playlist/playlist-root argument
- colorize ads
- add vtt_* to update config handler
- update packages
- add -muxpreload 0 and -muxdelay 0 to default settings
This commit is contained in:
Jonathan Baecker 2024-09-27 11:34:52 +02:00
parent 7af0d9a352
commit 7bfaa4a2d0
11 changed files with 119 additions and 129 deletions

106
Cargo.lock generated
View File

@ -549,9 +549,9 @@ dependencies = [
[[package]]
name = "async-trait"
version = "0.1.82"
version = "0.1.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1"
checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
dependencies = [
"proc-macro2",
"quote",
@ -582,9 +582,9 @@ dependencies = [
[[package]]
name = "autocfg"
version = "1.3.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "awc"
@ -793,9 +793,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.17"
version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac"
checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3"
dependencies = [
"clap_builder",
"clap_derive",
@ -803,9 +803,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.17"
version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73"
checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b"
dependencies = [
"anstream",
"anstyle",
@ -815,9 +815,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.13"
version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
dependencies = [
"heck",
"proc-macro2",
@ -1215,7 +1215,7 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
[[package]]
name = "ffplayout"
version = "0.24.0-rc1"
version = "0.24.0-beta5"
dependencies = [
"actix-files",
"actix-multipart",
@ -1314,9 +1314,9 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.0.33"
version = "1.0.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253"
checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0"
dependencies = [
"crc32fast",
"miniz_oxide",
@ -1697,9 +1697,9 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.8"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba"
checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b"
dependencies = [
"bytes",
"futures-channel",
@ -1710,7 +1710,6 @@ dependencies = [
"pin-project-lite",
"socket2",
"tokio",
"tower",
"tower-service",
"tracing",
]
@ -2068,9 +2067,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.158"
version = "0.2.159"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
[[package]]
name = "libm"
@ -2520,26 +2519,6 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pin-project"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.77",
]
[[package]]
name = "pin-project-lite"
version = "0.2.14"
@ -2575,9 +2554,9 @@ dependencies = [
[[package]]
name = "pkg-config"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]]
name = "powerfmt"
@ -2739,9 +2718,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.5.4"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853"
checksum = "355ae415ccd3a04315d3f8246e86d67689ea74d88d915576e1589a351062a13b"
dependencies = [
"bitflags 2.6.0",
]
@ -3089,9 +3068,9 @@ dependencies = [
[[package]]
name = "serde_spanned"
version = "0.6.7"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
dependencies = [
"serde",
]
@ -3681,7 +3660,7 @@ dependencies = [
[[package]]
name = "tests"
version = "0.24.0-rc1"
version = "0.24.0-beta5"
dependencies = [
"actix-rt",
"actix-test",
@ -3711,18 +3690,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.63"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.63"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
dependencies = [
"proc-macro2",
"quote",
@ -3861,9 +3840,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.22.21"
version = "0.22.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [
"indexmap 2.5.0",
"serde",
@ -3872,27 +3851,6 @@ dependencies = [
"winnow",
]
[[package]]
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
"futures-core",
"futures-util",
"pin-project",
"pin-project-lite",
"tokio",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-layer"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
[[package]]
name = "tower-service"
version = "0.3.3"
@ -4495,9 +4453,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.6.18"
version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
dependencies = [
"memchr",
]

View File

@ -3,7 +3,7 @@ members = ["engine", "tests"]
resolver = "2"
[workspace.package]
version = "0.24.0-rc1"
version = "0.24.0-beta5"
license = "GPL-3.0"
repository = "https://github.com/ffplayout/ffplayout"
authors = ["Jonathan Baecker <jonbae77@gmail.com>"]

View File

@ -3,7 +3,7 @@
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
## **ffplayout-engine (ffplayout)**
![player](/docs/images/player.png)
[ffplayout](/ffplayout-engine/README.md) is a 24/7 broadcasting solution. It can playout a folder containing audio or video clips, or play a *JSON* playlist for each day, keeping the current playlist editable.

23
docs/closed_captions.md Normal file
View File

@ -0,0 +1,23 @@
## Closed Captions
#### Note:
**This is only an _experimental feature_. Please be aware that bugs and unexpected behavior may occur. To utilize this feature, a [special patched](https://github.com/jb-alvarado/compile-ffmpeg-osx-linux) version of FFmpeg is required. Importantly, there is currently no official support for this functionality.**
### Usage
**ffplayout** can handle closed captions in WebVTT format for HLS streaming.
The captions can be embedded in the file, such as in a [Matroska](https://www.matroska.org/technical/subtitles.html) file, or they can be a separate *.vtt file that shares the same filename as the video file. In either case, the processing option **vtt_enable** must be enabled, and the path to the **vtt_dummy** file must exist.
To encode the closed captions, the **hls** mode needs to be enabled, and specific output parameters must be provided. Heres an example:
```
-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 \
-muxpreload 0 -muxdelay 0 -f hls -hls_time 6 -hls_list_size 600 \
-hls_flags append_list+delete_segments+omit_endlist \
-var_stream_map v:0,a:0,s:0,sgroup:subs,name:English,language:en-US,default:YES \
-master_pl_name master.m3u8 \
-hls_segment_filename \
live/stream-%d.ts live/stream.m3u8
```

View File

@ -211,7 +211,7 @@ pub async fn update_configuration(
id: i32,
config: PlayoutConfig,
) -> Result<SqliteQueryResult, sqlx::Error> {
let query = "UPDATE configurations SET general_stop_threshold = $2, mail_subject = $3, mail_smtp = $4, mail_addr = $5, mail_pass = $6, mail_recipient = $7, mail_starttls = $8, mail_level = $9, mail_interval = $10, logging_ffmpeg_level = $11, logging_ingest_level = $12, logging_detect_silence = $13, logging_ignore = $14, processing_mode = $15, processing_audio_only = $16, processing_copy_audio = $17, processing_copy_video = $18, processing_width = $19, processing_height = $20, processing_aspect = $21, processing_fps = $22, processing_add_logo = $23, processing_logo = $24, processing_logo_scale = $25, processing_logo_opacity = $26, processing_logo_position = $27, processing_audio_tracks = $28, processing_audio_track_index = $29, processing_audio_channels = $30, processing_volume = $31, processing_filter = $32, ingest_enable = $33, ingest_param = $34, ingest_filter = $35, playlist_day_start = $36, playlist_length = $37, playlist_infinit = $38, storage_filler = $39, storage_extensions = $40, storage_shuffle = $41, text_add = $42, text_from_filename = $43, text_font = $44, text_style = $45, text_regex = $46, task_enable = $47, task_path = $48, output_mode = $49, output_param = $50 WHERE id = $1";
let query = "UPDATE configurations SET general_stop_threshold = $2, mail_subject = $3, mail_smtp = $4, mail_addr = $5, mail_pass = $6, mail_recipient = $7, mail_starttls = $8, mail_level = $9, mail_interval = $10, logging_ffmpeg_level = $11, logging_ingest_level = $12, logging_detect_silence = $13, logging_ignore = $14, processing_mode = $15, processing_audio_only = $16, processing_copy_audio = $17, processing_copy_video = $18, processing_width = $19, processing_height = $20, processing_aspect = $21, processing_fps = $22, processing_add_logo = $23, processing_logo = $24, processing_logo_scale = $25, processing_logo_opacity = $26, processing_logo_position = $27, processing_audio_tracks = $28, processing_audio_track_index = $29, processing_audio_channels = $30, processing_volume = $31, processing_filter = $32, processing_vtt_enable = $33, processing_vtt_dummy = $34, ingest_enable = $35, ingest_param = $36, ingest_filter = $37, playlist_day_start = $38, playlist_length = $39, playlist_infinit = $40, storage_filler = $41, storage_extensions = $42, storage_shuffle = $43, text_add = $44, text_from_filename = $45, text_font = $46, text_style = $47, text_regex = $48, task_enable = $49, task_path = $50, output_mode = $51, output_param = $52 WHERE id = $1";
sqlx::query(query)
.bind(id)
@ -246,6 +246,8 @@ pub async fn update_configuration(
.bind(config.processing.audio_channels)
.bind(config.processing.volume)
.bind(config.processing.custom_filter)
.bind(config.processing.vtt_enable)
.bind(config.processing.vtt_dummy)
.bind(config.ingest.enable)
.bind(config.ingest.input_param)
.bind(config.ingest.custom_filter)

View File

@ -625,6 +625,10 @@ pub fn loop_image(config: &PlayoutConfig, node: &Media) -> Vec<String> {
.storage_path
.join(config.processing.vtt_dummy.clone().unwrap_or_default());
if node.seek > 0.5 {
source_cmd.append(&mut vec_strings!["-ss", node.seek]);
}
if vtt_file.is_file() {
source_cmd.append(&mut vec_strings![
"-i",
@ -732,6 +736,10 @@ pub fn seek_and_length(config: &PlayoutConfig, node: &mut Media) -> Vec<String>
.storage_path
.join(config.processing.vtt_dummy.clone().unwrap_or_default());
if node.seek > 0.5 {
source_cmd.append(&mut vec_strings!["-ss", node.seek]);
}
if vtt_file.is_file() {
if loop_count > 1 {
source_cmd.append(&mut vec_strings!["-stream_loop", loop_count]);

View File

@ -30,7 +30,12 @@ use crate::utils::db_path;
#[derive(Parser, Debug, Clone)]
#[clap(version,
about = "ffplayout - 24/7 broadcasting solution",
long_about = None)]
long_about = Some("ffplayout - 24/7 broadcasting solution\n
Stream dynamic playlists or folder contents with the power of ffmpeg.
The target can be an HLS playlist, rtmp/srt/udp server, desktop player
or any other output supported by ffmpeg.\n
ffplayout also provides a web frontend and API to control streaming,
manage config, files, text overlay, etc. "))]
pub struct Args {
#[clap(
short,
@ -43,20 +48,6 @@ pub struct Args {
#[clap(short, long, help_heading = Some("Initial Setup"), help = "Add a global admin user")]
pub add: bool,
#[clap(long, env, help_heading = Some("Initial Setup"), help = "Playlist root path")]
pub playlist_root: Option<String>,
#[clap(long, env, help_heading = Some("Initial Setup"), help = "Storage root path")]
pub storage_root: Option<String>,
#[clap(
long,
env,
help_heading = Some("Initial Setup"),
help = "Share storage root across channels, important for running in Container"
)]
pub shared_storage: bool,
#[clap(short, long, help_heading = Some("Initial Setup"), help = "Create admin user")]
pub username: Option<String>,
@ -66,18 +57,31 @@ pub struct Args {
#[clap(short, long, help_heading = Some("Initial Setup"), help = "Admin password")]
pub password: Option<String>,
#[clap(long, env, help_heading = Some("Initial Setup"), help = "Logging path")]
#[clap(long, env, help_heading = Some("Initial Setup"), help = "Storage root path")]
pub storage: Option<String>,
#[clap(
long,
env,
help_heading = Some("Initial Setup"),
help = "Share storage across channels, important for running in Containers"
)]
pub shared_storage: bool,
#[clap(long, env, help_heading = Some("Initial Setup / General"), help = "Logging path")]
pub log_path: Option<PathBuf>,
#[clap(long, env, help_heading = Some("Initial Setup"), help = "Path to public files, also HLS playlists")]
#[clap(long, env, help_heading = Some("Initial Setup / General"), help = "Path to public files, also HLS playlists")]
pub public: Option<String>,
#[clap(long, help_heading = Some("Initial Setup / Playlist"), help = "Path to playlist, or playlist root folder.")]
pub playlist: Option<String>,
#[clap(long, env, help_heading = Some("General"), help = "Path to database file")]
pub db: Option<PathBuf>,
#[clap(
long,
env,
help_heading = Some("General"),
help = "Drop database. WARNING: this will delete all configurations!"
)]
@ -110,6 +114,27 @@ pub struct Args {
#[clap(short, env, long, help_heading = Some("General"), help = "Listen on IP:PORT, like: 127.0.0.1:8787")]
pub listen: Option<String>,
#[clap(
long,
env,
help_heading = Some("General"),
help = "Override logging level: trace, debug, println, warn, eprintln"
)]
pub log_level: Option<String>,
#[clap(long, env, help_heading = Some("General"), help = "Log to console")]
pub log_to_console: bool,
#[clap(
short,
long,
env,
help_heading = Some("General / Playout"),
help = "Channels by ids to process (for export config, foreground running, etc.)",
num_args = 1..,
)]
pub channels: Option<Vec<i32>>,
#[clap(
short,
long,
@ -123,9 +148,6 @@ pub struct Args {
#[clap(long, help_heading = Some("Playlist"), help = "Optional path list for playlist generations", num_args = 1..)]
pub paths: Option<Vec<PathBuf>>,
#[clap(long, help_heading = Some("Playlist"), help = "Path to playlist, or playlist root folder.")]
pub playlist: Option<PathBuf>,
#[clap(
short,
long,
@ -134,22 +156,12 @@ pub struct Args {
)]
pub start: Option<String>,
#[clap(short = 'T', long, help_heading = Some("Playlist"), help = "JSON Template file for generating playlist")]
#[clap(short = 'T', long, help_heading = Some("Playlist"), help = "JSON template file for generating playlist")]
pub template: Option<PathBuf>,
#[clap(long, help_heading = Some("Playlist"), help = "Only validate given playlist")]
pub validate: bool,
#[clap(
short,
long,
env,
help_heading = Some("Playout"),
help = "Channels by ids to process (for foreground, etc.)",
num_args = 1..,
)]
pub channels: Option<Vec<i32>>,
#[clap(long, env, help_heading = Some("Playout"), help = "Run playout without webserver and frontend.")]
pub foreground: bool,
@ -159,17 +171,6 @@ pub struct Args {
#[clap(long, env, help_heading = Some("Playout"), help = "Keep log file for given days")]
pub log_backup_count: Option<usize>,
#[clap(
long,
env,
help_heading = Some("Playout"),
help = "Override logging level: trace, debug, println, warn, eprintln"
)]
pub log_level: Option<String>,
#[clap(long, env, help_heading = Some("Playout"), help = "Log to console")]
pub log_to_console: bool,
#[clap(short, long, help_heading = Some("Playout"), help = "Set output mode: desktop, hls, null, stream")]
pub output: Option<OutputMode>,
@ -393,8 +394,8 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
}
if !args.init
&& args.storage_root.is_some()
&& args.playlist_root.is_some()
&& args.storage.is_some()
&& args.playlist.is_some()
&& args.public.is_some()
&& args.log_path.is_some()
{
@ -404,9 +405,9 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
id: 0,
secret: None,
logging_path: args.log_path.unwrap().to_string_lossy().to_string(),
playlist_root: args.playlist_root.unwrap(),
playlist_root: args.playlist.unwrap(),
public_root: args.public.unwrap(),
storage_root: args.storage_root.unwrap(),
storage_root: args.storage.unwrap(),
shared_storage: args.shared_storage,
};

View File

@ -851,7 +851,7 @@ pub async fn get_config(
}
if let Some(playlist) = args.playlist {
config.channel.playlist_path = playlist;
config.channel.playlist_path = PathBuf::from(&playlist);
}
if let Some(folder) = args.folder {

View File

@ -15,12 +15,10 @@
class="form-control w-full"
:class="[typeof prop === 'boolean' && 'flex-row', name.toString() !== 'help_text' && 'mt-2']"
>
<!-- TODO: vtt_ check is temporary, needs to be removed when is done implemented -->
<template
v-if="
name.toString() !== 'startInSec' &&
name.toString() !== 'lengthInSec' &&
!name.startsWith('vtt_') &&
!(name.toString() === 'path' && key.toString() === 'storage')
"
>

View File

@ -78,7 +78,7 @@
'!bg-lime-500/30':
playlistStore.playoutIsRunning && listDate === todayDate && index === currentIndex,
'!bg-amber-600/40': element.overtime,
'text-base-content/50': element.category === 'advertisement',
'text-blue-300': element.category === 'advertisement',
}"
>
<td v-if="!configStore.playout.playlist.infinit" class="ps-4 py-2 text-left">

View File

@ -138,7 +138,7 @@ CREATE TABLE
task_path TEXT NOT NULL DEFAULT "",
output_help TEXT NOT NULL DEFAULT "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.\nIn production don't serve hls playlist with ffplayout, use nginx or another web server!",
output_mode TEXT NOT NULL DEFAULT "hls",
output_param TEXT NOT NULL DEFAULT "-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 live/stream-%d.ts live/stream.m3u8",
output_param TEXT NOT NULL DEFAULT "-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 -muxpreload 0 -muxdelay 0 -f hls -hls_time 6 -hls_list_size 600 -hls_flags append_list+delete_segments+omit_endlist -hls_segment_filename live/stream-%d.ts live/stream.m3u8",
FOREIGN KEY (channel_id) REFERENCES channels (id) ON UPDATE CASCADE ON DELETE CASCADE
);