AzuraCast icon indicating copy to clipboard operation
AzuraCast copied to clipboard

Start/end dates not respected by Liquidsoap playlists

Open loics2 opened this issue 4 years ago • 6 comments

Using Docker installation method Yes

Host Operating System Ubuntu 18.04 (LTS), AzuraCast v0.9.9, #ef6393e (2020-03-22 22:40)

Describe the bug The start/end dates are not respected by Liquidsoap playlists when they have start/end dates and a day of the week configured.

To Reproduce Steps to reproduce the behavior:

  1. Create a new playlist with the following settings :
  • Interrupt other songs to play at scheduled time., Only loop through playlist once. and Merge playlist to play as a single track.
  • add a schedule with a start and end time, a start and end date, and check one day of the week.
  1. Restart the broadcasting to apply the settings

The playlist will play on the specified day of the week event though the end date is already passed

Expected behavior The playlist is not played when we're outside the scheduled period.

Relevant Logs There's nothing really interesting in the logs, but the generated Liquidsoap configuration does not have any information about the start and en date (which is not surprising because it's not supported)

Here's the Liquidsoap config, generated on a day outside the start/end dates :

# WARNING! This file is automatically generated by AzuraCast.
# Do not update it directly!


set("init.daemon", false)
set("init.daemon.pidfile.path","/var/azuracast/stations/ebull_radio/config/liquidsoap.pid")
set("log.stdout", true)
set("log.file", false)
set("server.telnet",true)
set("server.telnet.bind_addr","0.0.0.0")
set("server.telnet.port", 8004)
set("harbor.bind_addrs",["0.0.0.0"])

set("tag.encodings",["UTF-8","ISO-8859-1"])
set("encoder.encoder.export",["artist","title","album","song"])

setenv("TZ", "Europe/Zurich")


playlist_fete_au_balcon = playlist.once(id="playlist_fete_au_balcon",random=true,reload_mode="watch","/var/azuracast/stations/ebull_radio/playlists/playlist_fete_au_balcon.m3u")
playlist_fete_au_balcon = audio_to_stereo(id="stereo_playlist_fete_au_balcon", playlist_fete_au_balcon)
playlist_fete_au_balcon = cue_cut(id="cue_playlist_fete_au_balcon", playlist_fete_au_balcon)

playlist_tp2 = playlist(id="playlist_tp2",mode="randomize",reload_mode="watch",conservative=true,default_duration=10.,length=20.,"/var/azuracast/stations/ebull_radio/playlists/playlist_tp2.m3u")
playlist_tp2 = audio_to_stereo(id="stereo_playlist_tp2", playlist_tp2)
playlist_tp2 = cue_cut(id="cue_playlist_tp2", playlist_tp2)

playlist_tp3 = playlist.once(id="playlist_tp3",random=true,reload_mode="watch","/var/azuracast/stations/ebull_radio/playlists/playlist_tp3.m3u")
playlist_tp3 = audio_to_stereo(id="stereo_playlist_tp3", playlist_tp3)
playlist_tp3 = cue_cut(id="cue_playlist_tp3", playlist_tp3)

playlist_chill_tony_jay = playlist(id="playlist_chill_tony_jay",mode="randomize",reload_mode="watch",conservative=true,default_duration=10.,length=20.,"/var/azuracast/stations/ebull_radio/playlists/playlist_chill_tony_jay.m3u")
playlist_chill_tony_jay = audio_to_stereo(id="stereo_playlist_chill_tony_jay", playlist_chill_tony_jay)
playlist_chill_tony_jay = cue_cut(id="cue_playlist_chill_tony_jay", playlist_chill_tony_jay)

playlist_tp4 = playlist.once(id="playlist_tp4",random=true,reload_mode="watch","/var/azuracast/stations/ebull_radio/playlists/playlist_tp4.m3u")
playlist_tp4 = audio_to_stereo(id="stereo_playlist_tp4", playlist_tp4)
playlist_tp4 = cue_cut(id="cue_playlist_tp4", playlist_tp4)

