So we're using Liquidsoap (with Airtime, but the Airtime piece isn't really
relevant to my question) to run a station that is going out to an FM and FM
HD translator as well. In order to do that, I have to run legal IDs at the
top of the hour every hour and run repeater IDs at three pretty specific
windows during the day. What I've done, following Kevin's suggestion on
this list, is create a script that builds hour-long and 30-minute-long
playlists for our regular programming and for specialty shows during the
day. The legal ID is the very first item on each playlist, and I'm using a
switch to do the scheduling.

 It looks, however, like my legal IDs aren't always hitting when the switch
triggers at the top of the hour. It's almost like when it does the switch,
it's popping into the middle of the playlist isntead of the front.

I'm suspecting it has something to do with how often the reload hits the
playlists in the liquidsoap script, but I'm not sure. I'm attaching the
ls_script.liq I'm using to do this, and I'm also attaching the python
script I use to build the playlists just in case anyone is interested.

Does anyone have any ideas?
-- 
Chris Muldrow
Chief Digital Officer
The Free Lance-Star Companies
[email protected]
540-368-5006
%include "/etc/airtime/liquidsoap.cfg"

set("log.file.path", log_file)
set("log.stdout", true)
set("server.telnet", true)
set("server.telnet.port", 1234)
set("init.daemon.pidfile.path", "/var/run/airtime-liquidsoap.pid")

%include "library/pervasives.liq"

#Dynamic source list
#dyn_sources = ref []
webstream_enabled = ref false

time = ref string_of(gettimeofday())

#live stream setup
set("harbor.bind_addr", "0.0.0.0")

current_dyn_id = ref '-1'

pypo_data = ref '0'
stream_metadata_type = ref 0
default_dj_fade = ref 0.
station_name = ref ''
show_name = ref ''

s1_connected = ref ''
s2_connected = ref ''
s3_connected = ref ''
s1_namespace = ref ''
s2_namespace = ref ''
s3_namespace = ref ''
just_switched = ref false

%include "ls_lib.liq"

queue = audio_to_stereo(id="queue_src", request.equeue(id="queue", length=0.5))
queue = cue_cut(queue)
queue = amplify(1., override="replay_gain", queue)

# the crossfade function controls fade in/out
queue = crossfade_airtime(queue)
queue = on_metadata(notify, queue)
output.dummy(fallible=true, queue)

http = input.http_restart(id="http")
http = cross_http(http_input_id="http",http)
output.dummy(fallible=true, http)
stream_queue = http_fallback(http_input_id="http", http=http, default=queue)
stream_queue = map_metadata(update=false, append_title, stream_queue)

ignore(output.dummy(stream_queue, fallible=true))

server.register(namespace="vars",
                "pypo_data",
                fun (s) -> begin log("vars.pypo_data") pypo_data := s "Done" 
end)
server.register(namespace="vars",
                "stream_metadata_type",
                fun (s) -> begin log("vars.stream_metadata_type") 
stream_metadata_type := int_of_string(s) s end)
server.register(namespace="vars",
                "show_name",
                fun (s) -> begin log("vars.show_name") show_name := s s end)
server.register(namespace="vars",
                "station_name",
                fun (s) -> begin log("vars.station_name") station_name := s s 
end)
server.register(namespace="vars",
                "bootup_time",
                fun (s) -> begin log("vars.bootup_time") time := s s end)
server.register(namespace="streams",
                "connection_status",
                fun (s) -> begin log("streams.connection_status") 
"1:#{!s1_connected},2:#{!s2_connected},3:#{!s3_connected}" end)
server.register(namespace="vars",
                "default_dj_fade",
                fun (s) -> begin log("vars.default_dj_fade") default_dj_fade := 
float_of_string(s) s end)

server.register(namespace="dynamic_source",
                description="Enable webstream output",
                usage='start',
                "output_start",
                fun (s) -> begin log("dynamic_source.output_start")
                    notify([("schedule_table_id", !current_dyn_id)])
                    webstream_enabled := true "enabled" end)
server.register(namespace="dynamic_source",
                description="Enable webstream output",
                usage='stop',
                "output_stop",
                fun (s) -> begin log("dynamic_source.output_stop") 
webstream_enabled := false "disabled" end)

server.register(namespace="dynamic_source",
                description="Set the streams cc_schedule row id",
                usage="id <id>",
                "id",
                fun (s) -> begin log("dynamic_source.id") 
set_dynamic_source_id(s) end)

