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)
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