playlist_general = playlist(id="playlist_general",mode="randomize",reload_mode="watch",conservative=true,default_duration=10.,length=20.,"/var/azuracast/stations/ebull_radio/playlists/playlist_general.m3u")
playlist_general = audio_to_stereo(id="stereo_playlist_general", playlist_general)
playlist_general = cue_cut(id="cue_playlist_general", playlist_general)

playlist_jingles = playlist(id="playlist_jingles",mode="randomize",reload_mode="watch",conservative=true,default_duration=10.,length=20.,"/var/azuracast/stations/ebull_radio/playlists/playlist_jingles.m3u")
playlist_jingles = audio_to_stereo(id="stereo_playlist_jingles", playlist_jingles)
playlist_jingles = cue_cut(id="cue_playlist_jingles", playlist_jingles)
playlist_jingles = drop_metadata(playlist_jingles)

# Standard Playlists
radio = random(id="ebull_radio_standard_playlists", weights=[3], [playlist_general])

# Standard Schedule Switches
radio = switch(id="ebull_radio_schedule_switch", track_sensitive=true, [ ({ (5w) and 18h0m-20h0m }, playlist_fete_au_balcon), ({ (0w) and 16h0m-18h0m }, playlist_tp2), ({ (1w) and 14h15m-15h20m }, playlist_chill_tony_jay), ({ (4w) and 16h0m-18h0m }, playlist_tp4), ({true}, radio) ])

# Once per x Songs Playlists
radio = rotate(weights=[1,8], [playlist_jingles, radio])

# AutoDJ Next Song Script
def azuracast_next_song() =
    uri = list.hd(get_process_lines("curl -s --request POST --url http://web/api/internal/1/nextsong --form api_auth="^string.quote("de2de27bbc88cf83a552aaa0247df8880749fae456684db9b7e471b8b9447e3ec2185185a498a4193043a2f3ba835529066b")^""), default="")
    log("AzuraCast Raw Response: #{uri}")
    
    if uri == "" or string.match(pattern="Error", uri) then
        log("AzuraCast Error: Delaying subsequent requests...")
        system("sleep 2")
        request.create("")
    else
        request.create(uri)
    end
end

dynamic = request.dynamic(id="ebull_radio_next_song", timeout=20., azuracast_next_song)
dynamic = audio_to_stereo(id="ebull_radio_stereo_next_song", dynamic)
dynamic = cue_cut(id="ebull_radio_cue_next_song", dynamic)
radio = fallback(id="ebull_radio_autodj_fallback", track_sensitive = true, [dynamic, radio])

# Interrupting Schedule Switches
radio = switch(id="ebull_radio_interrupt_switch", track_sensitive=false, [ ({ (2w) and 16h0m-18h0m }, playlist_tp3), ({true}, radio) ])

requests = request.queue(id="ebull_radio_requests")
requests = audio_to_stereo(id="ebull_radio_stereo_requests", requests)
requests = cue_cut(id="ebull_radio_cue_requests", requests)
radio = fallback(id="ebull_radio_requests_fallback", track_sensitive = true, [requests, radio])

add_skip_command(radio)

radio = fallback(id="ebull_radio_safe_fallback", track_sensitive = false, [radio, single(id="error_jingle", "/usr/local/share/icecast/web/error.mp3")])

radio = crossfade(smart=true, duration=3.,fade_out=2.,fade_in=2.,radio)

# DJ Authentication
live_enabled = ref false
last_authenticated_dj = ref ""
live_dj = ref ""

