#!/usr/bin/liquidsoap
# Standard settings
set("log.file.path","/var/log/liquidsoap/pi.log")
set("init.daemon",true)
set("log.stdout",false)
set("log.file",true)
set("init.daemon.pidfile.path","/var/run/liquidsoap/pi.pid")
# Enable telnet server
set("server.telnet",true)
# Enable harbor for any external
# connection
set("harbor.bind_addr","0.0.0.0")
# Verbose logs
set("log.level",4)
# We use the scheduler intensively,
# therefore we create many queues.
set("scheduler.generic_queues",5)
set("scheduler.fast_queues",3)
set("scheduler.non_blocking_queues",3)
# === Settings ===
# The host to request files
stream = "XXXxXXXx"
# The command to request files
scripts = "ssh XXxxxXXX@#{stream} '/path/to/scripts/"
# A substitution on the returned path
sed = " | sed -e s#/path/to/files/#ftp://user:password@#{stream}/#'"
# Enable replay gain
enable_replaygain_metadata ()
pass = "XXxXXXXx"
ice_host = "localhost"
descr = "RadioPi"
url = "http://radiopi.org"
# === Live ===
# A live source, on which we stip blank (make the source
# unavailable when streaming blank)
live =
strip_blank(
input.harbor(id="live", port=8000, password=pass,
buffer=8.,max=20.,"live.ogg"),
length=10., threshold=-50.)
# This source relays the live data, when available,
# to the other streamer, in uncompressed format (WAV)
output.icecast(%wav, host=stream,
port=8005, password=pass,
mount="live.ogg", fallible=true,
live)
# This source relays the live source to "live.ogg". This
# is used for debugging purposes, to see what is sent
# to the harbor source.
output.icecast(%vorbis, host="127.0.0.1",
port=8080, password=pass,
mount="live.ogg", fallible=true,
live)
# This source starts an archive of the live stream
# when available
title = '$(if $(title),"$(title)",\
"Emission inconnue")$(if $(artist), \
" par $(artist)") - %m-%d-%Y, %H:%M:%S'
output.file(%vorbis, reopen_on_metadata=true,
fallible=true,
"/data/archives/brutes/" ^ title ^ ".ogg",
live)
# === Channels ===
# Specialize the output functions by partial application
output.icecast = output.icecast(description=descr, url=url)
out = output.icecast(host=ice_host,port=8080,password=pass,fallible=true)
out_aac32 = out(%fdkaac(bitrate=32))
out_aac = out(%fdkaac(bitrate=64))
out = out(%mp3)
# A file for playing during failures
interlude =
single("/home/radiopi/fallback.mp3")
# Lastfm submission
def lastfm (m) =
if (m["type"] == "chansons") then
if (m["canal"] == "reggae" or m["canal"] == "Jazz" or m["canal"] == "That70Sound") then
canal =
if (m["canal"] == "That70Sound") then
"70sound"
else
m["canal"]
end
user = "radiopi-" ^ canal
lastfm.submit(user=user,password="xXXxx",m)
end
end
end
# === Basic sources ===
# Custom crossfade to deal with jingles..
def smart_crossfade (~start_next=5.,~fade_in=3.,~fade_out=3.,
~default=(fun (a,b) -> sequence([a, b])),
~high=-15., ~medium=-32., ~margin=4.,
~width=2.,~conservative=false,s)
fade.out = fade.out(type="sin",duration=fade_out)
fade.in = fade.in(type="sin",duration=fade_in)
add = fun (a,b) -> add(normalize=false,[b, a])
log = log(label="smart_crossfade")
def transition(a,b,ma,mb,sa,sb)
list.iter(fun(x)-> log(level=4,"Before: #{x}"),ma)
list.iter(fun(x)-> log(level=4,"After : #{x}"),mb)
if ma["type"] == "jingles" or mb["type"] == "jingles" then
log("Old or new file is a jingle: sequenced transition.")
sequence([sa, sb])
elsif
# If A and B are not too loud and close, fully cross-fade them.
a <= medium and b <= medium and abs(a - b) <= margin
then
log("Old <= medium, new <= medium and |old-new| <= margin.")
log("Old and new source are not too loud and close.")
log("Transition: crossed, fade-in, fade-out.")
add(fade.out(sa),fade.in(sb))
elsif
# If B is significantly louder than A, only fade-out A.
# We don't want to fade almost silent things, ask for >medium.
b >= a + margin and a >= medium and b <= high
then
log("new >= old + margin, old >= medium and new <= high.")
log("New source is significantly louder than old one.")
log("Transition: crossed, fade-out.")
add(fade.out(sa),sb)
elsif
# Opposite as the previous one.
a >= b + margin and b >= medium and a <= high
then
log("old >= new + margin, new >= medium and old <= high")
log("Old source is significantly louder than new one.")
log("Transition: crossed, fade-in.")
add(sa,fade.in(sb))
elsif
# Do not fade if it's already very low.
b >= a + margin and a <= medium and b <= high
then
log("new >= old + margin, old <= medium and new <= high.")
log("Do not fade if it's already very low.")
log("Transition: crossed, no fade.")
add(sa,sb)
# What to do with a loud end and a quiet beginning ?
# A good idea is to use a jingle to separate the two tracks,
# but that's another story.
else
# Otherwise, A and B are just too loud to overlap nicely,
# or the difference between them is too large and overlapping would
# completely mask one of them.
log("No transition: using default.")
default(sa, sb)
end
end
smart_cross(width=width, duration=start_next, conservative=conservative,
transition,s)
end
# Create a radiopilote-driven source
def channel_radiopilote(~skip=true,name)
log("Creating canal #{name}")
# Request function
def request () =
log("Request for #{name}")
ret = list.hd(get_process_lines(scripts^"radiopilote-getnext "^quote(name)^sed))
log("Got answer: #{ret} for #{name}")
request.create(ret)
end
# Create the request.dynamic source
# Set conservative to true to queue
# several songs in advance
source =
request.dynamic(conservative=true, length=400.,
id="dyn_"^name,request,
timeout=60.)
# Apply normalization using replaygain
# information
source = amplify(1.,override="replay_gain", source)
# Skip blank when asked to
source =
if skip then
skip_blank(source, length=10., threshold=-40.)
else
source
end
# Submit new tracks on lastfm
source = on_metadata(lastfm,source)
# Tell the system when a new track
# is played
source = on_metadata(fun (meta) ->
system(scripts ^ "radiopilote-feedback "
^quote(meta["canal"])^" "
^quote(meta["file_id"]) ^ "'"), source)
# Finally apply a smart crossfading
smart_crossfade(source)
end
# Basic source
jazz = channel_radiopilote("jazz")
discoqueen = channel_radiopilote("discoqueen")
# Avoid skiping blank with classic music !!
classique = channel_radiopilote(skip=false,"classique")
That70Sound = channel_radiopilote("That70Sound")
metal = channel_radiopilote("metal")
reggae = channel_radiopilote("reggae")
Rock = channel_radiopilote("Rock")
# Group those sources in a seperate
# clock (good for multithreading/multicore)
clock.assign_new([jazz,That70Sound,metal,reggae])
# === Mixing live ===
# To create a channel from a basic source, add:
# - a new-track notification for radiopilote
# - metadata rewriting
# - the live shows
# - the failsafe 'interlude' source to channels
# - blank detection
def mklive(source) =
# Transition function: if transitioning
# to the live, fade out the old source
# if transitioning from live, fade.in
# the new source. NOTE: We cannot
# skip the current song because
# reloading new songs for all the
# sources when live starts costs too much
# CPU.
def trans(old,new) =
if source.id(new) == source.id(live) then
log("Transition to live!")
add([new,fade.final(old)])
elsif source.id(old) == source.id(live) then
log("Transitioning from live!")
add([fade.initial(new),old])
else
log("Dummy transition")
new
end
end
fallback(track_sensitive=false,
transitions=[trans,trans,trans],
[live,source,interlude])
end
# Create a channel using mklive(), encode and output it to icecast.
def mkoutput(~out=out,mount,source,name,genre)
out(id=mount,mount=mount,name=name,genre=genre,
mklive(source)
)
end
# === Outputs ===
mkoutput("jazz", jazz, "RadioPi - Canal Jazz","jazz")
mkoutput("discoqueen", discoqueen, "RadioPi - Canal DiscoQueen","discoqueen")
mkoutput("classique", classique, "RadioPi - Canal Classique","classique")
mkoutput("That70Sound", That70Sound,
"RadioPi - Canal That70Sound","That70Sound")
mkoutput("metal", metal, "RadioPi - Canal Metal","metal")
mkoutput("reggae", reggae, "RadioPi - Canal Reggae","reggae")
mkoutput("Rock", Rock, "RadioPi - Canal Rock","Rock")
# Test outouts
mkoutput(out=out_aac,"reggae.aacp", reggae, "RadioPi - Canal Reggae \
(64 kbits AAC+ test stream)","reggae")
mkoutput(out=out_aac32,"reggae.aacp32", reggae, "RadioPi - Canal Reggae \
(32 kbits AAC+ test stream)","reggae")