Closed RM-FM closed 3 months ago
Thanks for the report! For reference, a copy of the AzuraCast script would help a lot as we are not responsible not knowledgable of its content.
Yeah, I'm gonna need a little more details around the stereotool integration. This simple reproduction script works fine:
s = playlist("~/sources/test-stream/audio")
s =
stereotool(
library_file="/path/to/shared/lib",
s
)
output.ao(fallible=true, s)
Thanks for the report! For reference, a copy of the AzuraCast script would help a lot as we are not responsible not knowledgable of its content.
# WARNING! This file is automatically generated by AzuraCast.
# Do not update it directly!
init.daemon.set(false)
init.daemon.pidfile.path.set("/var/azuracast/stations/test230/config/liquidsoap.pid")
log.stdout.set(true)
log.file.set(false)
settings.server.log.level.set(4)
settings.server.socket.set(true)
settings.server.socket.permissions.set(0o660)
settings.server.socket.path.set("/var/azuracast/stations/test230/config/liquidsoap.sock")
settings.harbor.bind_addrs.set(["0.0.0.0"])
settings.encoder.metadata.export.set(["artist","title","album","song"])
environment.set("TZ", "UTC")
autodj_is_loading = ref(true)
ignore(autodj_is_loading)
autodj_ping_attempts = ref(0)
ignore(autodj_ping_attempts)
# Track live-enabled status.
live_enabled = ref(false)
ignore(live_enabled)
# Track live transition for crossfades.
to_live = ref(false)
ignore(to_live)
# Reimplement LS's now-deprecated drop_metadata function.
def drop_metadata(~id=null(), s)
let {metadata=_, ...tracks} = source.tracks(s)
source(id=id, tracks)
end
# Transport for HTTPS outputs.
https_transport = http.transport.ssl()
ignore(https_transport)
azuracast_api_url = "http://127.0.0.1:6010/api/internal/2/liquidsoap"
azuracast_api_key = "(PASSWORD)"
def azuracast_api_call(~timeout=2.0, url, payload) =
full_url = "#{azuracast_api_url}/#{url}"
log("API #{url} - Sending POST request to '#{full_url}' with body: #{payload}")
try
response = http.post(full_url,
headers=[
("Content-Type", "application/json"),
("User-Agent", "Liquidsoap AzuraCast"),
("X-Liquidsoap-Api-Key", "#{azuracast_api_key}")
],
timeout=timeout,
data=payload
)
log("API #{url} - Response (#{response.status_code}): #{response}")
"#{response}"
catch err do
log("API #{url} - Error: #{error.kind(err)} - #{error.message(err)}")
"false"
end
end
station_media_dir = "/var/azuracast/stations/test230/media"
def azuracast_media_protocol(~rlog=_,~maxtime=_,arg) =
"#{station_media_dir}/#{arg}"
end
protocol.add(
"media",
azuracast_media_protocol,
doc="Pull files from AzuraCast media directory.",
syntax="media:uri"
)
# AutoCue
%include "/var/azuracast/autocue/autocue.liq"
settings.autocue.cue_file.nice := true
settings.request.prefetch := 2
# Custom Configuration (Specified in Station Profile)
settings.autocue.cue_file.fade_out := 6.0 # seconds
settings.autocue.cue_file.target := -16.0 # LUFS#
settings.autocue.cue_file.sustained_loudness_drop := 40.0
settings.autocue.cue_file.overlay_longtail := -12.0 # extra LU
# Ensure AutoCue settings are valid
ignore(check_autocue_setup(shutdown=true, print=false))
playlist_default = playlist(id="playlist_default",mime_type="audio/x-mpegurl",mode="random",reload_mode="watch","/var/azuracast/stations/test230/playlists/playlist_default.m3u")
playlist_jingles = playlist(id="playlist_jingles",mime_type="audio/x-mpegurl",mode="normal",reload_mode="watch","/var/azuracast/stations/test230/playlists/playlist_jingles.m3u")
playlist_jingles = drop_metadata(playlist_jingles)
# Standard Playlists
radio = random(id="standard_playlists", weights=[3], [playlist_default])
# Once per x Songs Playlists
radio = rotate(weights=[1,2], [playlist_jingles, radio])
# AutoDJ Next Song Script
def autodj_next_song() =
response = azuracast_api_call(
"nextsong",
""
)
if (response == "") or (response == "false") then
null()
else
request.create(response)
end
end
# Delayed ping for AutoDJ Next Song
def wait_for_next_song(autodj)
autodj_ping_attempts.set(autodj_ping_attempts() + 1)
if source.is_ready(autodj) then
log("AutoDJ is ready!")
autodj_is_loading.set(false)
-1.0
elsif autodj_ping_attempts() > 200 then
log("AutoDJ could not be initialized within the specified timeout.")
autodj_is_loading.set(false)
-1.0
else
0.5
end
end
dynamic = request.dynamic(id="next_song", timeout=settings.autocue.cue_file.timeout(), retry_delay=10., autodj_next_song)
dynamic_startup = fallback(
id = "dynamic_startup",
track_sensitive = false,
[
dynamic,
source.available(
blank(id = "autodj_startup_blank", duration = 120.),
predicate.activates({autodj_is_loading()})
)
]
)
radio = fallback(id="autodj_fallback", track_sensitive = true, [dynamic_startup, radio])
ref_dynamic = ref(dynamic);
thread.run.recurrent(delay=0.25, { wait_for_next_song(ref_dynamic()) })
requests = request.queue(id="requests", timeout=settings.autocue.cue_file.timeout())
radio = fallback(id="requests_fallback", track_sensitive = true, [requests, radio])
interrupting_queue = request.queue(id="interrupting_requests", timeout=settings.autocue.cue_file.timeout())
radio = fallback(id="interrupting_fallback", track_sensitive = false, [interrupting_queue, radio])
# Skip command (used by web UI)
def add_skip_command(s) =
def skip(_) =
source.skip(s)
"Done!"
end
server.register(namespace="radio", usage="skip", description="Skip the current song.", "skip",skip)
end
add_skip_command(radio)
# Apply amplification metadata (if supplied)
radio = amplify(override="liq_amplify", 1., radio)
# Show metadata in log (Debug)
def show_meta(m)
label="show_meta"
l = list.sort.natural(metadata.cover.remove(m))
list.iter(fun(v) -> log(level=4, label=label, "#{v}"), l)
nowplaying = ref(m["artist"] ^ " - " ^ m["title"])
if m["artist"] == "" then
if string.contains(substring=" - ", m["title"]) then
let (a, t) = string.split.first(separator=" - ", m["title"])
nowplaying := a ^ " - " ^ t
end
end
# show `liq_` & other metadata in level 3
def fl(k, _) =
tags = ["duration", "media_id", "replaygain_track_gain", "replaygain_reference_loudness"]
string.contains(prefix="liq_", k) or list.mem(k, tags)
end
liq = list.assoc.filter((fl), l)
list.iter(fun(v) -> log(level=3, label=label, "#{v}"), liq)
log(level=3, label=label, "Now playing: #{nowplaying()}")
if m["liq_amplify"] == "" then
log(level=2, label=label, "Warning: No liq_amplify found, expect loudness jumps!")
end
if m["liq_blank_skipped"] == "true" then
log(level=2, label=label, "Blank (silence) detected in track, ending early.")
end
end
radio.on_metadata(show_meta)
# Fading/crossing/segueing
def live_aware_crossfade(old, new) =
if to_live() then
# If going to the live show, play a simple sequence
# fade out AutoDJ, do (almost) not fade in streamer
sequence([
fade.out(duration=settings.autocue.cue_file.fade_out(), old.source),
fade.in(duration=settings.autocue.cue_file.fade_in(), new.source)
])
else
# Otherwise, use a beautiful add
add(normalize=false, [
fade.in(
initial_metadata=new.metadata,
duration=settings.autocue.cue_file.fade_in(),
new.source
),
fade.out(
initial_metadata=old.metadata,
duration=settings.autocue.cue_file.fade_out(),
old.source
)
])
end
end
radio = cross(
duration=settings.autocue.cue_file.fade_out(),
live_aware_crossfade,
radio
)
# Stereo Tool Pipe
radio = stereotool(
library_file="/var/azuracast/storage/stereo_tool/libStereoTool_intel64.so",
license_key="(LICENSE_KEY)",
preset="/var/azuracast/stations/test230/config/stereo-tool.sts",
radio
)
# Allow for Telnet-driven insertion of custom metadata.
radio = server.insert_metadata(id="custom_metadata", radio)
error_file = single(id="error_jingle", "/usr/local/share/icecast/web/error.mp3")
def tag_error_file(m) =
ignore(m)
[("is_error_file", "true")]
end
error_file = metadata.map(tag_error_file, error_file)
radio = fallback(id="safe_fallback", track_sensitive = false, [radio, error_file])
# Send metadata changes back to AzuraCast
last_title = ref("")
last_artist = ref("")
def metadata_updated(m) =
def f() =
if (m["is_error_file"] != "true") then
if (m["title"] != last_title() or m["artist"] != last_artist()) then
last_title.set(m["title"])
last_artist.set(m["artist"])
# Only send some metadata to AzuraCast
def fl(k, _) =
tags = ["song_id", "media_id", "playlist_id", "artist", "title"]
string.contains(prefix="liq_", k) or string.contains(prefix="replaygain_", k) or list.mem(k, tags)
end
feedback_meta = list.assoc.filter((fl), metadata.cover.remove(m))
j = json()
for item = list.iterator(feedback_meta) do
let (tag, value) = item
j.add(tag, value)
end
_ = azuracast_api_call(
"feedback",
json.stringify(compact=true, j)
)
end
end
end
thread.run(f)
end
radio.on_metadata(metadata_updated)
# Handle "Jingle Mode" tracks by replaying the previous metadata.
last_metadata = ref([])
def handle_jingle_mode(m) =
if (m["jingle_mode"] == "true") then
last_metadata()
else
last_metadata.set(m)
m
end
end
radio = metadata.map(update=false, strip=true, handle_jingle_mode, radio)
# Local Broadcasts
output.icecast(%mp3(samplerate=44100, stereo=true, bitrate=320), id="local_1", host = "127.0.0.1", port = 8000, password = "(PASSWORD)", mount = "/radio.mp3", name = "Test230", description = "", genre = "", public = false, encoding = "UTF-8", radio)
# Remote Relays
I can confirm that the issue has been resolved in the fix-empty-frame
branch.
That's awesome. Let me get another pass at cleaning the base fix and we should be good to go.
Description
Stereotool (10.30) doesn't seem to work with Azuracast (branch
dev-ls2.3.x
). Based on the error messages, it appears the issue is related to LS 2.3.0 rather than Azuracast.Azuracast discussion thread: https://github.com/AzuraCast/AzuraCast/discussions/7354
Steps to reproduce
Expected behavior
Steretool 10.30 to work with Liquidsoap 2.3.x RR
Liquidsoap version
Liquidsoap build config
Installation method
From official packages in the release artifacts
Additional Info
No response