def dj_auth(auth_user,auth_pw) =
    user = ref ""
    password = ref ""
  
    if (auth_user == "source" or auth_user == "") and (string.match(pattern="(:|,)+", auth_pw)) then
        auth_string = string.split(separator="(:|,)", auth_pw)
        
        user := list.nth(default="", auth_string, 0)
        password := list.nth(default="", auth_string, 2)
    else
        user := auth_user
        password := auth_pw
    end
    
    log("Authenticating DJ: #{!user}")
    
    ret = list.hd(get_process_lines("curl -s --request POST --url http://web/api/internal/1/auth --form dj-user="^string.quote(!user)^" --form dj-password="^string.quote(!password)^" --form api_auth="^string.quote("de2de27bbc88cf83a552aaa0247df8880749fae456684db9b7e471b8b9447e3ec2185185a498a4193043a2f3ba835529066b")^""), default="")
    log("AzuraCast DJ Auth Response: #{ret}")
    
    authed = bool_of_string(ret)
    if (authed) then
        last_authenticated_dj := !user
    end
    
    authed
end

def live_connected(header) =
    dj = !last_authenticated_dj
    log("DJ Source connected! Last authenticated DJ: #{dj} - #{header}")
    
    live_enabled := true
    live_dj := dj
    
    ret = list.hd(get_process_lines("curl -s --request POST --url http://web/api/internal/1/djon --form dj-user="^string.quote(dj)^" --form api_auth="^string.quote("de2de27bbc88cf83a552aaa0247df8880749fae456684db9b7e471b8b9447e3ec2185185a498a4193043a2f3ba835529066b")^""), default="")
    log("AzuraCast Live Connected Response: #{ret}")
end

def live_disconnected() = 
    dj = !live_dj
    
    log("DJ Source disconnected! Current live DJ: #{dj}")
    
    ret = list.hd(get_process_lines("curl -s --request POST --url http://web/api/internal/1/djoff --form dj-user="^string.quote(dj)^" --form api_auth="^string.quote("de2de27bbc88cf83a552aaa0247df8880749fae456684db9b7e471b8b9447e3ec2185185a498a4193043a2f3ba835529066b")^""), default="")
    log("AzuraCast Live Disconnected Response: #{ret}")
    
    live_enabled := false
    last_authenticated_dj := ""
    live_dj := ""
end 

# A Pre-DJ source of radio that can be broadcast if needed
radio_without_live = radio
ignore(radio_without_live)

# Live Broadcasting
live = audio_to_stereo(input.harbor("/", id = "ebull_radio_input_streamer", port = 8005, auth = dj_auth, icy = true, icy_metadata_charset = "UTF-8", metadata_charset = "UTF-8", on_connect = live_connected, on_disconnect = live_disconnected, buffer = 5., max = 10.))
ignore(output.dummy(live, fallible=true))

radio = fallback(id="ebull_radio_live_fallback", track_sensitive=false, [live, radio])

# Record Live Broadcasts
stop_recording_f = ref (fun () -> ())

def start_recording(path) =
    output_live_recording = output.file(%mp3(samplerate=44100, stereo=true, bitrate=128, id3v2=true), fallible=true, reopen_on_metadata=false, "#{path}", live)
    stop_recording_f := fun () -> source.shutdown(output_live_recording)
end

def stop_recording() =
    f = !stop_recording_f
    f ()
    
    stop_recording_f := fun () -> ()
end

server.register(namespace="recording", description="Start recording.", usage="recording.start filename", "start", fun (s) -> begin start_recording(s) "Done!" end)
server.register(namespace="recording", description="Stop recording.", usage="recording.stop", "stop", fun (s) -> begin stop_recording() "Done!" end)

# Allow for Telnet-driven insertion of custom metadata.
radio = server.insert_metadata(id="custom_metadata", radio)

# Apply amplification metadata (if supplied)
radio = amplify(override="liq_amplify", 1., radio)

# Normalization and Compression
radio = normalize(target = 0., window = 0.03, gain_min = -16., gain_max = 0., radio)
radio = compress.exponential(radio, mu = 1.0)