server.register(namespace="dynamic_source",
                description="Get the streams cc_schedule row id",
                usage="get_id",
                "get_id",
                fun (s) -> begin log("dynamic_source.get_id") 
get_dynamic_source_id() end)

#server.register(namespace="dynamic_source",
#                description="Start a new dynamic source.",
#                usage="start <uri>",
#                "read_start",
#                fun (uri) -> begin log("dynamic_source.read_start") 
begin_stream_read(uri) end)
#server.register(namespace="dynamic_source",
#                description="Stop a dynamic source.",
#                usage="stop <id>",
#                "read_stop",
#                fun (s) -> begin log("dynamic_source.read_stop") 
stop_stream_read(s) end)

#server.register(namespace="dynamic_source",
#                description="Stop a dynamic source.",
#                usage="stop <id>",
#                "read_stop_all",
#                fun (s) -> begin log("dynamic_source.read_stop") 
destroy_dynamic_source_all() end)

# default = amplify(id="silence_src", 0.00001, noise())
# silence = amplify(id="silence_src", 0.00001, noise())
ref_off_air_meta = ref off_air_meta
if !ref_off_air_meta == "" then
    ref_off_air_meta := "Airtime - offline"
end
bigbandwednesday = playlist(mode="normal", prefix="replay_gain:",  reload=3570, 
"/mnt/musicMount/playlists/bigbandwednesday.m3u")
bigbandwednesday2 = playlist(mode="normal", prefix="replay_gain:",  
reload=3570, "/mnt/musicMount/playlists/bigbandwednesday2.m3u")
jazzsaturday = playlist(mode="normal", prefix="replay_gain:",  reload=3570, 
"/mnt/musicMount/playlists/jazzsaturday.m3u")
jazzsaturday2 = playlist(mode="normal", prefix="replay_gain:",  reload=3570, 
"/mnt/musicMount/playlists/jazzsaturday2.m3u")
timeless2 = playlist(mode="normal", prefix="replay_gain:",  reload=3570, 
"/mnt/musicMount/playlists/timeless2.m3u")
timeless = playlist(mode="normal", prefix="replay_gain:",  reload=3570, 
"/mnt/musicMount/playlists/timeless.m3u")
bluegrassthursday = playlist(mode="normal", prefix="replay_gain:",  
reload=3570, "/mnt/musicMount/playlists/bluegrassthursday.m3u")
bluegrassthursday2 = playlist(mode="normal", prefix="replay_gain:",  
reload=3570, "/mnt/musicMount/playlists/bluegrassthursday2.m3u")
soulfriday = playlist(mode="normal", prefix="replay_gain:",  reload=3570, 
"/mnt/musicMount/playlists/soulfriday.m3u")
soulfriday2 = playlist(mode="normal", prefix="replay_gain:",  reload=3570, 
"/mnt/musicMount/playlists/soulfriday2.m3u")
timelessmorningsBOH = playlist(mode="normal", prefix="replay_gain:",  
reload=1770, "/mnt/musicMount/playlists/timelessmorningsBOH.m3u")
bluemonday = playlist(mode="normal", prefix="replay_gain:",  reload=3570, 
"/mnt/musicMount/playlists/bluemonday.m3u")
bluemonday2 = playlist(mode="normal", prefix="replay_gain:",  reload=3570, 
"/mnt/musicMount/playlists/bluemonday2.m3u")
soundsofpraise = playlist(mode="normal", prefix="replay_gain:",  reload=3570, 
"/mnt/musicMount/playlists/soundsofpraise.m3u")
soundsofpraise2 = playlist(mode="normal", prefix="replay_gain:",  reload=3570, 
"/mnt/musicMount/playlists/soundsofpraise2.m3u")
timelessmorningsLEGALID700 = playlist(mode="normal", prefix="replay_gain:",  
reload=1770, "/mnt/musicMount/playlists/timelessmorningsLEGALID700.m3u")
#gospelsunday = playlist(mode="normal", prefix="replay_gain:",  reload=3570, 
"/mnt/musicMount/playlists/gospelsunday.m3u")
#gospelsunday2 = playlist(mode="normal", prefix="replay_gain:",  reload=3570, 
"/mnt/musicMount/playlists/gospelsunday2.m3u")
timelessdrive = playlist(mode="normal", prefix="replay_gain:",  reload=3570, 
"/mnt/musicMount/playlists/timelessdrive.m3u")
timelessdrive2 = playlist(mode="normal", prefix="replay_gain:",  reload=3570, 
"/mnt/musicMount/playlists/timelessdrive2.m3u")
timelessmorningsTOH = playlist(mode="normal", prefix="replay_gain:",  
reload=1770, "/mnt/musicMount/playlists/timelessmorningsTOH.m3u")
honkytonktuesday = playlist(mode="normal", prefix="replay_gain:",  reload=3570, 
"/mnt/musicMount/playlists/honkytonktuesday.m3u")
honkytonktuesday2 = playlist(mode="normal", prefix="replay_gain:",  
reload=3570, "/mnt/musicMount/playlists/honkytonktuesday2.m3u")
timelessLEGALID1255 = playlist(mode="normal", prefix="replay_gain:",  
reload=1770, "/mnt/musicMount/playlists/timelessLEGALID1255.m3u")
#timelessLEGALID1255_2 = playlist(mode="normal", prefix="replay_gain:",  
reload=3570, "/mnt/musicMount/playlists/timelessLEGALID1255_2.m3u")
timelessmusic = mksafe(playlist(mode='randomize', 
"/mnt/musicMount/watch/timeless/"))

