From 7efe718f6aa93a02e3f92d7fa609814cba60ff6c Mon Sep 17 00:00:00 2001 From: mp3butcher Date: Fri, 1 Dec 2023 10:08:43 +0100 Subject: [PATCH 01/26] hls support first commit --- legacy/application/configs/conf.php | 39 +++++++++++- legacy/application/models/StreamSetting.php | 15 +++-- .../liquidsoap/templates/outputs.liq.j2 | 62 +++++++++++++++++++ shared/libretime_shared/config/__init__.py | 1 + shared/libretime_shared/config/_models.py | 26 ++++++++ 5 files changed, 137 insertions(+), 6 deletions(-) diff --git a/legacy/application/configs/conf.php b/legacy/application/configs/conf.php index 42fa9002a8..1b681785db 100644 --- a/legacy/application/configs/conf.php +++ b/legacy/application/configs/conf.php @@ -203,7 +203,43 @@ public function getConfigTreeBuilder() /* */->scalarNode('genre')->end() /* */->booleanNode('mobile')->defaultFalse()->end() /**/->end()->end()->end() + // Hls outputs + /**/->arrayNode('hls')->arrayPrototype()->children() + /* */->arrayNode('streams')->arrayPrototype()->children() + /* */->scalarNode('fragment_prefix')->end() + /* */->scalarNode('bitrate')->isRequired()->end() + /* */->IntegerNode('sample_rate')->defaultValue(44100)->end() + /* */->scalarNode('codec')->cannotBeEmpty() + /* */->validate()->ifNotInArray(['aac', 'libmp3lame', 'flac', 'libopus', 'libvorbis']) + /* */->thenInvalid('invalid stream.outputs.hls.streams.codec %s') + /* */->end() + /* */->end() + /* */->end()->end()->end() + /* */->scalarNode('format')->cannotBeEmpty() + /* */->validate()->ifNotInArray(['mpegts', 'mp3', 'adts', 'mp4']) + /* */->thenInvalid('invalid stream.outputs.hls.format %s') + /* */->end() + /* */->end() + /* */->floatNode('segment_duration')->defaultValue(2.0)->end() + /* */->integerNode('segment_count')->defaultValue(5)->end() + /* */->integerNode('segments_overhead')->defaultValue(5)->end() + /* */->booleanNode('enabled')->defaultFalse()->end() + + /* */->enumNode('kind')->values(['hls'])->defaultValue('hls')->end() + /* */->scalarNode('public_url')->end() + /* */->scalarNode('host')->defaultValue('localhost')->end() + /* */->integerNode('port')->defaultValue(80)->end() + /* */->scalarNode('mount')->cannotBeEmpty() + /* */->validate()->ifString()->then($trim_leading_slash)->end() + /* */->end() + + /* */->scalarNode('name')->end() + /* */->scalarNode('description')->end() + /* */->scalarNode('website')->end() + /* */->scalarNode('genre')->end() + /* */->booleanNode('mobile')->defaultFalse()->end() + /**/->end()->end()->end() // System outputs /**/->arrayNode('system')->arrayPrototype()->children() /* */->booleanNode('enabled')->defaultFalse()->end() @@ -270,7 +306,8 @@ private static function load() // Merge Icecast and Shoutcast outputs $values['stream']['outputs']['merged'] = array_merge( $values['stream']['outputs']['icecast'], - $values['stream']['outputs']['shoutcast'] + $values['stream']['outputs']['shoutcast'], + $values['stream']['outputs']['hls'] ); self::$values = $values; diff --git a/legacy/application/models/StreamSetting.php b/legacy/application/models/StreamSetting.php index 04e322d16f..e1588bec71 100644 --- a/legacy/application/models/StreamSetting.php +++ b/legacy/application/models/StreamSetting.php @@ -55,9 +55,6 @@ public static function getOutput($key, $add_prefix = false) $prefix . 'pass' => $output['source_password'] ?? '', $prefix . 'admin_user' => $output['admin_user'] ?? 'admin', $prefix . 'admin_pass' => $output['admin_password'] ?? '', - $prefix . 'channels' => $output['audio']['channels'] ?? 'stereo', - $prefix . 'bitrate' => $output['audio']['bitrate'] ?? 128, - $prefix . 'type' => $output['audio']['format'], $prefix . 'name' => $output['name'] ?? '', $prefix . 'description' => $output['description'] ?? '', $prefix . 'genre' => $output['genre'] ?? '', @@ -65,6 +62,14 @@ public static function getOutput($key, $add_prefix = false) $prefix . 'mobile' => $output['mobile'] ?? 'false', // $prefix . 'liquidsoap_error' => 'waiting', ]; + if (array_key_exists('audio',$output)) + { + $result = $result.merge([ + $prefix . 'channels' => $output['audio']['channels'] ?? 'stereo', + $prefix . 'bitrate' => $output['audio']['bitrate'] ?? 128, + $prefix . 'type' => $output['audio']['format'], + ]); + } } if (!$result[$prefix . 'public_url']) { @@ -108,8 +113,8 @@ public static function getEnabledStreamData() $prefix = $id . '_'; $streams[$id] = [ 'url' => $streamData[$prefix . 'public_url'], - 'codec' => $streamData[$prefix . 'type'], - 'bitrate' => $streamData[$prefix . 'bitrate'], + 'codec' => $streamData[$prefix . 'type'] ?? 'hls', + 'bitrate' => $streamData[$prefix . 'bitrate'] ?? '', 'mobile' => $streamData[$prefix . 'mobile'], ]; } diff --git a/playout/libretime_playout/liquidsoap/templates/outputs.liq.j2 b/playout/libretime_playout/liquidsoap/templates/outputs.liq.j2 index 06b21efc1a..04381df6e0 100644 --- a/playout/libretime_playout/liquidsoap/templates/outputs.liq.j2 +++ b/playout/libretime_playout/liquidsoap/templates/outputs.liq.j2 @@ -41,6 +41,61 @@ {%- endif -%} {%- endmacro -%} + +{#- + Build an hls output the output configuration. +#} +{%- macro output_hls(output_id, output) -%} +# hls:{{ output_id }} + +output_hls_{{ output_id }}_source = s + +{#- assume stereo output +{% if output.audio.channels == "stereo" -%} +{% else -%} +output_hls_{{ output_id }}_source = mean(s) +{% endif -%} +#} +{#- Add per output stream modifications. +{% if output.audio.format == "ogg" and not output.audio.enable_metadata -%} +# Disable ogg metadata +output_hls_{{ output_id }}_source = add(normalize=false, [amplify(0.00001, noise()), output_hls_{{ output_id }}_source]) +{% endif -%} +-#} + +streams = ref([]) + +{% for stream in output.streams -%} + + streamout = %ffmpeg(format="{{output.format}}" , + strict="-2", + %audio( codec="{{stream.codec}}", + channels=2, + ar={{stream.sample_rate}}, + b="{{stream.bitrate}}")) + streams := list.append([("{{stream.fragment_prefix}}",streamout)], !streams) + +{% endfor -%} + +def segment_name(~position,~extname,stream_name) = + timestamp = int_of_float(time()) + duration = 2 + "#{stream_name}_#{duration}_#{timestamp}_#{position}.#{extname}" +end + +argstream=!streams + +output.file.hls(playlist=input_main_mount, + segment_duration={{output.segment_duration}}, + segments={{output.segment_count}}, + segments_overhead={{output.segments_overhead}}, + segment_name=segment_name, + "/var/lib/libretime/playout/hls", + argstream, + output_hls_{{ output_id }}_source) + +{%- endmacro -%} + {#- Build an icecast output the output configuration. #} @@ -130,6 +185,13 @@ output.{{ output.kind.value }}(id="{{ output.kind.value }}:{{ loop.index }}", s) {% endif -%} {% endfor -%} +{% for output in config.stream.outputs.hls -%} +{% if output.enabled -%} +{{ output_hls(loop.index, output) }} + +{% endif -%} +{% endfor -%} + {% for output in config.stream.outputs.icecast -%} {% if output.enabled -%} {{ output_icecast(loop.index, output) }} diff --git a/shared/libretime_shared/config/__init__.py b/shared/libretime_shared/config/__init__.py index c4a4510fb9..eb82318ee2 100644 --- a/shared/libretime_shared/config/__init__.py +++ b/shared/libretime_shared/config/__init__.py @@ -5,6 +5,7 @@ DatabaseConfig, GeneralConfig, HarborInput, + HlsOutput, IcecastOutput, RabbitMQConfig, ShoutcastOutput, diff --git a/shared/libretime_shared/config/_models.py b/shared/libretime_shared/config/_models.py index 13e70927a2..c6713b8bee 100644 --- a/shared/libretime_shared/config/_models.py +++ b/shared/libretime_shared/config/_models.py @@ -197,6 +197,31 @@ class AudioOpus(BaseAudio): format: Literal[AudioFormat.OPUS] = AudioFormat.OPUS +class HlsStream(BaseModel): + fragment_prefix: str = "mp3_128" + bitrate: str = "128k" + sample_rate: int = 44100 + codec: str = "libmp3lame" + + +class HlsOutput(BaseModel): + kind: Literal["hls"] = "hls" + enabled: bool = False + public_url: Optional[AnyUrl] = None + host: str = "localhost" + port: int = 80 + format: str = "mpegts" + segment_duration: float = 2.0 + segment_count: int = 5 + segments_overhead: int = 5 + streams: List[HlsStream] = Field([], max_items=10) + mount: str + + mobile: bool = False + + _mount_no_leading_slash = no_leading_slash_validator("mount") + + class IcecastOutput(BaseModel): kind: Literal["icecast"] = "icecast" enabled: bool = False @@ -266,6 +291,7 @@ class SystemOutput(BaseModel): class Outputs(BaseModel): icecast: List[IcecastOutput] = Field([], max_items=3) shoutcast: List[ShoutcastOutput] = Field([], max_items=1) + hls: List[HlsOutput] = Field([], max_items=1) system: List[SystemOutput] = Field([], max_items=1) @property From 08b9d0e060314812190583ae01b7542a88a4af0b Mon Sep 17 00:00:00 2001 From: Julien Valentin Date: Fri, 1 Dec 2023 10:56:18 +0100 Subject: [PATCH 02/26] Update config.yml for hls config --- installer/config.yml | 49 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/installer/config.yml b/installer/config.yml index 69940446c6..1f769d5019 100644 --- a/installer/config.yml +++ b/installer/config.yml @@ -317,6 +317,55 @@ stream: # > default is false mobile: false + hls: + - # Whether the output is enabled. + # > default is false + enabled: true + # > must be one of ('mpegts', 'mp3', 'adts', 'mp4') + # > this field is REQUIRED + format: mpegts + # > segment_duration (default:2.0) + segment_duration: 2.0 + # > segments count (default:5) + segment_count: 5 + # > segments_overhead (default:5) + segments_overhead: 5 + # Output public url, If not defined, the value will be generated from + # the [general.public_url] hostname, the output port and mount. + public_url: + # web server host. + # > default is localhost + host: localhost + # webs server server port. + # > default is 80 + port: 80 + # hls mount point + # > this field is REQUIRED + mount: hls/main + streams: + - # > prefix of generated fragment + # > this field is REQUIRED + fragment_prefix: mp3_low + # > must be one of ('aac', 'libmp3lame', 'opus') + # > this field is REQUIRED + codec: libmp3lame + # > bitrate of the stream + # > this field is REQUIRED + bitrate: 32k + # > sampling rate (default: 44100Hz) + sample_rate: 44100 + - + fragment_prefix: mp3_med + codec: libmp3lame + bitrate: 128k + - + fragment_prefix: mp3_hifi + codec: libmp3lame + bitrate: 256k + # Whether the stream should be used for mobile devices. + # > default is false + mobile: false + # System outputs. # > max items is 1 system: From ed56081f53ed52f159ef9e918b93448b05f764e5 Mon Sep 17 00:00:00 2001 From: mp3butcher Date: Fri, 1 Dec 2023 11:33:28 +0100 Subject: [PATCH 03/26] remove useless fields --- legacy/application/configs/conf.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/legacy/application/configs/conf.php b/legacy/application/configs/conf.php index 1b681785db..f6bb878a49 100644 --- a/legacy/application/configs/conf.php +++ b/legacy/application/configs/conf.php @@ -203,6 +203,7 @@ public function getConfigTreeBuilder() /* */->scalarNode('genre')->end() /* */->booleanNode('mobile')->defaultFalse()->end() /**/->end()->end()->end() + // Hls outputs /**/->arrayNode('hls')->arrayPrototype()->children() /* */->arrayNode('streams')->arrayPrototype()->children() @@ -232,12 +233,6 @@ public function getConfigTreeBuilder() /* */->scalarNode('mount')->cannotBeEmpty() /* */->validate()->ifString()->then($trim_leading_slash)->end() /* */->end() - - - /* */->scalarNode('name')->end() - /* */->scalarNode('description')->end() - /* */->scalarNode('website')->end() - /* */->scalarNode('genre')->end() /* */->booleanNode('mobile')->defaultFalse()->end() /**/->end()->end()->end() // System outputs From 17f47caeb603c01801b915e05a26cdc1c2fe3c6d Mon Sep 17 00:00:00 2001 From: mp3butcher Date: Fri, 1 Dec 2023 12:15:14 +0100 Subject: [PATCH 04/26] CI compliance --- legacy/application/models/StreamSetting.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/legacy/application/models/StreamSetting.php b/legacy/application/models/StreamSetting.php index e1588bec71..aac371dd22 100644 --- a/legacy/application/models/StreamSetting.php +++ b/legacy/application/models/StreamSetting.php @@ -62,9 +62,8 @@ public static function getOutput($key, $add_prefix = false) $prefix . 'mobile' => $output['mobile'] ?? 'false', // $prefix . 'liquidsoap_error' => 'waiting', ]; - if (array_key_exists('audio',$output)) - { - $result = $result.merge([ + if (array_key_exists('audio',$output)) { + $result .= merge([ $prefix . 'channels' => $output['audio']['channels'] ?? 'stereo', $prefix . 'bitrate' => $output['audio']['bitrate'] ?? 128, $prefix . 'type' => $output['audio']['format'], From bc5050cd28fd692b93303fe1b9f697b625715ab7 Mon Sep 17 00:00:00 2001 From: Julien Valentin Date: Fri, 1 Dec 2023 12:16:45 +0100 Subject: [PATCH 05/26] Update install and config --- docker/config.template.yml | 47 +++++++++++++++++++++ docker/config.yml | 47 +++++++++++++++++++++ docker/example/config.yml | 47 +++++++++++++++++++++ install | 1 + installer/config.yml | 36 ++++++++-------- legacy/application/models/StreamSetting.php | 2 +- 6 files changed, 160 insertions(+), 20 deletions(-) diff --git a/docker/config.template.yml b/docker/config.template.yml index 5cb7027341..5a99400a29 100644 --- a/docker/config.template.yml +++ b/docker/config.template.yml @@ -317,6 +317,53 @@ stream: # > default is false mobile: false + hls: + - # Whether the output is enabled. + # > default is false + enabled: true + # > must be one of ('mpegts', 'mp3', 'adts', 'mp4') + # > this field is REQUIRED + format: mpegts + # > segment_duration (default:2.0) + segment_duration: 2.0 + # > segments count (default:5) + segment_count: 5 + # > segments_overhead (default:5) + segments_overhead: 5 + # Output public url, If not defined, the value will be generated from + # the [general.public_url] hostname, the output port and mount. + public_url: + # web server host. + # > default is localhost + host: localhost + # webs server server port. + # > default is 80 + port: 80 + # hls mount point + # > this field is REQUIRED + mount: hls/main + streams: + - # > prefix of generated fragment + # > this field is REQUIRED + fragment_prefix: mp3_low + # > must be one of ( 'libmp3lame', 'flac', 'aac', 'libopus', 'libvorbis') + # > this field is REQUIRED + codec: libmp3lame + # > bitrate of the stream + # > this field is REQUIRED + bitrate: 32k + # > sampling rate (default: 44100Hz) + sample_rate: 44100 + - fragment_prefix: mp3_med + codec: libmp3lame + bitrate: 128k + - fragment_prefix: mp3_hifi + codec: libmp3lame + bitrate: 256k + # Whether the stream should be used for mobile devices. + # > default is false + mobile: false + # System outputs. # > max items is 1 system: diff --git a/docker/config.yml b/docker/config.yml index 27892d0c58..3f423d9f85 100644 --- a/docker/config.yml +++ b/docker/config.yml @@ -317,6 +317,53 @@ stream: # > default is false mobile: false + hls: + - # Whether the output is enabled. + # > default is false + enabled: true + # > must be one of ('mpegts', 'mp3', 'adts', 'mp4') + # > this field is REQUIRED + format: mpegts + # > segment_duration (default:2.0) + segment_duration: 2.0 + # > segments count (default:5) + segment_count: 5 + # > segments_overhead (default:5) + segments_overhead: 5 + # Output public url, If not defined, the value will be generated from + # the [general.public_url] hostname, the output port and mount. + public_url: + # web server host. + # > default is localhost + host: localhost + # webs server server port. + # > default is 80 + port: 80 + # hls mount point + # > this field is REQUIRED + mount: hls/main + streams: + - # > prefix of generated fragment + # > this field is REQUIRED + fragment_prefix: mp3_low + # > must be one of ( 'libmp3lame', 'flac', 'aac', 'libopus', 'libvorbis') + # > this field is REQUIRED + codec: libmp3lame + # > bitrate of the stream + # > this field is REQUIRED + bitrate: 32k + # > sampling rate (default: 44100Hz) + sample_rate: 44100 + - fragment_prefix: mp3_med + codec: libmp3lame + bitrate: 128k + - fragment_prefix: mp3_hifi + codec: libmp3lame + bitrate: 256k + # Whether the stream should be used for mobile devices. + # > default is false + mobile: false + # System outputs. # > max items is 1 system: diff --git a/docker/example/config.yml b/docker/example/config.yml index f5020f1b3a..5ebd51254c 100644 --- a/docker/example/config.yml +++ b/docker/example/config.yml @@ -317,6 +317,53 @@ stream: # > default is false mobile: false + hls: + - # Whether the output is enabled. + # > default is false + enabled: true + # > must be one of ('mpegts', 'mp3', 'adts', 'mp4') + # > this field is REQUIRED + format: mpegts + # > segment_duration (default:2.0) + segment_duration: 2.0 + # > segments count (default:5) + segment_count: 5 + # > segments_overhead (default:5) + segments_overhead: 5 + # Output public url, If not defined, the value will be generated from + # the [general.public_url] hostname, the output port and mount. + public_url: + # web server host. + # > default is localhost + host: localhost + # webs server server port. + # > default is 80 + port: 80 + # hls mount point + # > this field is REQUIRED + mount: hls/main + streams: + - # > prefix of generated fragment + # > this field is REQUIRED + fragment_prefix: mp3_low + # > must be one of ( 'libmp3lame', 'flac', 'aac', 'libopus', 'libvorbis') + # > this field is REQUIRED + codec: libmp3lame + # > bitrate of the stream + # > this field is REQUIRED + bitrate: 32k + # > sampling rate (default: 44100Hz) + sample_rate: 44100 + - fragment_prefix: mp3_med + codec: libmp3lame + bitrate: 128k + - fragment_prefix: mp3_hifi + codec: libmp3lame + bitrate: 256k + # Whether the stream should be used for mobile devices. + # > default is false + mobile: false + # System outputs. # > max items is 1 system: diff --git a/install b/install index af9726981b..a199adb9f7 100755 --- a/install +++ b/install @@ -628,6 +628,7 @@ link_python_app libretime-playout-notify info "creating libretime-playout working directory" mkdir_and_chown "$LIBRETIME_USER" "$WORKING_DIR/playout" +mkdir_and_chown "$LIBRETIME_USER" "$WORKING_DIR/playout/hls" install_service "libretime-liquidsoap.service" "$SCRIPT_DIR/playout/install/systemd/libretime-liquidsoap.service" install_service "libretime-playout.service" "$SCRIPT_DIR/playout/install/systemd/libretime-playout.service" diff --git a/installer/config.yml b/installer/config.yml index 1f769d5019..294e9ab1ca 100644 --- a/installer/config.yml +++ b/installer/config.yml @@ -343,25 +343,23 @@ stream: # > this field is REQUIRED mount: hls/main streams: - - # > prefix of generated fragment - # > this field is REQUIRED - fragment_prefix: mp3_low - # > must be one of ('aac', 'libmp3lame', 'opus') - # > this field is REQUIRED - codec: libmp3lame - # > bitrate of the stream - # > this field is REQUIRED - bitrate: 32k - # > sampling rate (default: 44100Hz) - sample_rate: 44100 - - - fragment_prefix: mp3_med - codec: libmp3lame - bitrate: 128k - - - fragment_prefix: mp3_hifi - codec: libmp3lame - bitrate: 256k + - # > prefix of generated fragment + # > this field is REQUIRED + fragment_prefix: mp3_low + # > must be one of ( 'libmp3lame', 'flac', 'aac', 'libopus', 'libvorbis') + # > this field is REQUIRED + codec: libmp3lame + # > bitrate of the stream + # > this field is REQUIRED + bitrate: 32k + # > sampling rate (default: 44100Hz) + sample_rate: 44100 + - fragment_prefix: mp3_med + codec: libmp3lame + bitrate: 128k + - fragment_prefix: mp3_hifi + codec: libmp3lame + bitrate: 256k # Whether the stream should be used for mobile devices. # > default is false mobile: false diff --git a/legacy/application/models/StreamSetting.php b/legacy/application/models/StreamSetting.php index aac371dd22..7c411eaadf 100644 --- a/legacy/application/models/StreamSetting.php +++ b/legacy/application/models/StreamSetting.php @@ -62,7 +62,7 @@ public static function getOutput($key, $add_prefix = false) $prefix . 'mobile' => $output['mobile'] ?? 'false', // $prefix . 'liquidsoap_error' => 'waiting', ]; - if (array_key_exists('audio',$output)) { + if (array_key_exists('audio', $output)) { $result .= merge([ $prefix . 'channels' => $output['audio']['channels'] ?? 'stereo', $prefix . 'bitrate' => $output['audio']['bitrate'] ?? 128, From 3119454d42b3b2ade2952d7ba6b7d8b3e50f9c47 Mon Sep 17 00:00:00 2001 From: mp3butcher Date: Sat, 2 Dec 2023 18:08:39 +0100 Subject: [PATCH 06/26] make compatible with liquidsoap 1.4 output.file.hls prototype --- playout/libretime_playout/liquidsoap/1.4/ls_lib.liq | 4 ++++ .../liquidsoap/templates/outputs.liq.j2 | 13 +++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/playout/libretime_playout/liquidsoap/1.4/ls_lib.liq b/playout/libretime_playout/liquidsoap/1.4/ls_lib.liq index 9e55fcdae4..2042c0e37e 100644 --- a/playout/libretime_playout/liquidsoap/1.4/ls_lib.liq +++ b/playout/libretime_playout/liquidsoap/1.4/ls_lib.liq @@ -1,3 +1,7 @@ +def appendinfos(streaminfos, fragment_prefix, bandwidth, codec, container) + streaminfos := list.append([(fragment_prefix, (bandwidth, codec, container))], !streaminfos) +end + def gateway(args) command = "timeout --signal=KILL 45 libretime-playout-notify #{args} &" log(command) diff --git a/playout/libretime_playout/liquidsoap/templates/outputs.liq.j2 b/playout/libretime_playout/liquidsoap/templates/outputs.liq.j2 index 04381df6e0..367ac0782a 100644 --- a/playout/libretime_playout/liquidsoap/templates/outputs.liq.j2 +++ b/playout/libretime_playout/liquidsoap/templates/outputs.liq.j2 @@ -64,31 +64,36 @@ output_hls_{{ output_id }}_source = add(normalize=false, [amplify(0.00001, noise -#} streams = ref([]) +streaminfos = ref([]) {% for stream in output.streams -%} - streamout = %ffmpeg(format="{{output.format}}" , + streamout = %ffmpeg(format="{{output.format}}", strict="-2", %audio( codec="{{stream.codec}}", channels=2, ar={{stream.sample_rate}}, b="{{stream.bitrate}}")) streams := list.append([("{{stream.fragment_prefix}}",streamout)], !streams) + {# Append dummy infos (required arg for 1.4) player will find out#} + appendinfos(streaminfos, "{{stream.fragment_prefix}}", {{ loop.index }}*10000, "", "") {% endfor -%} -def segment_name(~position,~extname,stream_name) = +def segment_name(~position,~extname, stream_name) = timestamp = int_of_float(time()) - duration = 2 + duration = {{output.segment_duration}} "#{stream_name}_#{duration}_#{timestamp}_#{position}.#{extname}" end -argstream=!streams +argstream= !streams +arginfos= !streaminfos output.file.hls(playlist=input_main_mount, segment_duration={{output.segment_duration}}, segments={{output.segment_count}}, segments_overhead={{output.segments_overhead}}, + streams_info=arginfos, segment_name=segment_name, "/var/lib/libretime/playout/hls", argstream, From c62cb8fe772999845f027a33373b4fec895065b2 Mon Sep 17 00:00:00 2001 From: mp3butcher Date: Tue, 12 Dec 2023 16:19:27 +0100 Subject: [PATCH 07/26] liquidsoap 2.2 removed streaminfos parameter so crossversion hls pattern have to be modded --- .../liquidsoap/1.4/ls_lib.liq | 19 +++++++- .../liquidsoap/templates/outputs.liq.j2 | 47 +++---------------- 2 files changed, 24 insertions(+), 42 deletions(-) diff --git a/playout/libretime_playout/liquidsoap/1.4/ls_lib.liq b/playout/libretime_playout/liquidsoap/1.4/ls_lib.liq index 2042c0e37e..f13beb6dcb 100644 --- a/playout/libretime_playout/liquidsoap/1.4/ls_lib.liq +++ b/playout/libretime_playout/liquidsoap/1.4/ls_lib.liq @@ -1,5 +1,20 @@ -def appendinfos(streaminfos, fragment_prefix, bandwidth, codec, container) - streaminfos := list.append([(fragment_prefix, (bandwidth, codec, container))], !streaminfos) +def start_hls(input_main_mount, streamsref, servpath, segment_duration, segments, segments_overhead, segment_named, output_source) + streams_infos = ref([]) + #generate dummy stream infos does the trick for 1.4 + list.iter(fun(item) -> + streams_infos := list.append([(fst(item), (0,'',''))], !streams_infos ) + , streamsref()) + argstream = !streamsref + arginfos = !streams_infos + output.file.hls(playlist=input_main_mount, + segment_duration=segment_duration, + segments=segments, + segments_overhead=segments_overhead, + streams_info=arginfos, + segment_name=segment_named, + servpath, + argstream, + output_source ) end def gateway(args) diff --git a/playout/libretime_playout/liquidsoap/templates/outputs.liq.j2 b/playout/libretime_playout/liquidsoap/templates/outputs.liq.j2 index 367ac0782a..33551159c1 100644 --- a/playout/libretime_playout/liquidsoap/templates/outputs.liq.j2 +++ b/playout/libretime_playout/liquidsoap/templates/outputs.liq.j2 @@ -50,35 +50,7 @@ output_hls_{{ output_id }}_source = s -{#- assume stereo output -{% if output.audio.channels == "stereo" -%} -{% else -%} -output_hls_{{ output_id }}_source = mean(s) -{% endif -%} -#} -{#- Add per output stream modifications. -{% if output.audio.format == "ogg" and not output.audio.enable_metadata -%} -# Disable ogg metadata -output_hls_{{ output_id }}_source = add(normalize=false, [amplify(0.00001, noise()), output_hls_{{ output_id }}_source]) -{% endif -%} --#} - streams = ref([]) -streaminfos = ref([]) - -{% for stream in output.streams -%} - - streamout = %ffmpeg(format="{{output.format}}", - strict="-2", - %audio( codec="{{stream.codec}}", - channels=2, - ar={{stream.sample_rate}}, - b="{{stream.bitrate}}")) - streams := list.append([("{{stream.fragment_prefix}}",streamout)], !streams) - {# Append dummy infos (required arg for 1.4) player will find out#} - appendinfos(streaminfos, "{{stream.fragment_prefix}}", {{ loop.index }}*10000, "", "") - -{% endfor -%} def segment_name(~position,~extname, stream_name) = timestamp = int_of_float(time()) @@ -86,18 +58,13 @@ def segment_name(~position,~extname, stream_name) = "#{stream_name}_#{duration}_#{timestamp}_#{position}.#{extname}" end -argstream= !streams -arginfos= !streaminfos - -output.file.hls(playlist=input_main_mount, - segment_duration={{output.segment_duration}}, - segments={{output.segment_count}}, - segments_overhead={{output.segments_overhead}}, - streams_info=arginfos, - segment_name=segment_name, - "/var/lib/libretime/playout/hls", - argstream, - output_hls_{{ output_id }}_source) +start_hls(input_main_mount, streams, + "/var/lib/libretime/playout/hls", + {{output.segment_duration}}, + {{output.segment_count}}, + {{output.segments_overhead}}, + segment_name, + output_hls_{{ output_id }}_source) {%- endmacro -%} From 8f3bde7abd95431d3e4d84002c466194030f4720 Mon Sep 17 00:00:00 2001 From: mp3butcher Date: Tue, 12 Dec 2023 16:22:26 +0100 Subject: [PATCH 08/26] add an alias to playout hls output --- installer/nginx/libretime.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/installer/nginx/libretime.conf b/installer/nginx/libretime.conf index 1976f74a16..b4a398cd04 100644 --- a/installer/nginx/libretime.conf +++ b/installer/nginx/libretime.conf @@ -12,6 +12,10 @@ server { client_max_body_size 512M; client_body_timeout 300s; + location /hls { + alias /var/lib/libretime/playout/hls; + } + location ~ \.php$ { fastcgi_buffers 64 4K; fastcgi_split_path_info ^(.+\.php)(/.+)$; From 61aa1fb73d3bc6bd1d9aca056a23f72f8445dd78 Mon Sep 17 00:00:00 2001 From: mp3butcher Date: Tue, 12 Dec 2023 16:47:59 +0100 Subject: [PATCH 09/26] use SERVER_PORT for hls stream --- legacy/application/models/StreamSetting.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/legacy/application/models/StreamSetting.php b/legacy/application/models/StreamSetting.php index 7c411eaadf..7b640b5112 100644 --- a/legacy/application/models/StreamSetting.php +++ b/legacy/application/models/StreamSetting.php @@ -63,11 +63,16 @@ public static function getOutput($key, $add_prefix = false) // $prefix . 'liquidsoap_error' => 'waiting', ]; if (array_key_exists('audio', $output)) { - $result .= merge([ + $result = array_merge($result, [ $prefix . 'channels' => $output['audio']['channels'] ?? 'stereo', $prefix . 'bitrate' => $output['audio']['bitrate'] ?? 128, $prefix . 'type' => $output['audio']['format'], ]); + } else { + // assume HLS : set web server port + $result = array_merge($result, [ + $prefix . 'port' => $_SERVER['SERVER_PORT'], + ]); } } From 76010bcb86f74de75a30bfb9b074100a76ebf0e2 Mon Sep 17 00:00:00 2001 From: mp3butcher Date: Tue, 12 Dec 2023 23:41:01 +0100 Subject: [PATCH 10/26] liquidsoap 1.4 require differents streams bitrates for player to select --- playout/libretime_playout/liquidsoap/1.4/ls_lib.liq | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playout/libretime_playout/liquidsoap/1.4/ls_lib.liq b/playout/libretime_playout/liquidsoap/1.4/ls_lib.liq index f13beb6dcb..88dbe97ec4 100644 --- a/playout/libretime_playout/liquidsoap/1.4/ls_lib.liq +++ b/playout/libretime_playout/liquidsoap/1.4/ls_lib.liq @@ -2,7 +2,7 @@ def start_hls(input_main_mount, streamsref, servpath, segment_duration, segments streams_infos = ref([]) #generate dummy stream infos does the trick for 1.4 list.iter(fun(item) -> - streams_infos := list.append([(fst(item), (0,'',''))], !streams_infos ) + streams_infos := list.append([(fst(item), (list.length(!streams_infos)+1,'',''))], !streams_infos ) , streamsref()) argstream = !streamsref arginfos = !streams_infos From 45af9eda95a7a0cd90d61ba96eb6fb79b0d04332 Mon Sep 17 00:00:00 2001 From: mp3butcher Date: Wed, 13 Dec 2023 13:20:57 +0100 Subject: [PATCH 11/26] remove useless host port settings --- docker/config.template.yml | 10 ++-------- docker/config.yml | 10 ++-------- docker/example/config.yml | 10 ++-------- installer/config.yml | 10 ++-------- legacy/application/configs/conf.php | 3 --- legacy/application/models/StreamSetting.php | 5 +++-- shared/libretime_shared/config/_models.py | 2 -- 7 files changed, 11 insertions(+), 39 deletions(-) diff --git a/docker/config.template.yml b/docker/config.template.yml index 5a99400a29..cb4e21ab88 100644 --- a/docker/config.template.yml +++ b/docker/config.template.yml @@ -320,7 +320,7 @@ stream: hls: - # Whether the output is enabled. # > default is false - enabled: true + enabled: false # > must be one of ('mpegts', 'mp3', 'adts', 'mp4') # > this field is REQUIRED format: mpegts @@ -333,12 +333,6 @@ stream: # Output public url, If not defined, the value will be generated from # the [general.public_url] hostname, the output port and mount. public_url: - # web server host. - # > default is localhost - host: localhost - # webs server server port. - # > default is 80 - port: 80 # hls mount point # > this field is REQUIRED mount: hls/main @@ -346,7 +340,7 @@ stream: - # > prefix of generated fragment # > this field is REQUIRED fragment_prefix: mp3_low - # > must be one of ( 'libmp3lame', 'flac', 'aac', 'libopus', 'libvorbis') + # > codec must be one of ( 'libmp3lame', 'flac', 'aac', 'libopus', 'libvorbis') # > this field is REQUIRED codec: libmp3lame # > bitrate of the stream diff --git a/docker/config.yml b/docker/config.yml index 3f423d9f85..c1adac5f87 100644 --- a/docker/config.yml +++ b/docker/config.yml @@ -320,7 +320,7 @@ stream: hls: - # Whether the output is enabled. # > default is false - enabled: true + enabled: false # > must be one of ('mpegts', 'mp3', 'adts', 'mp4') # > this field is REQUIRED format: mpegts @@ -333,12 +333,6 @@ stream: # Output public url, If not defined, the value will be generated from # the [general.public_url] hostname, the output port and mount. public_url: - # web server host. - # > default is localhost - host: localhost - # webs server server port. - # > default is 80 - port: 80 # hls mount point # > this field is REQUIRED mount: hls/main @@ -346,7 +340,7 @@ stream: - # > prefix of generated fragment # > this field is REQUIRED fragment_prefix: mp3_low - # > must be one of ( 'libmp3lame', 'flac', 'aac', 'libopus', 'libvorbis') + # > codec must be one of ( 'libmp3lame', 'flac', 'aac', 'libopus', 'libvorbis') # > this field is REQUIRED codec: libmp3lame # > bitrate of the stream diff --git a/docker/example/config.yml b/docker/example/config.yml index 5ebd51254c..09c60f03ab 100644 --- a/docker/example/config.yml +++ b/docker/example/config.yml @@ -320,7 +320,7 @@ stream: hls: - # Whether the output is enabled. # > default is false - enabled: true + enabled: false # > must be one of ('mpegts', 'mp3', 'adts', 'mp4') # > this field is REQUIRED format: mpegts @@ -333,12 +333,6 @@ stream: # Output public url, If not defined, the value will be generated from # the [general.public_url] hostname, the output port and mount. public_url: - # web server host. - # > default is localhost - host: localhost - # webs server server port. - # > default is 80 - port: 80 # hls mount point # > this field is REQUIRED mount: hls/main @@ -346,7 +340,7 @@ stream: - # > prefix of generated fragment # > this field is REQUIRED fragment_prefix: mp3_low - # > must be one of ( 'libmp3lame', 'flac', 'aac', 'libopus', 'libvorbis') + # > codec must be one of ( 'libmp3lame', 'flac', 'aac', 'libopus', 'libvorbis') # > this field is REQUIRED codec: libmp3lame # > bitrate of the stream diff --git a/installer/config.yml b/installer/config.yml index 294e9ab1ca..ef8f619a5a 100644 --- a/installer/config.yml +++ b/installer/config.yml @@ -320,7 +320,7 @@ stream: hls: - # Whether the output is enabled. # > default is false - enabled: true + enabled: false # > must be one of ('mpegts', 'mp3', 'adts', 'mp4') # > this field is REQUIRED format: mpegts @@ -333,12 +333,6 @@ stream: # Output public url, If not defined, the value will be generated from # the [general.public_url] hostname, the output port and mount. public_url: - # web server host. - # > default is localhost - host: localhost - # webs server server port. - # > default is 80 - port: 80 # hls mount point # > this field is REQUIRED mount: hls/main @@ -346,7 +340,7 @@ stream: - # > prefix of generated fragment # > this field is REQUIRED fragment_prefix: mp3_low - # > must be one of ( 'libmp3lame', 'flac', 'aac', 'libopus', 'libvorbis') + # > codec must be one of ( 'libmp3lame', 'flac', 'aac', 'libopus', 'libvorbis') # > this field is REQUIRED codec: libmp3lame # > bitrate of the stream diff --git a/legacy/application/configs/conf.php b/legacy/application/configs/conf.php index f6bb878a49..1baaad23f4 100644 --- a/legacy/application/configs/conf.php +++ b/legacy/application/configs/conf.php @@ -225,11 +225,8 @@ public function getConfigTreeBuilder() /* */->integerNode('segment_count')->defaultValue(5)->end() /* */->integerNode('segments_overhead')->defaultValue(5)->end() /* */->booleanNode('enabled')->defaultFalse()->end() - /* */->enumNode('kind')->values(['hls'])->defaultValue('hls')->end() /* */->scalarNode('public_url')->end() - /* */->scalarNode('host')->defaultValue('localhost')->end() - /* */->integerNode('port')->defaultValue(80)->end() /* */->scalarNode('mount')->cannotBeEmpty() /* */->validate()->ifString()->then($trim_leading_slash)->end() /* */->end() diff --git a/legacy/application/models/StreamSetting.php b/legacy/application/models/StreamSetting.php index 7b640b5112..c8a48f9ce9 100644 --- a/legacy/application/models/StreamSetting.php +++ b/legacy/application/models/StreamSetting.php @@ -68,10 +68,11 @@ public static function getOutput($key, $add_prefix = false) $prefix . 'bitrate' => $output['audio']['bitrate'] ?? 128, $prefix . 'type' => $output['audio']['format'], ]); - } else { - // assume HLS : set web server port + } elseif ($output['kind'] == 'hls') { + // HLS : set web server host and port $result = array_merge($result, [ $prefix . 'port' => $_SERVER['SERVER_PORT'], + $prefix . 'host' => $_SERVER['SERVER_NAME'], ]); } } diff --git a/shared/libretime_shared/config/_models.py b/shared/libretime_shared/config/_models.py index c6713b8bee..e621f8dc04 100644 --- a/shared/libretime_shared/config/_models.py +++ b/shared/libretime_shared/config/_models.py @@ -208,8 +208,6 @@ class HlsOutput(BaseModel): kind: Literal["hls"] = "hls" enabled: bool = False public_url: Optional[AnyUrl] = None - host: str = "localhost" - port: int = 80 format: str = "mpegts" segment_duration: float = 2.0 segment_count: int = 5 From faa3f86fc89a46f7e55aa8fb54ffafca8ff3339a Mon Sep 17 00:00:00 2001 From: Julien Valentin Date: Wed, 13 Dec 2023 14:44:53 +0100 Subject: [PATCH 12/26] document hls output --- docs/admin-manual/configuration.md | 67 ++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/docs/admin-manual/configuration.md b/docs/admin-manual/configuration.md index faa017f5b7..c1643f6fff 100644 --- a/docs/admin-manual/configuration.md +++ b/docs/admin-manual/configuration.md @@ -371,6 +371,7 @@ stream: outputs: icecast: # See the [stream.outputs.icecast] section. shoutcast: # See the [stream.outputs.shoutcast] section. + hls: # See the [stream.outputs.hls] section. system: # See the [stream.outputs.system] section. ``` @@ -514,6 +515,72 @@ stream: mobile: false ``` +#### Hls + +The `stream.outputs.hls` section configures the HLS output streams. + +HLS is an HTTP-based adaptive bitrate streaming protocol. It consists on presenting multiple streams with differents qualities to the client player which select the most fitted to its bandwidth. + +To configure hls streams, first describe the container of these streams by its **format**. + +Then setup the differents streams composing it with differents **codec**s and **bitrate**s. + +:::warning + +For security purposes, liquidsoap produces hls files in the webserver mount point **hls/** instead of creating its own. + +**mount** setting must then be prefixed with **hls/**. + +The hls mount point is defined in /etc/nginx/sites-available/libretime.conf, redefine it to your need. + +```yml +stream: + outputs: + # Shoutcast output streams. + # > max items is 1 + hls: + - # Whether the output is enabled. + # > default is false + enabled: false + # Output public url. If not defined, the value will be generated from + # the [general.public_url] hostname and the output port. + public_url: + # hls server mount point. + # > this field is REQUIRED + mount: "hls/main.m3u8" + # > format of the container must be one of ('mpegts', 'mp3', 'adts', 'mp4') + # > this field is REQUIRED + format: mpegts + # > segment_duration (default:2.0) + segment_duration: 2.0 + # > segments count: segments count buffered (default:5) + segment_count: 5 + # > segments_overhead: segments count kept past the playlist size for those listeners who are still listening on outdated segments. (default:5) + segments_overhead: 5 + streams: + - # > prefix of generated fragment + # > this field is REQUIRED + fragment_prefix: mp3_low + # > codec used for the stream, must be one of ( 'libmp3lame', 'flac', 'aac', 'libopus', 'libvorbis') + # > this field is REQUIRED + codec: libmp3lame + # > bitrate of the stream + # > this field is REQUIRED + bitrate: 32k + # > sampling rate (default: 44100Hz) + sample_rate: 44100 + - fragment_prefix: mp3_med + codec: libmp3lame + bitrate: 128k + - fragment_prefix: mp3_hifi + codec: libmp3lame + bitrate: 256k + + # Whether the stream should be used for mobile devices. + # > default is false + mobile: false +``` + #### System The `stream.outputs.system` section configures the system outputs. From 7da16a15d60439d4f3394e47cfa0c9fa9ccaf0e5 Mon Sep 17 00:00:00 2001 From: mp3butcher Date: Wed, 13 Dec 2023 17:07:57 +0100 Subject: [PATCH 13/26] add an alias to playout hls output --- docker/nginx.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker/nginx.conf b/docker/nginx.conf index 46748a871d..c8b3cefb8a 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -9,6 +9,10 @@ server { client_max_body_size 512M; client_body_timeout 300s; + location /hls { + alias /var/lib/libretime/playout/hls; + } + location ~ \.php$ { fastcgi_buffers 64 4K; fastcgi_split_path_info ^(.+\.php)(/.+)$; From a439af83f6753be04856ba6ab74941cb98e85ea4 Mon Sep 17 00:00:00 2001 From: mp3butcher Date: Wed, 13 Dec 2023 17:24:30 +0100 Subject: [PATCH 14/26] simplification --- legacy/application/models/StreamSetting.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/legacy/application/models/StreamSetting.php b/legacy/application/models/StreamSetting.php index c8a48f9ce9..52f3fa92e9 100644 --- a/legacy/application/models/StreamSetting.php +++ b/legacy/application/models/StreamSetting.php @@ -73,6 +73,8 @@ public static function getOutput($key, $add_prefix = false) $result = array_merge($result, [ $prefix . 'port' => $_SERVER['SERVER_PORT'], $prefix . 'host' => $_SERVER['SERVER_NAME'], + $prefix . 'type' => 'hls', + $prefix . 'bitrate' => '', ]); } } @@ -118,8 +120,8 @@ public static function getEnabledStreamData() $prefix = $id . '_'; $streams[$id] = [ 'url' => $streamData[$prefix . 'public_url'], - 'codec' => $streamData[$prefix . 'type'] ?? 'hls', - 'bitrate' => $streamData[$prefix . 'bitrate'] ?? '', + 'codec' => $streamData[$prefix . 'type'], + 'bitrate' => $streamData[$prefix . 'bitrate'], 'mobile' => $streamData[$prefix . 'mobile'], ]; } From 4872cae7aa2cc1127a5d584ca2cbf936597d53da Mon Sep 17 00:00:00 2001 From: mp3butcher Date: Wed, 13 Dec 2023 17:35:24 +0100 Subject: [PATCH 15/26] add HlsStream to imports --- shared/libretime_shared/config/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/shared/libretime_shared/config/__init__.py b/shared/libretime_shared/config/__init__.py index eb82318ee2..a3d810b88d 100644 --- a/shared/libretime_shared/config/__init__.py +++ b/shared/libretime_shared/config/__init__.py @@ -6,6 +6,7 @@ GeneralConfig, HarborInput, HlsOutput, + HlsStream, IcecastOutput, RabbitMQConfig, ShoutcastOutput, From 5ee1e843bfde31fbf1b25495ddd39a015d97f51f Mon Sep 17 00:00:00 2001 From: Julien Valentin Date: Wed, 13 Dec 2023 17:47:36 +0100 Subject: [PATCH 16/26] Update configuration.md --- docs/admin-manual/configuration.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/admin-manual/configuration.md b/docs/admin-manual/configuration.md index c1643f6fff..7adca14655 100644 --- a/docs/admin-manual/configuration.md +++ b/docs/admin-manual/configuration.md @@ -519,11 +519,13 @@ stream: The `stream.outputs.hls` section configures the HLS output streams. -HLS is an HTTP-based adaptive bitrate streaming protocol. It consists on presenting multiple streams with differents qualities to the client player which select the most fitted to its bandwidth. +HLS is an HTTP-based adaptive bitrate streaming protocol. + +It consists on presenting multiple streams with varying qualities to the client player which select the most fitted to its bandwidth. To configure hls streams, first describe the container of these streams by its **format**. -Then setup the differents streams composing it with differents **codec**s and **bitrate**s. +Then setup the composites streams with various **codec**s and **bitrate**s. :::warning From 28e83ad6765617e5a691fcfed6829b023054cbd2 Mon Sep 17 00:00:00 2001 From: Julien Valentin Date: Sun, 17 Dec 2023 22:35:52 +0100 Subject: [PATCH 17/26] add missing changes --- dev/config.yml | 41 +++++++++++++++++++ .../liquidsoap/1.4/ls_lib.liq | 2 +- .../liquidsoap/templates/outputs.liq.j2 | 11 +++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/dev/config.yml b/dev/config.yml index 78e76ee557..8f6259ff92 100644 --- a/dev/config.yml +++ b/dev/config.yml @@ -66,6 +66,47 @@ stream: format: mp3 bitrate: 256 + hls: + - # Whether the output is enabled. + # > default is false + enabled: false + # > must be one of ('mpegts', 'mp3', 'adts', 'mp4') + # > this field is REQUIRED + format: mpegts + # > segment_duration (default:2.0) + segment_duration: 2.0 + # > segments count (default:5) + segment_count: 5 + # > segments_overhead (default:5) + segments_overhead: 5 + # Output public url, If not defined, the value will be generated from + # the [general.public_url] hostname, the output port and mount. + public_url: + # hls mount point + # > this field is REQUIRED + mount: hls/main + streams: + - # > prefix of generated fragment + # > this field is REQUIRED + fragment_prefix: mp3_low + # > codec must be one of ( 'libmp3lame', 'flac', 'aac', 'libopus', 'libvorbis') + # > this field is REQUIRED + codec: libmp3lame + # > bitrate of the stream + # > this field is REQUIRED + bitrate: 32k + # > sampling rate (default: 44100Hz) + sample_rate: 44100 + - fragment_prefix: mp3_med + codec: libmp3lame + bitrate: 128k + - fragment_prefix: mp3_hifi + codec: libmp3lame + bitrate: 256k + # Whether the stream should be used for mobile devices. + # > default is false + mobile: false + system: - enabled: false kind: pulseaudio diff --git a/playout/libretime_playout/liquidsoap/1.4/ls_lib.liq b/playout/libretime_playout/liquidsoap/1.4/ls_lib.liq index 88dbe97ec4..8653437440 100644 --- a/playout/libretime_playout/liquidsoap/1.4/ls_lib.liq +++ b/playout/libretime_playout/liquidsoap/1.4/ls_lib.liq @@ -3,7 +3,7 @@ def start_hls(input_main_mount, streamsref, servpath, segment_duration, segments #generate dummy stream infos does the trick for 1.4 list.iter(fun(item) -> streams_infos := list.append([(fst(item), (list.length(!streams_infos)+1,'',''))], !streams_infos ) - , streamsref()) + , !streamsref) argstream = !streamsref arginfos = !streams_infos output.file.hls(playlist=input_main_mount, diff --git a/playout/libretime_playout/liquidsoap/templates/outputs.liq.j2 b/playout/libretime_playout/liquidsoap/templates/outputs.liq.j2 index 33551159c1..d848584b63 100644 --- a/playout/libretime_playout/liquidsoap/templates/outputs.liq.j2 +++ b/playout/libretime_playout/liquidsoap/templates/outputs.liq.j2 @@ -52,6 +52,17 @@ output_hls_{{ output_id }}_source = s streams = ref([]) +{% for stream in output.streams -%} + + streamout = %ffmpeg(format="{{output.format}}" , + codec="{{stream.codec}}", + channels=2, + ar={{stream.sample_rate}}, + b="{{stream.bitrate}}") + streams := list.append([("{{stream.fragment_prefix}}", streamout)], !streams) + +{% endfor -%} + def segment_name(~position,~extname, stream_name) = timestamp = int_of_float(time()) duration = {{output.segment_duration}} From 2723f2bf1bec8ed1856f5be57ae2b47b2758e2b4 Mon Sep 17 00:00:00 2001 From: mp3butcher Date: Mon, 18 Dec 2023 17:57:57 +0100 Subject: [PATCH 18/26] add hls testing --- playout/tests/conftest.py | 2 +- playout/tests/liquidsoap/fixtures/__init__.py | 46 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/playout/tests/conftest.py b/playout/tests/conftest.py index cb30a3d62d..397922439d 100644 --- a/playout/tests/conftest.py +++ b/playout/tests/conftest.py @@ -30,7 +30,7 @@ def config(): "source_password": "hackme", "audio": {"format": "mp3", "bitrate": 256}, }, - ] + ], } }, } diff --git a/playout/tests/liquidsoap/fixtures/__init__.py b/playout/tests/liquidsoap/fixtures/__init__.py index 1675bbbb38..bb9d4150e9 100644 --- a/playout/tests/liquidsoap/fixtures/__init__.py +++ b/playout/tests/liquidsoap/fixtures/__init__.py @@ -67,6 +67,31 @@ def make_config_with_stream(**kwargs) -> Config: ], } ), + make_config_with_stream( + outputs={ + "hls": [ + { + "enabled": True, + "mount": "hls/main", + "format": "mpegts", + "streams": [ + { + "fragment_prefix": "mp3low", + "format": "mpegts", + "codec": "libmp3lame", + "bitrate": "64k", + }, + { + "fragment_prefix": "mp3high", + "format": "mpegts", + "codec": "libmp3lame", + "bitrate": "128k", + }, + ], + } + ], + } + ), make_config_with_stream( outputs={ "icecast": [ @@ -84,6 +109,27 @@ def make_config_with_stream(**kwargs) -> Config: "audio": {"format": "mp3", "bitrate": 256}, }, ], + "hls": [ + { + "enabled": True, + "mount": "hls/main", + "format": "mpegts", + "streams": [ + { + "fragment_prefix": "mp3low", + "format": "mpegts", + "codec": "libmp3lame", + "bitrate": "64k", + }, + { + "fragment_prefix": "mp3high", + "format": "mpegts", + "codec": "libmp3lame", + "bitrate": "128k", + }, + ], + } + ], } ), make_config_with_stream( From 2d6211760bcfee906a4974de82dba30dd8abf66a Mon Sep 17 00:00:00 2001 From: mp3butcher Date: Mon, 18 Dec 2023 18:48:21 +0100 Subject: [PATCH 19/26] change hls configuration --- legacy/application/configs/conf.php | 21 +++++++++--------- legacy/application/models/StreamSetting.php | 7 +++--- shared/libretime_shared/config/__init__.py | 4 ++-- shared/libretime_shared/config/_models.py | 24 ++++++++++----------- 4 files changed, 27 insertions(+), 29 deletions(-) diff --git a/legacy/application/configs/conf.php b/legacy/application/configs/conf.php index 1baaad23f4..baaf34b728 100644 --- a/legacy/application/configs/conf.php +++ b/legacy/application/configs/conf.php @@ -207,30 +207,29 @@ public function getConfigTreeBuilder() // Hls outputs /**/->arrayNode('hls')->arrayPrototype()->children() /* */->arrayNode('streams')->arrayPrototype()->children() - /* */->scalarNode('fragment_prefix')->end() - /* */->scalarNode('bitrate')->isRequired()->end() - /* */->IntegerNode('sample_rate')->defaultValue(44100)->end() + /* */->scalarNode('segments_prefix')->end() + /* */->scalarNode('format')->cannotBeEmpty() + /* */->validate()->ifNotInArray(['mpegts', 'adts']) + /* */->thenInvalid('invalid stream.outputs.hls.format %s') + /* */->end() + /* */->end() /* */->scalarNode('codec')->cannotBeEmpty() - /* */->validate()->ifNotInArray(['aac', 'libmp3lame', 'flac', 'libopus', 'libvorbis']) + /* */->validate()->ifNotInArray(['aac', 'libmp3lame']) /* */->thenInvalid('invalid stream.outputs.hls.streams.codec %s') /* */->end() /* */->end() + /* */->IntegerNode('sample_rate')->end() + /* */->scalarNode('bitrate')->isRequired()->end() /* */->end()->end()->end() - /* */->scalarNode('format')->cannotBeEmpty() - /* */->validate()->ifNotInArray(['mpegts', 'mp3', 'adts', 'mp4']) - /* */->thenInvalid('invalid stream.outputs.hls.format %s') - /* */->end() - /* */->end() /* */->floatNode('segment_duration')->defaultValue(2.0)->end() /* */->integerNode('segment_count')->defaultValue(5)->end() /* */->integerNode('segments_overhead')->defaultValue(5)->end() /* */->booleanNode('enabled')->defaultFalse()->end() /* */->enumNode('kind')->values(['hls'])->defaultValue('hls')->end() /* */->scalarNode('public_url')->end() - /* */->scalarNode('mount')->cannotBeEmpty() + /* */->scalarNode('manifest')->cannotBeEmpty() /* */->validate()->ifString()->then($trim_leading_slash)->end() /* */->end() - /* */->booleanNode('mobile')->defaultFalse()->end() /**/->end()->end()->end() // System outputs /**/->arrayNode('system')->arrayPrototype()->children() diff --git a/legacy/application/models/StreamSetting.php b/legacy/application/models/StreamSetting.php index 52f3fa92e9..b7bf312b69 100644 --- a/legacy/application/models/StreamSetting.php +++ b/legacy/application/models/StreamSetting.php @@ -71,10 +71,11 @@ public static function getOutput($key, $add_prefix = false) } elseif ($output['kind'] == 'hls') { // HLS : set web server host and port $result = array_merge($result, [ - $prefix . 'port' => $_SERVER['SERVER_PORT'], - $prefix . 'host' => $_SERVER['SERVER_NAME'], - $prefix . 'type' => 'hls', + $prefix . 'port' => Config::get('general.public_url_raw')->getPort(), + $prefix . 'type' => 'x-mpegurl', $prefix . 'bitrate' => '', + #prefix manifest with webserver hls mount point + $prefix . 'mount' => 'hls/'.$output['manifest'], ]); } } diff --git a/shared/libretime_shared/config/__init__.py b/shared/libretime_shared/config/__init__.py index a3d810b88d..8980907b7e 100644 --- a/shared/libretime_shared/config/__init__.py +++ b/shared/libretime_shared/config/__init__.py @@ -5,8 +5,8 @@ DatabaseConfig, GeneralConfig, HarborInput, - HlsOutput, - HlsStream, + HLSOutput, + HLSStream, IcecastOutput, RabbitMQConfig, ShoutcastOutput, diff --git a/shared/libretime_shared/config/_models.py b/shared/libretime_shared/config/_models.py index e621f8dc04..05cf257f03 100644 --- a/shared/libretime_shared/config/_models.py +++ b/shared/libretime_shared/config/_models.py @@ -197,27 +197,25 @@ class AudioOpus(BaseAudio): format: Literal[AudioFormat.OPUS] = AudioFormat.OPUS -class HlsStream(BaseModel): - fragment_prefix: str = "mp3_128" - bitrate: str = "128k" - sample_rate: int = 44100 - codec: str = "libmp3lame" +class HLSStream(BaseModel): + segments_prefix: str + format: str + codec: str + sample_rate: int + bitrate: str -class HlsOutput(BaseModel): +class HLSOutput(BaseModel): kind: Literal["hls"] = "hls" enabled: bool = False public_url: Optional[AnyUrl] = None - format: str = "mpegts" segment_duration: float = 2.0 segment_count: int = 5 segments_overhead: int = 5 - streams: List[HlsStream] = Field([], max_items=10) - mount: str - - mobile: bool = False + streams: List[HLSStream] = Field([], max_items=10) + manifest: str - _mount_no_leading_slash = no_leading_slash_validator("mount") + _mount_no_leading_slash = no_leading_slash_validator("manifest") class IcecastOutput(BaseModel): @@ -289,7 +287,7 @@ class SystemOutput(BaseModel): class Outputs(BaseModel): icecast: List[IcecastOutput] = Field([], max_items=3) shoutcast: List[ShoutcastOutput] = Field([], max_items=1) - hls: List[HlsOutput] = Field([], max_items=1) + hls: List[HLSOutput] = Field([], max_items=1) system: List[SystemOutput] = Field([], max_items=1) @property From 083323994b3fc2f6042d0256c24ea5d0aecf3b19 Mon Sep 17 00:00:00 2001 From: mp3butcher Date: Wed, 20 Dec 2023 21:02:28 +0100 Subject: [PATCH 20/26] make hls output path an install setting --- install | 22 +++++++++++++-- installer/nginx/libretime.conf | 2 +- .../systemd/libretime-liquidsoap.service | 1 + .../liquidsoap/1.4/ls_lib.liq | 6 ++--- .../liquidsoap/entrypoint.py | 2 ++ playout/libretime_playout/liquidsoap/main.py | 15 +++++++++-- .../liquidsoap/templates/outputs.liq.j2 | 9 ++++--- shared/libretime_shared/cli.py | 27 +++++++++++++++++++ 8 files changed, 72 insertions(+), 12 deletions(-) diff --git a/install b/install index a199adb9f7..9c8e46267f 100755 --- a/install +++ b/install @@ -66,6 +66,7 @@ Options: --user USER, -u USER User used to run LibreTime. --listen-port PORT, -p PORT Listen port for LibreTime. + --hls-output-path PATH custom path where hls manifest will be produced --in-place, -i Install LibreTime in place. @@ -123,6 +124,9 @@ LIBRETIME_UPDATE_NGINX=${LIBRETIME_UPDATE_NGINX:-false} # > Comma separated list of sections to exclude from packages list. LIBRETIME_PACKAGES_EXCLUDES=${LIBRETIME_PACKAGES_EXCLUDES:-} +# > Alternative path to the hls web server mount point +LIBRETIME_HLS_CUSTOM_OUTPUT="" + while [[ $# -gt 0 ]]; do case "$1" in --user | -u) @@ -157,6 +161,10 @@ while [[ $# -gt 0 ]]; do LIBRETIME_PACKAGES_EXCLUDES=$2 shift 2 ;; + --hls-output-path) + LIBRETIME_HLS_CUSTOM_OUTPUT=$2 + shift 2 + ;; --help | -h) usage exit 0 @@ -190,6 +198,12 @@ LOG_DIR="/var/log/libretime" STORAGE_DIR="/srv/libretime" LEGACY_WEB_ROOT="/usr/share/libretime/legacy" +if [[ -z "$LIBRETIME_HLS_CUSTOM_OUTPUT" ]]; then + HLS_PLAYOUT_OUTPUT="$WORKING_DIR/playout/hls" +else + HLS_PLAYOUT_OUTPUT="$LIBRETIME_HLS_CUSTOM_OUTPUT" +fi + SERVICE_DIR="/usr/lib/systemd/system" # command_exist @@ -309,7 +323,8 @@ install_service() { -e "s|@@CONFIG_DIR@@|${CONFIG_DIR}|g" \ -e "s|@@CONFIG_FILEPATH@@|${CONFIG_FILEPATH}|g" \ -e "s|@@LOG_DIR@@|${LOG_DIR}|g" \ - -e "s|@@WORKING_DIR@@|${WORKING_DIR}|g" + -e "s|@@WORKING_DIR@@|${WORKING_DIR}|g" \ + -e "s|@@HLS_PLAYOUT_OUTPUT@@|${HLS_PLAYOUT_OUTPUT}|g" chmod 0644 "$service_dest" chown root:root "$service_dest" @@ -628,7 +643,9 @@ link_python_app libretime-playout-notify info "creating libretime-playout working directory" mkdir_and_chown "$LIBRETIME_USER" "$WORKING_DIR/playout" -mkdir_and_chown "$LIBRETIME_USER" "$WORKING_DIR/playout/hls" + +info "creating libretime-playout hls output directory" +mkdir_and_chown "$LIBRETIME_USER" "$HLS_PLAYOUT_OUTPUT" install_service "libretime-liquidsoap.service" "$SCRIPT_DIR/playout/install/systemd/libretime-liquidsoap.service" install_service "libretime-playout.service" "$SCRIPT_DIR/playout/install/systemd/libretime-playout.service" @@ -757,6 +774,7 @@ nginx_config_template_args=( sed -e "s|@@LISTEN_PORT@@|${LIBRETIME_LISTEN_PORT}|g" -e "s|@@LEGACY_WEB_ROOT@@|${LEGACY_WEB_ROOT}|g" + -e "s|@@HLS_PLAYOUT_OUTPUT@@|${HLS_PLAYOUT_OUTPUT}|g" ) if $is_first_install || $LIBRETIME_UPDATE_NGINX; then diff --git a/installer/nginx/libretime.conf b/installer/nginx/libretime.conf index b4a398cd04..b1ab706a93 100644 --- a/installer/nginx/libretime.conf +++ b/installer/nginx/libretime.conf @@ -13,7 +13,7 @@ server { client_body_timeout 300s; location /hls { - alias /var/lib/libretime/playout/hls; + alias @@HLS_PLAYOUT_OUTPUT@@; } location ~ \.php$ { diff --git a/playout/install/systemd/libretime-liquidsoap.service b/playout/install/systemd/libretime-liquidsoap.service index f92b35af32..4ea365d5fc 100644 --- a/playout/install/systemd/libretime-liquidsoap.service +++ b/playout/install/systemd/libretime-liquidsoap.service @@ -19,6 +19,7 @@ ProtectSystem=full Environment=LIBRETIME_CONFIG_FILEPATH=@@CONFIG_FILEPATH@@ Environment=LIBRETIME_LOG_FILEPATH=@@LOG_DIR@@/liquidsoap.log +Environment=LIBRETIME_OUTPUT_PATH=@@HLS_PLAYOUT_OUTPUT@@ WorkingDirectory=@@WORKING_DIR@@/playout ExecStart=/usr/local/bin/libretime-liquidsoap diff --git a/playout/libretime_playout/liquidsoap/1.4/ls_lib.liq b/playout/libretime_playout/liquidsoap/1.4/ls_lib.liq index 8653437440..da42d5f3e2 100644 --- a/playout/libretime_playout/liquidsoap/1.4/ls_lib.liq +++ b/playout/libretime_playout/liquidsoap/1.4/ls_lib.liq @@ -1,8 +1,8 @@ -def start_hls(input_main_mount, streamsref, servpath, segment_duration, segments, segments_overhead, segment_named, output_source) +def start_hls(input_main_mount, segment_duration, segments, segments_overhead, segment_named, servpath, streamsref, output_source) streams_infos = ref([]) - #generate dummy stream infos does the trick for 1.4 + #generate dummy stream infos does the trick for 1.4 (just setup ascending bitrate for player can select) list.iter(fun(item) -> - streams_infos := list.append([(fst(item), (list.length(!streams_infos)+1,'',''))], !streams_infos ) + streams_infos := list.append([(fst(item), (list.length(!streamsref)-list.length(!streams_infos)+1,'',''))], !streams_infos ) , !streamsref) argstream = !streamsref arginfos = !streams_infos diff --git a/playout/libretime_playout/liquidsoap/entrypoint.py b/playout/libretime_playout/liquidsoap/entrypoint.py index 869256b9db..b07e2c4dfa 100644 --- a/playout/libretime_playout/liquidsoap/entrypoint.py +++ b/playout/libretime_playout/liquidsoap/entrypoint.py @@ -19,12 +19,14 @@ def generate_entrypoint( log_filepath: Optional[Path], + hls_output_path: Optional[Path], config: Config, preferences: StreamPreferences, info: Info, version: Tuple[int, int, int], ) -> str: paths = {} + paths["hls_output_path"] = hls_output_path.resolve() paths["lib_filepath"] = here / f"{version[0]}.{version[1]}/ls_script.liq" if log_filepath is not None: diff --git a/playout/libretime_playout/liquidsoap/main.py b/playout/libretime_playout/liquidsoap/main.py index 6679af29e7..145551cb14 100644 --- a/playout/libretime_playout/liquidsoap/main.py +++ b/playout/libretime_playout/liquidsoap/main.py @@ -5,7 +5,11 @@ import click from libretime_api_client.v2 import ApiClient -from libretime_shared.cli import cli_config_options, cli_logging_options +from libretime_shared.cli import ( + cli_config_options, + cli_logging_options, + cli_output_options, +) from libretime_shared.config import DEFAULT_ENV_PREFIX from libretime_shared.logging import setup_logger @@ -22,7 +26,13 @@ @click.command(context_settings={"auto_envvar_prefix": DEFAULT_ENV_PREFIX}) @cli_logging_options() @cli_config_options() -def cli(log_level: str, log_filepath: Optional[Path], config_filepath: Optional[Path]): +@cli_output_options() +def cli( + log_level: str, + log_filepath: Optional[Path], + config_filepath: Optional[Path], + output_path: Path, +): """ Run liquidsoap. """ @@ -43,6 +53,7 @@ def cli(log_level: str, log_filepath: Optional[Path], config_filepath: Optional[ entrypoint_filepath.write_text( generate_entrypoint( log_filepath, + output_path, config, preferences, info, diff --git a/playout/libretime_playout/liquidsoap/templates/outputs.liq.j2 b/playout/libretime_playout/liquidsoap/templates/outputs.liq.j2 index d848584b63..ea78b65d87 100644 --- a/playout/libretime_playout/liquidsoap/templates/outputs.liq.j2 +++ b/playout/libretime_playout/liquidsoap/templates/outputs.liq.j2 @@ -54,12 +54,12 @@ streams = ref([]) {% for stream in output.streams -%} - streamout = %ffmpeg(format="{{output.format}}" , + streamout = %ffmpeg(format="{{stream.format}}" , codec="{{stream.codec}}", channels=2, ar={{stream.sample_rate}}, b="{{stream.bitrate}}") - streams := list.append([("{{stream.fragment_prefix}}", streamout)], !streams) + streams := list.append([("{{stream.segments_prefix}}", streamout)], !streams) {% endfor -%} @@ -69,12 +69,13 @@ def segment_name(~position,~extname, stream_name) = "#{stream_name}_#{duration}_#{timestamp}_#{position}.#{extname}" end -start_hls(input_main_mount, streams, - "/var/lib/libretime/playout/hls", +start_hls("{{output.manifest}}", {{output.segment_duration}}, {{output.segment_count}}, {{output.segments_overhead}}, segment_name, + "{{ paths.hls_output_path }}", + streams, output_hls_{{ output_id }}_source) {%- endmacro -%} diff --git a/shared/libretime_shared/cli.py b/shared/libretime_shared/cli.py index e4c8032e44..9fae4bc087 100644 --- a/shared/libretime_shared/cli.py +++ b/shared/libretime_shared/cli.py @@ -59,3 +59,30 @@ def decorator(func: Callable) -> Callable: return func return decorator + + +def cli_output_options( + required: bool = False, + default: Optional[Any] = None, +) -> Callable: + def decorator(func: Callable) -> Callable: + """ + Decorator function to add output directory options to a click application. + + This decorator add the following arguments: + - output_path: Optional[Path] or Path + """ + + func = click.option( + "--o", + "--output", + "output_path", + type=click.Path(path_type=Path), + help="Path to output.", + required=required, + default=default, + )(func) + + return func + + return decorator From 44f2dae0dc9fc3b3bec916a0eda6be30f7975100 Mon Sep 17 00:00:00 2001 From: mp3butcher Date: Fri, 19 Jan 2024 16:41:16 +0100 Subject: [PATCH 21/26] update test --- .../liquidsoap/entrypoint.py | 18 ++++++++++-- playout/libretime_playout/liquidsoap/main.py | 5 ++-- playout/tests/liquidsoap/entrypoint_test.py | 29 ++++++++++++------- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/playout/libretime_playout/liquidsoap/entrypoint.py b/playout/libretime_playout/liquidsoap/entrypoint.py index b07e2c4dfa..f1f0e42743 100644 --- a/playout/libretime_playout/liquidsoap/entrypoint.py +++ b/playout/libretime_playout/liquidsoap/entrypoint.py @@ -1,3 +1,4 @@ +import dataclasses from pathlib import Path from typing import Optional, Tuple @@ -17,14 +18,25 @@ templates.filters["quote"] = quote +@dataclasses.dataclass +class InfoVersion: + info: Info + version: Tuple[int, int, int] + + def __init__(self, inf: Info, ver: Tuple[int, int, int]): + self.info = inf + self.version = ver + + def generate_entrypoint( log_filepath: Optional[Path], - hls_output_path: Optional[Path], + hls_output_path: Path, config: Config, preferences: StreamPreferences, - info: Info, - version: Tuple[int, int, int], + infoversion: InfoVersion, ) -> str: + info = infoversion.info + version = infoversion.version paths = {} paths["hls_output_path"] = hls_output_path.resolve() paths["lib_filepath"] = here / f"{version[0]}.{version[1]}/ls_script.liq" diff --git a/playout/libretime_playout/liquidsoap/main.py b/playout/libretime_playout/liquidsoap/main.py index 145551cb14..1922b769b8 100644 --- a/playout/libretime_playout/liquidsoap/main.py +++ b/playout/libretime_playout/liquidsoap/main.py @@ -14,7 +14,7 @@ from libretime_shared.logging import setup_logger from ..config import Config -from .entrypoint import generate_entrypoint +from .entrypoint import InfoVersion, generate_entrypoint from .models import Info, StreamPreferences from .version import get_liquidsoap_version @@ -56,8 +56,7 @@ def cli( output_path, config, preferences, - info, - version, + InfoVersion(info, version), ), encoding="utf-8", ) diff --git a/playout/tests/liquidsoap/entrypoint_test.py b/playout/tests/liquidsoap/entrypoint_test.py index c92a68ec73..df9d12386c 100644 --- a/playout/tests/liquidsoap/entrypoint_test.py +++ b/playout/tests/liquidsoap/entrypoint_test.py @@ -5,7 +5,7 @@ import pytest from libretime_playout.config import Config -from libretime_playout.liquidsoap.entrypoint import generate_entrypoint +from libretime_playout.liquidsoap.entrypoint import InfoVersion, generate_entrypoint from libretime_playout.liquidsoap.models import Info, StreamPreferences from libretime_playout.liquidsoap.version import get_liquidsoap_version @@ -28,16 +28,19 @@ def test_generate_entrypoint(stream_config: Config, version, snapshot): ): found = generate_entrypoint( log_filepath=Path("/var/log/radio.log"), + hls_output_path=Path.cwd(), config=stream_config, preferences=StreamPreferences( input_fade_transition=0.0, message_format=0, message_offline="LibreTime - offline", ), - info=Info( - station_name="LibreTime", + infoversion=InfoVersion( + Info( + station_name="LibreTime", + ), + version, ), - version=version, ) assert found == snapshot @@ -58,16 +61,19 @@ def test_liquidsoap_syntax(tmp_path: Path, stream_config): entrypoint_filepath.write_text( generate_entrypoint( log_filepath=log_filepath, + hls_output_path=Path.cwd(), config=stream_config, preferences=StreamPreferences( input_fade_transition=0.0, message_format=0, message_offline="LibreTime - offline", ), - info=Info( - station_name="LibreTime", + infoversion=InfoVersion( + Info( + station_name="LibreTime", + ), + get_liquidsoap_version(), ), - version=get_liquidsoap_version(), ), encoding="utf-8", ) @@ -86,6 +92,7 @@ def test_liquidsoap_unsupported_output_aac(tmp_path: Path): entrypoint_filepath.write_text( generate_entrypoint( log_filepath=log_filepath, + hls_output_path=Path.cwd(), config=make_config_with_stream( outputs={ "icecast": [ @@ -103,10 +110,12 @@ def test_liquidsoap_unsupported_output_aac(tmp_path: Path): message_format=0, message_offline="LibreTime - offline", ), - info=Info( - station_name="LibreTime", + infoversion=InfoVersion( + Info( + station_name="LibreTime", + ), + get_liquidsoap_version(), ), - version=get_liquidsoap_version(), ), encoding="utf-8", ) From 95316d615401dc68d594bdb65182092bcaf8b089 Mon Sep 17 00:00:00 2001 From: mp3butcher Date: Fri, 19 Jan 2024 17:19:09 +0100 Subject: [PATCH 22/26] update configuration files and manual --- dev/config.yml | 43 +++++++++------------------- docker/config.template.yml | 41 +++++++++++++++------------ docker/config.yml | 41 +++++++++++++++------------ docker/example/config.yml | 41 +++++++++++++++------------ docs/admin-manual/configuration.md | 45 ++++++++++++------------------ installer/config.yml | 41 +++++++++++++++------------ 6 files changed, 123 insertions(+), 129 deletions(-) diff --git a/dev/config.yml b/dev/config.yml index 8f6259ff92..27f4734d10 100644 --- a/dev/config.yml +++ b/dev/config.yml @@ -43,7 +43,7 @@ stream: icecast: - <<: *default_icecast_output - enabled: true + enabled: false mount: main.ogg public_url: https://localhost:8443/main.ogg audio: @@ -51,7 +51,7 @@ stream: bitrate: 256 - <<: *default_icecast_output - enabled: true + enabled: false mount: main.opus public_url: https://localhost:8443/main.opus audio: @@ -59,7 +59,7 @@ stream: bitrate: 256 - <<: *default_icecast_output - enabled: true + enabled: false mount: main.mp3 public_url: https://localhost:8443/main.mp3 audio: @@ -67,45 +67,28 @@ stream: bitrate: 256 hls: - - # Whether the output is enabled. - # > default is false - enabled: false - # > must be one of ('mpegts', 'mp3', 'adts', 'mp4') - # > this field is REQUIRED - format: mpegts - # > segment_duration (default:2.0) + - enabled: true segment_duration: 2.0 - # > segments count (default:5) segment_count: 5 - # > segments_overhead (default:5) segments_overhead: 5 - # Output public url, If not defined, the value will be generated from - # the [general.public_url] hostname, the output port and mount. public_url: - # hls mount point - # > this field is REQUIRED - mount: hls/main + manifest: main.m3u8 streams: - - # > prefix of generated fragment - # > this field is REQUIRED - fragment_prefix: mp3_low - # > codec must be one of ( 'libmp3lame', 'flac', 'aac', 'libopus', 'libvorbis') - # > this field is REQUIRED + - segments_prefix: mp3_low codec: libmp3lame - # > bitrate of the stream - # > this field is REQUIRED bitrate: 32k - # > sampling rate (default: 44100Hz) sample_rate: 44100 - - fragment_prefix: mp3_med + format: mpegts + - segments_prefix: mp3_med + format: mpegts codec: libmp3lame bitrate: 128k - - fragment_prefix: mp3_hifi + sample_rate: 44100 + - segments_prefix: mp3_hifi + format: mpegts codec: libmp3lame bitrate: 256k - # Whether the stream should be used for mobile devices. - # > default is false - mobile: false + sample_rate: 44100 system: - enabled: false diff --git a/docker/config.template.yml b/docker/config.template.yml index cb4e21ab88..928e7b2fe9 100644 --- a/docker/config.template.yml +++ b/docker/config.template.yml @@ -7,9 +7,8 @@ general: # The internal API authentication key. # > this field is REQUIRED api_key: - # The Django API secret key. If not defined, the value of [general.api_key] will be - # used as fallback. - # > this field will be REQUIRED starting with LibreTime 4.0.0 + # The Django API secret key. + # > this field is REQUIRED secret_key: # List of origins allowed to access resources on the server, the public url @@ -32,7 +31,8 @@ general: auth: local storage: - # Path of the storage directory. + # Path of the storage directory. Make sure to update the Nginx configuration after + # updating the storage path. # > default is /srv/libretime path: /srv/libretime @@ -320,10 +320,7 @@ stream: hls: - # Whether the output is enabled. # > default is false - enabled: false - # > must be one of ('mpegts', 'mp3', 'adts', 'mp4') - # > this field is REQUIRED - format: mpegts + enabled: true # > segment_duration (default:2.0) segment_duration: 2.0 # > segments count (default:5) @@ -335,12 +332,15 @@ stream: public_url: # hls mount point # > this field is REQUIRED - mount: hls/main + manifest: main.m3u8 streams: - # > prefix of generated fragment # > this field is REQUIRED - fragment_prefix: mp3_low - # > codec must be one of ( 'libmp3lame', 'flac', 'aac', 'libopus', 'libvorbis') + segments_prefix: mp3_low + # > must be one of ('mpegts', 'adts') + # > this field is REQUIRED + format: mpegts + # > codec must be one of ('libmp3lame', 'aac') # > this field is REQUIRED codec: libmp3lame # > bitrate of the stream @@ -348,15 +348,16 @@ stream: bitrate: 32k # > sampling rate (default: 44100Hz) sample_rate: 44100 - - fragment_prefix: mp3_med + - segments_prefix: mp3_med + format: mpegts codec: libmp3lame bitrate: 128k - - fragment_prefix: mp3_hifi + sample_rate: 44100 + - segments_prefix: mp3_hifi + format: mpegts codec: libmp3lame bitrate: 256k - # Whether the stream should be used for mobile devices. - # > default is false - mobile: false + sample_rate: 44100 # System outputs. # > max items is 1 @@ -366,5 +367,9 @@ stream: enabled: false # System output kind. # > must be one of (alsa, ao, oss, portaudio, pulseaudio) - # > default is alsa - kind: alsa + # > default is pulseaudio + kind: pulseaudio + + # System output device. + # > only available for kind=(alsa, pulseaudio) + device: diff --git a/docker/config.yml b/docker/config.yml index c1adac5f87..74478ce647 100644 --- a/docker/config.yml +++ b/docker/config.yml @@ -7,9 +7,8 @@ general: # The internal API authentication key. # > this field is REQUIRED api_key: - # The Django API secret key. If not defined, the value of [general.api_key] will be - # used as fallback. - # > this field will be REQUIRED starting with LibreTime 4.0.0 + # The Django API secret key. + # > this field is REQUIRED secret_key: # List of origins allowed to access resources on the server, the public url @@ -32,7 +31,8 @@ general: auth: local storage: - # Path of the storage directory. + # Path of the storage directory. Make sure to update the Nginx configuration after + # updating the storage path. # > default is /srv/libretime path: /srv/libretime @@ -320,10 +320,7 @@ stream: hls: - # Whether the output is enabled. # > default is false - enabled: false - # > must be one of ('mpegts', 'mp3', 'adts', 'mp4') - # > this field is REQUIRED - format: mpegts + enabled: true # > segment_duration (default:2.0) segment_duration: 2.0 # > segments count (default:5) @@ -335,12 +332,15 @@ stream: public_url: # hls mount point # > this field is REQUIRED - mount: hls/main + manifest: main.m3u8 streams: - # > prefix of generated fragment # > this field is REQUIRED - fragment_prefix: mp3_low - # > codec must be one of ( 'libmp3lame', 'flac', 'aac', 'libopus', 'libvorbis') + segments_prefix: mp3_low + # > must be one of ('mpegts', 'adts') + # > this field is REQUIRED + format: mpegts + # > codec must be one of ('libmp3lame', 'aac') # > this field is REQUIRED codec: libmp3lame # > bitrate of the stream @@ -348,15 +348,16 @@ stream: bitrate: 32k # > sampling rate (default: 44100Hz) sample_rate: 44100 - - fragment_prefix: mp3_med + - segments_prefix: mp3_med + format: mpegts codec: libmp3lame bitrate: 128k - - fragment_prefix: mp3_hifi + sample_rate: 44100 + - segments_prefix: mp3_hifi + format: mpegts codec: libmp3lame bitrate: 256k - # Whether the stream should be used for mobile devices. - # > default is false - mobile: false + sample_rate: 44100 # System outputs. # > max items is 1 @@ -366,5 +367,9 @@ stream: enabled: false # System output kind. # > must be one of (alsa, ao, oss, portaudio, pulseaudio) - # > default is alsa - kind: alsa + # > default is pulseaudio + kind: pulseaudio + + # System output device. + # > only available for kind=(alsa, pulseaudio) + device: diff --git a/docker/example/config.yml b/docker/example/config.yml index 09c60f03ab..09d111f590 100644 --- a/docker/example/config.yml +++ b/docker/example/config.yml @@ -7,9 +7,8 @@ general: # The internal API authentication key. # > this field is REQUIRED api_key: some_secret_api_key - # The Django API secret key. If not defined, the value of [general.api_key] will be - # used as fallback. - # > this field will be REQUIRED starting with LibreTime 4.0.0 + # The Django API secret key. + # > this field is REQUIRED secret_key: # List of origins allowed to access resources on the server, the public url @@ -32,7 +31,8 @@ general: auth: local storage: - # Path of the storage directory. + # Path of the storage directory. Make sure to update the Nginx configuration after + # updating the storage path. # > default is /srv/libretime path: /srv/libretime @@ -320,10 +320,7 @@ stream: hls: - # Whether the output is enabled. # > default is false - enabled: false - # > must be one of ('mpegts', 'mp3', 'adts', 'mp4') - # > this field is REQUIRED - format: mpegts + enabled: true # > segment_duration (default:2.0) segment_duration: 2.0 # > segments count (default:5) @@ -335,12 +332,15 @@ stream: public_url: # hls mount point # > this field is REQUIRED - mount: hls/main + manifest: main.m3u8 streams: - # > prefix of generated fragment # > this field is REQUIRED - fragment_prefix: mp3_low - # > codec must be one of ( 'libmp3lame', 'flac', 'aac', 'libopus', 'libvorbis') + segments_prefix: mp3_low + # > must be one of ('mpegts', 'adts') + # > this field is REQUIRED + format: mpegts + # > codec must be one of ('libmp3lame', 'aac') # > this field is REQUIRED codec: libmp3lame # > bitrate of the stream @@ -348,15 +348,16 @@ stream: bitrate: 32k # > sampling rate (default: 44100Hz) sample_rate: 44100 - - fragment_prefix: mp3_med + - segments_prefix: mp3_med + format: mpegts codec: libmp3lame bitrate: 128k - - fragment_prefix: mp3_hifi + sample_rate: 44100 + - segments_prefix: mp3_hifi + format: mpegts codec: libmp3lame bitrate: 256k - # Whether the stream should be used for mobile devices. - # > default is false - mobile: false + sample_rate: 44100 # System outputs. # > max items is 1 @@ -366,5 +367,9 @@ stream: enabled: false # System output kind. # > must be one of (alsa, ao, oss, portaudio, pulseaudio) - # > default is alsa - kind: alsa + # > default is pulseaudio + kind: pulseaudio + + # System output device. + # > only available for kind=(alsa, pulseaudio) + device: diff --git a/docs/admin-manual/configuration.md b/docs/admin-manual/configuration.md index 7adca14655..09cc23cdaf 100644 --- a/docs/admin-manual/configuration.md +++ b/docs/admin-manual/configuration.md @@ -531,10 +531,6 @@ Then setup the composites streams with various **codec**s and **bitrate**s. For security purposes, liquidsoap produces hls files in the webserver mount point **hls/** instead of creating its own. -**mount** setting must then be prefixed with **hls/**. - -The hls mount point is defined in /etc/nginx/sites-available/libretime.conf, redefine it to your need. - ```yml stream: outputs: @@ -543,27 +539,27 @@ stream: hls: - # Whether the output is enabled. # > default is false - enabled: false - # Output public url. If not defined, the value will be generated from - # the [general.public_url] hostname and the output port. - public_url: - # hls server mount point. - # > this field is REQUIRED - mount: "hls/main.m3u8" - # > format of the container must be one of ('mpegts', 'mp3', 'adts', 'mp4') - # > this field is REQUIRED - format: mpegts - # > segment_duration (default:2.0) + enabled: true + # > segment_duration (default:2.0) segment_duration: 2.0 - # > segments count: segments count buffered (default:5) + # > segments count (default:5) segment_count: 5 - # > segments_overhead: segments count kept past the playlist size for those listeners who are still listening on outdated segments. (default:5) + # > segments_overhead (default:5) segments_overhead: 5 + # Output public url, If not defined, the value will be generated from + # the [general.public_url] hostname, the output port and mount. + public_url: + # hls mount point + # > this field is REQUIRED + manifest: main.m3u8 streams: - # > prefix of generated fragment # > this field is REQUIRED - fragment_prefix: mp3_low - # > codec used for the stream, must be one of ( 'libmp3lame', 'flac', 'aac', 'libopus', 'libvorbis') + segments_prefix: mp3_low + # > must be one of ('mpegts', 'adts') + # > this field is REQUIRED + format: mpegts + # > codec must be one of ('libmp3lame', 'aac') # > this field is REQUIRED codec: libmp3lame # > bitrate of the stream @@ -571,16 +567,11 @@ stream: bitrate: 32k # > sampling rate (default: 44100Hz) sample_rate: 44100 - - fragment_prefix: mp3_med - codec: libmp3lame - bitrate: 128k - - fragment_prefix: mp3_hifi + - segments_prefix: mp3_hifi + format: mpegts codec: libmp3lame bitrate: 256k - - # Whether the stream should be used for mobile devices. - # > default is false - mobile: false + sample_rate: 44100 ``` #### System diff --git a/installer/config.yml b/installer/config.yml index ef8f619a5a..27294cf17c 100644 --- a/installer/config.yml +++ b/installer/config.yml @@ -7,9 +7,8 @@ general: # The internal API authentication key. # > this field is REQUIRED api_key: - # The Django API secret key. If not defined, the value of [general.api_key] will be - # used as fallback. - # > this field will be REQUIRED starting with LibreTime 4.0.0 + # The Django API secret key. + # > this field is REQUIRED secret_key: # List of origins allowed to access resources on the server, the public url @@ -32,7 +31,8 @@ general: auth: local storage: - # Path of the storage directory. + # Path of the storage directory. Make sure to update the Nginx configuration after + # updating the storage path. # > default is /srv/libretime path: /srv/libretime @@ -320,10 +320,7 @@ stream: hls: - # Whether the output is enabled. # > default is false - enabled: false - # > must be one of ('mpegts', 'mp3', 'adts', 'mp4') - # > this field is REQUIRED - format: mpegts + enabled: true # > segment_duration (default:2.0) segment_duration: 2.0 # > segments count (default:5) @@ -335,12 +332,15 @@ stream: public_url: # hls mount point # > this field is REQUIRED - mount: hls/main + manifest: main.m3u8 streams: - # > prefix of generated fragment # > this field is REQUIRED - fragment_prefix: mp3_low - # > codec must be one of ( 'libmp3lame', 'flac', 'aac', 'libopus', 'libvorbis') + segments_prefix: mp3_low + # > must be one of ('mpegts', 'adts') + # > this field is REQUIRED + format: mpegts + # > codec must be one of ('libmp3lame', 'aac') # > this field is REQUIRED codec: libmp3lame # > bitrate of the stream @@ -348,15 +348,16 @@ stream: bitrate: 32k # > sampling rate (default: 44100Hz) sample_rate: 44100 - - fragment_prefix: mp3_med + - segments_prefix: mp3_med + format: mpegts codec: libmp3lame bitrate: 128k - - fragment_prefix: mp3_hifi + sample_rate: 44100 + - segments_prefix: mp3_hifi + format: mpegts codec: libmp3lame bitrate: 256k - # Whether the stream should be used for mobile devices. - # > default is false - mobile: false + sample_rate: 44100 # System outputs. # > max items is 1 @@ -366,5 +367,9 @@ stream: enabled: false # System output kind. # > must be one of (alsa, ao, oss, portaudio, pulseaudio) - # > default is alsa - kind: alsa + # > default is pulseaudio + kind: pulseaudio + + # System output device. + # > only available for kind=(alsa, pulseaudio) + device: From 07d9448b174eb12beb8f954c02ac5a36d1e263b2 Mon Sep 17 00:00:00 2001 From: mp3butcher Date: Fri, 19 Jan 2024 17:35:28 +0100 Subject: [PATCH 23/26] setup docker files --- Dockerfile | 4 ++++ docker-compose.yml | 4 ++++ docker/nginx.conf | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 92f4d45026..a2ddaabdb8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,6 +33,7 @@ RUN set -eux \ && install --directory --owner=${USER} /etc/libretime /srv/libretime ENV LIBRETIME_CONFIG_FILEPATH=/etc/libretime/config.yml +ENV LIBRETIME_OUTPUT_PATH=/var/playout/hls # Shared packages COPY tools/packages.py /tmp/packages.py @@ -124,6 +125,9 @@ COPY playout . RUN --mount=type=cache,target=/root/.cache/pip \ pip install --editable .[sentry] +RUN mkdir -p $LIBRETIME_OUTPUT_PATH +RUN chown ${USER}:${USER} $LIBRETIME_OUTPUT_PATH + # Run USER ${UID}:${GID} WORKDIR /app diff --git a/docker-compose.yml b/docker-compose.yml index eed3c4d260..e7f70b299a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -47,6 +47,7 @@ services: volumes: - ${LIBRETIME_CONFIG_FILEPATH:-./config.yml}:/etc/libretime/config.yml:ro - libretime_playout:/app + - libretime_playout_hls:/var/playout/hls environment: LIBRETIME_GENERAL_PUBLIC_URL: http://nginx @@ -108,7 +109,9 @@ services: - legacy volumes: - libretime_assets:/var/www/html:ro + - libretime_storage:/srv/libretime:ro - ${NGINX_CONFIG_FILEPATH:-./nginx.conf}:/etc/nginx/conf.d/default.conf:ro + - libretime_playout_hls:/var/playout/hls:ro icecast: image: ghcr.io/libretime/icecast:2.4.4 @@ -124,3 +127,4 @@ volumes: libretime_storage: {} libretime_assets: {} libretime_playout: {} + libretime_playout_hls: {} diff --git a/docker/nginx.conf b/docker/nginx.conf index c8b3cefb8a..9924fc23b4 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -10,7 +10,7 @@ server { client_body_timeout 300s; location /hls { - alias /var/lib/libretime/playout/hls; + alias /var/playout/hls; } location ~ \.php$ { From 6f76da023f153d3dad79a27a85b05c19f1d095b4 Mon Sep 17 00:00:00 2001 From: mp3butcher Date: Fri, 19 Jan 2024 18:09:48 +0100 Subject: [PATCH 24/26] merge misses --- legacy/application/models/StreamSetting.php | 4 ++-- shared/libretime_shared/config/_models.py | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/legacy/application/models/StreamSetting.php b/legacy/application/models/StreamSetting.php index b7bf312b69..e40883f027 100644 --- a/legacy/application/models/StreamSetting.php +++ b/legacy/application/models/StreamSetting.php @@ -74,8 +74,8 @@ public static function getOutput($key, $add_prefix = false) $prefix . 'port' => Config::get('general.public_url_raw')->getPort(), $prefix . 'type' => 'x-mpegurl', $prefix . 'bitrate' => '', - #prefix manifest with webserver hls mount point - $prefix . 'mount' => 'hls/'.$output['manifest'], + // prefix manifest with webserver hls mount point + $prefix . 'mount' => 'hls/' . $output['manifest'], ]); } } diff --git a/shared/libretime_shared/config/_models.py b/shared/libretime_shared/config/_models.py index bf408e8e8c..ae4df5a058 100644 --- a/shared/libretime_shared/config/_models.py +++ b/shared/libretime_shared/config/_models.py @@ -177,15 +177,13 @@ class HLSStream(BaseModel): class HLSOutput(BaseModel): kind: Literal["hls"] = "hls" enabled: bool = False - public_url: Optional[AnyUrl] = None + public_url: Optional[AnyUrlStr] = None segment_duration: float = 2.0 segment_count: int = 5 segments_overhead: int = 5 - streams: List[HLSStream] = Field([], max_items=10) + streams: List[HLSStream] = Field([], max_length=10) manifest: str - _mount_no_leading_slash = no_leading_slash_validator("manifest") - class IcecastOutput(BaseModel): kind: Literal["icecast"] = "icecast" From 06cf70114a198f4175f3d2a9c2075b2e23a945d6 Mon Sep 17 00:00:00 2001 From: mp3butcher Date: Fri, 19 Jan 2024 18:26:56 +0100 Subject: [PATCH 25/26] test miss --- playout/tests/liquidsoap/fixtures/__init__.py | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/playout/tests/liquidsoap/fixtures/__init__.py b/playout/tests/liquidsoap/fixtures/__init__.py index 477bab5a50..30e4bf7141 100644 --- a/playout/tests/liquidsoap/fixtures/__init__.py +++ b/playout/tests/liquidsoap/fixtures/__init__.py @@ -73,20 +73,21 @@ def make_config_with_stream(**kwargs) -> Config: "hls": [ { "enabled": True, - "mount": "hls/main", - "format": "mpegts", + "manifest": "main", "streams": [ { - "fragment_prefix": "mp3low", + "segments_prefix": "mp3low", "format": "mpegts", "codec": "libmp3lame", - "bitrate": "64k", + "bitrate": "32k", + "sample_rate": "44100", }, { - "fragment_prefix": "mp3high", + "segments_prefix": "mp3high", "format": "mpegts", "codec": "libmp3lame", "bitrate": "128k", + "sample_rate": "44100", }, ], } @@ -113,20 +114,21 @@ def make_config_with_stream(**kwargs) -> Config: "hls": [ { "enabled": True, - "mount": "hls/main", - "format": "mpegts", + "manifest": "main", "streams": [ { - "fragment_prefix": "mp3low", + "segments_prefix": "mp3low", "format": "mpegts", "codec": "libmp3lame", - "bitrate": "64k", + "bitrate": "32k", + "sample_rate": "44100", }, { - "fragment_prefix": "mp3high", + "segments_prefix": "mp3high", "format": "mpegts", "codec": "libmp3lame", "bitrate": "128k", + "sample_rate": "44100", }, ], } From c7e95fda63c3320a068e7de0c849ef38649fa4b9 Mon Sep 17 00:00:00 2001 From: mp3butcher Date: Fri, 19 Jan 2024 23:05:34 +0100 Subject: [PATCH 26/26] update pytest snapshot --- .../__snapshots__/entrypoint_test.ambr | 113 +++++++++++++++++- 1 file changed, 110 insertions(+), 3 deletions(-) diff --git a/playout/tests/liquidsoap/__snapshots__/entrypoint_test.ambr b/playout/tests/liquidsoap/__snapshots__/entrypoint_test.ambr index 3c7f66ef87..1608a7f476 100644 --- a/playout/tests/liquidsoap/__snapshots__/entrypoint_test.ambr +++ b/playout/tests/liquidsoap/__snapshots__/entrypoint_test.ambr @@ -227,6 +227,113 @@ %include "/fake/1.4/ls_script.liq" + # hls:1 + + output_hls_1_source = s + + streams = ref([]) + + streamout = %ffmpeg(format="mpegts" , + codec="libmp3lame", + channels=2, + ar=44100, + b="32k") + streams := list.append([("mp3low", streamout)], !streams) + + streamout = %ffmpeg(format="mpegts" , + codec="libmp3lame", + channels=2, + ar=44100, + b="128k") + streams := list.append([("mp3high", streamout)], !streams) + + def segment_name(~position,~extname, stream_name) = + timestamp = int_of_float(time()) + duration = 2.0 + "#{stream_name}_#{duration}_#{timestamp}_#{position}.#{extname}" + end + + start_hls("main", + 2.0, + 5, + 5, + segment_name, + "/__w/libretime/libretime/playout", + streams, + output_hls_1_source) + + + + gateway("started") + + ''' +# --- +# name: test_generate_entrypoint[stream_config5-1.4] + ''' + # THIS FILE IS AUTO GENERATED. PLEASE DO NOT EDIT! + ########################################################### + # The ignore() lines are to squash unused variable warnings + + # Inputs + input_main_mount = "main" + input_main_port = 8001 + input_main_secure = false + input_show_mount = "show" + input_show_port = 8002 + input_show_secure = false + + # Settings + set("log.file.path", "/var/log/radio.log") + + set("server.telnet", true) + set("server.telnet.bind_addr", "127.0.0.1") + set("server.telnet.port", 1234) + + set("harbor.bind_addrs", ["0.0.0.0"]) + + station_name = interactive.string("station_name", "LibreTime") + + message_offline = interactive.string("message_offline", "LibreTime - offline") + message_format = interactive.string("message_format", "0") + input_fade_transition = interactive.float("input_fade_transition", 0.0) + + %include "/fake/1.4/ls_script.liq" + + # hls:1 + + output_hls_1_source = s + + streams = ref([]) + + streamout = %ffmpeg(format="mpegts" , + codec="libmp3lame", + channels=2, + ar=44100, + b="32k") + streams := list.append([("mp3low", streamout)], !streams) + + streamout = %ffmpeg(format="mpegts" , + codec="libmp3lame", + channels=2, + ar=44100, + b="128k") + streams := list.append([("mp3high", streamout)], !streams) + + def segment_name(~position,~extname, stream_name) = + timestamp = int_of_float(time()) + duration = 2.0 + "#{stream_name}_#{duration}_#{timestamp}_#{position}.#{extname}" + end + + start_hls("main", + 2.0, + 5, + 5, + segment_name, + "/__w/libretime/libretime/playout", + streams, + output_hls_1_source) + # icecast:1 output_icecast_1_source = s # Disable ogg metadata @@ -279,7 +386,7 @@ ''' # --- -# name: test_generate_entrypoint[stream_config5-1.4] +# name: test_generate_entrypoint[stream_config6-1.4] ''' # THIS FILE IS AUTO GENERATED. PLEASE DO NOT EDIT! ########################################################### @@ -327,7 +434,7 @@ ''' # --- -# name: test_generate_entrypoint[stream_config6-1.4] +# name: test_generate_entrypoint[stream_config7-1.4] ''' # THIS FILE IS AUTO GENERATED. PLEASE DO NOT EDIT! ########################################################### @@ -376,7 +483,7 @@ ''' # --- -# name: test_generate_entrypoint[stream_config7-1.4] +# name: test_generate_entrypoint[stream_config8-1.4] ''' # THIS FILE IS AUTO GENERATED. PLEASE DO NOT EDIT! ###########################################################