# Send metadata changes back to AzuraCast
def metadata_updated(m) =
    if (m["song_id"] != "") then
        ret = list.hd(get_process_lines("curl -s --request POST --url http://web/api/internal/1/feedback --form song="^string.quote(m["song_id"])^" --form media="^string.quote(m["media_id"])^" --form playlist="^string.quote(m["playlist_id"])^" --form api_auth="^string.quote("de2de27bbc88cf83a552aaa0247df8880749fae456684db9b7e471b8b9447e3ec2185185a498a4193043a2f3ba835529066b")^""), default="")
        log("AzuraCast Feedback Response: #{ret}")
    end
end

radio = on_metadata(metadata_updated,radio)

# Local Broadcasts
output.icecast(%mp3(samplerate=44100, stereo=true, bitrate=128, id3v2=true), id="ebull_radio_local_1", host = "127.0.0.1", port = 8000, password = "yz34tKCC", mount = "/radio.mp3", name = "Ebull Radio", description = "", genre = "", url = "http://www.ebull.ch", public = false, encoding = "UTF-8", radio)

# Remote Relays

playlist_tp2 has an end date that's already passed, but the it's still playing on the specified day of the week.

Device(s): All platforms

Additional context I have a cron restarting the broadcasting very day, so refreshing Liquidsoap is not the issue I think.

A workaround is simply to disable the playlist and restart broadcasting, but it's 1) manual and 2) needs a brodcast restart, wich is not great.

loics2 avatar Apr 17 '20 15:04 loics2

@loics2 This is correct; if you're using the AzuraCast AutoDJ, it can properly keep the playlist only playing within the specified date range, but if you use Liquidsoap in manual mode (as you do when you have the advanced AutoDJ settings turned on), it depends on Liquidsoap's configuration to do this, and currently, the Liquidsoap configuration doesn't limit by a start and end date.

Honestly, the reason for this is because I simply don't know how to accomplish that in the Liquidsoap scripting language, and I couldn't find any documentation online about how to do it. I wanted to roll out the feature for the AzuraCast AutoDJ users (the majority of users) but meant to follow up with the LS team to ask them how (if it's at all possible) to actually implement a start and end date boundary for switches in Liquidsoap configuration, but I haven't gotten around to it yet.

BusterNeece avatar Apr 18 '20 04:04 BusterNeece

An easy solution could just be to remove the playlist from the generated Liquidsoap config. It would still need frequent broadcast restart, but still better than nothing. I don't really know how Liquidsoap work for now, so I don't know if there's another solution.

I'm slowly digging into the source code of Azuracast, so I could try to implement this simple solution if you're interested.

loics2 avatar Apr 18 '20 13:04 loics2

@loics2 I wouldn't necessarily count that as an actual solution. Really I just need to ask the Liquidsoap folks about how to accomplish this, and then draw conclusions based on that.

BusterNeece avatar Apr 18 '20 14:04 BusterNeece

Just for my own documentation purposes, the response from the LS Slack was:

you may need to write your own function to act as a predicate def checkDate() ... end switch([{checkDate()}, source), ...]) convert your date range to unix seconds and use gettimeofday()

It appears there's no out-of-the-box way of doing this, so we'll have to add the ability to write that kind of function to our configuration generator. We'll get around to it as soon as we can. In the meantime, we may simply remove those specific schedule entries.

BusterNeece avatar Apr 18 '20 23:04 BusterNeece

Start/end dates and hours not working if you don't touch to advanced settings or activate manual auto dj mode. We are experiencing difficulties with playlist schedulings too.

onurk7 avatar Apr 20 '20 02:04 onurk7

@radiolize This issue is not related to whether scheduled times operate correctly with the AzuraCast AutoDJ, this is specifically an upstream issue with the Liquidsoap scripting that we're working on separately.

For issues with the scheduling issue, please see #2631, and please supply ample documentation of your playlists, their settings, and what the station playback queue is showing.

BusterNeece avatar Apr 20 '20 02:04 BusterNeece