default = switch([
({0h-0h59m}, timeless),
({1h-1h59m}, timeless2),
({2h-2h59m}, timeless),
({3h-3h59m}, timeless2),
({4h-4h59m}, timeless),
({5h-5h59m}, timeless2),
({6h-6h29m}, timelessmorningsTOH),
({6h30m-6h59m}, timelessmorningsBOH),
({7h-7h29m}, timelessmorningsLEGALID700),
({7h30m-7h59m}, timelessmorningsBOH),
({8h-8h29m}, timelessmorningsLEGALID700),
({8h30m-8h59m}, timelessmorningsBOH),
({9h-9h59m}, timeless),
({10h-10h59m}, timeless2),
({11h-11h59m}, timeless),
({12h-12h54m}, timeless2),
({12h55m-13h5m}, timelessLEGALID1255),
({13h6m-13h59m}, timeless),
({14h-14h59m}, timeless2),
({15h-15h59m}, timeless),
({16h-16h59m}, timelessdrive),
({17h-17h59m}, timelessdrive2),
({18h-18h59m}, timelessdrive),
({ (1w) and 19h-19h59m}, bluemonday),
({ (1w) and 20h-20h59m}, bluemonday2),
({ (1w) and 21h-21h59m}, bluemonday),
({ (1w) and 22h-22h59m}, bluemonday2),
({ (1w) and 23h-23h59m}, bluemonday),
({ (2w) and 19h-19h59m}, honkytonktuesday),
({ (2w) and 20h-20h59m}, honkytonktuesday2),
({ (2w) and 21h-21h59m}, honkytonktuesday),
({ (2w) and 22h-22h59m}, honkytonktuesday2),
({ (2w) and 23h-23h59m}, honkytonktuesday),
({ (3w) and 19h-19h59m}, bigbandwednesday),
({ (3w) and 20h-20h59m}, bigbandwednesday2),
({ (3w) and 21h-21h59m}, bigbandwednesday),
({ (3w) and 22h-22h59m}, bigbandwednesday2),
({ (3w) and 23h-23h59m}, bigbandwednesday),
({ (4w) and 19h-19h59m}, bluegrassthursday),
({ (4w) and 20h-20h59m}, bluegrassthursday2),
({ (4w) and 21h-21h59m}, bluegrassthursday),
({ (4w) and 22h-22h59m}, bluegrassthursday2),
({ (4w) and 23h-23h59m}, bluegrassthursday),
({ (5w) and 19h-19h59m}, soulfriday),
({ (5w) and 20h-20h59m}, soulfriday2),
({ (5w) and 21h-21h59m}, soulfriday),
({ (5w) and 22h-22h59m}, soulfriday2),
({ (5w) and 23h-23h59m}, soulfriday),
({ (6w) and 19h-19h59m}, jazzsaturday),
({ (6w) and 20h-20h59m}, jazzsaturday2),
({ (6w) and 21h-21h59m}, jazzsaturday),
({ (6w) and 22h-22h59m}, jazzsaturday2),
({ (6w) and 23h-23h59m}, jazzsaturday),
({ (7w) and 19h-19h59m}, soundsofpraise),
({ (7w) and 20h-20h59m}, soundsofpraise2),
({ (7w) and 21h-21h59m}, soundsofpraise),
({ (7w) and 22h-22h59m}, soundsofpraise2),
({ (7w) and 23h-23h59m}, soundsofpraise),
({true}, timelessmusic)
])


# Let's play!
default=mksafe(default)
default = amplify(1.,override="replay_gain",default)

master_dj_enabled = ref false
live_dj_enabled = ref false
scheduled_play_enabled = ref false

def make_master_dj_available()
    master_dj_enabled := true
end

def make_master_dj_unavailable()
    master_dj_enabled := false
end

def make_live_dj_available()
    live_dj_enabled := true
end

def make_live_dj_unavailable()
    live_dj_enabled := false
end

def make_scheduled_play_available()
    scheduled_play_enabled := true
    just_switched := true
end

def make_scheduled_play_unavailable()
    scheduled_play_enabled := false
end

def update_source_status(sourcename, status) =
    command = "/usr/lib/airtime/pypo/bin/liquidsoap_scripts/notify.sh 
--source-name=#{sourcename} --source-status=#{status} &"
    system(command)
    log(command)
end

def live_dj_connect(header) =
    update_source_status("live_dj", true)
end

def live_dj_disconnect() =
    update_source_status("live_dj", false)
end

def master_dj_connect(header) =
    update_source_status("master_dj", true)
end

def master_dj_disconnect() =
    update_source_status("master_dj", false)
end

#auth function for live stream
def check_master_dj_client(user,password) =
    log("master connected")
    #get the output of the php script
    ret = get_process_lines("python 
/usr/lib/airtime/pypo/bin/liquidsoap_scripts/liquidsoap_auth.py --master 
#{user} #{password}")
    #ret has now the value of the live client (dj1,dj2, or djx), or 
"ERROR"/"unknown" ...
    ret = list.hd(ret)

    #return true to let the client transmit data, or false to tell harbor to 
decline
    ret == "True"
end

def check_dj_client(user,password) =
    log("live dj connected")
    #get the output of the php script
    ret = get_process_lines("python 
/usr/lib/airtime/pypo/bin/liquidsoap_scripts/liquidsoap_auth.py --dj #{user} 
#{password}")
    #ret has now the value of the live client (dj1,dj2, or djx), or 
"ERROR"/"unknown" ...
    hd = list.hd(ret)
    hd == "True"
end

s = switch(id="schedule_noise_switch",
            track_sensitive=false,
            transitions=[transition_default, transition],
            [({!scheduled_play_enabled}, stream_queue), ({true}, default)]
    )

s = if dj_live_stream_port != 0 and dj_live_stream_mp != "" then
    dj_live = mksafe(
        audio_to_stereo(
            input.harbor(id="live_dj_harbor",
                dj_live_stream_mp,
                port=dj_live_stream_port,
                auth=check_dj_client,
                max=40.,
                on_connect=live_dj_connect,
                on_disconnect=live_dj_disconnect)))

    ignore(output.dummy(dj_live, fallible=true))

    switch(id="show_schedule_noise_switch",
            track_sensitive=false,
            transitions=[transition, transition],
            [({!live_dj_enabled}, dj_live), ({true}, s)]
        )
else
    s
end

s = if master_live_stream_port != 0 and master_live_stream_mp != "" then
    master_dj = mksafe(
                    audio_to_stereo(
                        input.harbor(id="master_harbor",
                            master_live_stream_mp,
                            port=master_live_stream_port,
                            auth=check_master_dj_client,
                            max=40.,
                            on_connect=master_dj_connect,
                            on_disconnect=master_dj_disconnect)))

    ignore(output.dummy(master_dj, fallible=true))

    switch(id="master_show_schedule_noise_switch",
            track_sensitive=false,
            transitions=[transition, transition],
            [({!master_dj_enabled}, master_dj), ({true}, s)]
        )
else
    s
end


# Attach a skip command to the source s:
add_skip_command(s)

server.register(namespace="streams",
    description="Stop Master DJ source.",
    usage="master_dj_stop",
    "master_dj_stop",
    fun (s) -> begin log("streams.master_dj_stop") make_master_dj_unavailable() 
"Done." end)
server.register(namespace="streams",
    description="Start Master DJ source.",
    usage="master_dj_start",
    "master_dj_start",
    fun (s) -> begin log("streams.master_dj_start") make_master_dj_available() 
"Done." end)
server.register(namespace="streams",
    description="Stop Live DJ source.",
    usage="live_dj_stop",
    "live_dj_stop",
    fun (s) -> begin log("streams.live_dj_stop") make_live_dj_unavailable() 
"Done." end)
server.register(namespace="streams",
    description="Start Live DJ source.",
    usage="live_dj_start",
    "live_dj_start",
    fun (s) -> begin log("streams.live_dj_start") make_live_dj_available() 
"Done." end)
server.register(namespace="streams",
    description="Stop Scheduled Play source.",
    usage="scheduled_play_stop",
    "scheduled_play_stop",
    fun (s) -> begin log("streams.scheduled_play_stop") 
make_scheduled_play_unavailable() "Done." end)
server.register(namespace="streams",
    description="Start Scheduled Play source.",
    usage="scheduled_play_start",
    "scheduled_play_start",
    fun (s) -> begin log("streams.scheduled_play_start") 
make_scheduled_play_available() "Done." end)

if output_sound_device then
    success = ref false

    log(output_sound_device_type)

    %ifdef output.alsa
        if output_sound_device_type == "ALSA" then
                ignore(output.alsa(s))
        success := true
        end
        %endif

        %ifdef output.ao
        if output_sound_device_type == "AO" then
                ignore(output.ao(s))
        success := true
        end
        %endif

        %ifdef output.oss
        if output_sound_device_type == "OSS" then
        ignore(output.oss(s))
        success := true
        end
        %endif

        %ifdef output.portaudio
        if output_sound_device_type == "Portaudio" then
        ignore(output.portaudio(s))
        success := true
        end
        %endif

        %ifdef output.pulseaudio
        if output_sound_device_type == "Pulseaudio" then
        ignore(output.pulseaudio(s))
        success := true
        end
        %endif

    if (!success == false) then
        ignore(output.prefered(s))
        end

end

if s1_enable == true then
    if s1_output == 'shoutcast' then
        s1_namespace := "shoutcast_stream_1"
    else
        s1_namespace := s1_mount
    end
    server.register(namespace=!s1_namespace, "connected", fun (s) -> begin 
log("#{!s1_namespace}.connected") !s1_connected end)
    output_to(s1_output, s1_type, s1_bitrate, s1_host, s1_port, s1_pass,
                s1_mount, s1_url, s1_description, s1_genre, s1_user, s, "1",
                s1_connected, s1_name, s1_channels)
end

if s2_enable == true then
    if s2_output == 'shoutcast' then
        s2_namespace := "shoutcast_stream_2"
    else
        s2_namespace := s2_mount
    end
    server.register(namespace=!s2_namespace, "connected", fun (s) -> begin 
log("#{!s2_namespace}.connected") !s2_connected end)
    output_to(s2_output, s2_type, s2_bitrate, s2_host, s2_port, s2_pass,
                s2_mount, s2_url, s2_description, s2_genre, s2_user, s, "2",
                s2_connected, s2_name, s2_channels)

end

if s3_enable == true then
    if s3_output == 'shoutcast' then
        s3_namespace := "shoutcast_stream_3"
    else
        s3_namespace := s3_mount
    end
    server.register(namespace=!s3_namespace, "connected", fun (s) -> begin 
log("#{!s3_namespace}.connected") !s3_connected end)
    output_to(s3_output, s3_type, s3_bitrate, s3_host, s3_port, s3_pass,
                s3_mount, s3_url, s3_name, s3_genre, s3_user, s, "3",
                s3_connected, s3_description, s3_channels)
end

command = "/usr/lib/airtime/pypo/bin/liquidsoap_scripts/notify.sh 
--liquidsoap-started &"
log(command)
system(command)


Attachment: playlistcreator.py
Description: Binary data

------------------------------------------------------------------------------
Precog is a next-generation analytics platform capable of advanced
analytics on semi-structured data. The platform includes APIs for building
apps and a phenomenal toolset for data science. Developers can use
our toolset for easy data analysis & visualization. Get a free account!
http://www2.precog.com/precogplatform/slashdotnewsletter
_______________________________________________
Savonet-users mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/savonet-users

Reply via email to