Merge branch '2.2.x' into devel

This commit is contained in:
Martin Konecny 2012-11-20 11:21:29 -05:00
commit fed39077c0
9 changed files with 199 additions and 175 deletions

View file

@ -1116,7 +1116,6 @@ class Application_Model_Preference
} else { } else {
/*For now we just have this hack for debugging. We should not /*For now we just have this hack for debugging. We should not
rely on this crappy behaviour in case of failure*/ rely on this crappy behaviour in case of failure*/
Logging::info("Pref: $pref_param");
Logging::warn("Index $x does not exist preferences"); Logging::warn("Index $x does not exist preferences");
Logging::warn("Defaulting to identity and printing preferences"); Logging::warn("Defaulting to identity and printing preferences");
Logging::warn($ds); Logging::warn($ds);

View file

@ -321,7 +321,7 @@ SQL;
ws.description AS file_album_title, ws.description AS file_album_title,
ws.length AS file_length, ws.length AS file_length,
't'::BOOL AS file_exists, 't'::BOOL AS file_exists,
NULL as file_mime ws.mime as file_mime
SQL; SQL;
$streamJoin = <<<SQL $streamJoin = <<<SQL
cc_schedule AS sched cc_schedule AS sched

View file

@ -402,8 +402,9 @@ function setupUI() {
$(".repeat_tracks_help_icon").qtip({ $(".repeat_tracks_help_icon").qtip({
content: { content: {
text: "If your criteria is too strict, Airtime may not be able to fill up the desired smart block length." + text: "The desired block length will not be reached if Airtime cannot find " +
" Hence, if you check this option, tracks will be used more than once." "enough unique tracks to match your criteria. Enable this option if you wish to allow " +
"tracks to be added multiple times to the smart block."
}, },
hide: { hide: {
delay: 500, delay: 500,

View file

@ -25,7 +25,8 @@ echo "----------------------------------------------------"
dist=`lsb_release -is` dist=`lsb_release -is`
code=`lsb_release -cs` code=`lsb_release -cs`
if [ "$dist" = "Debian" ]; then #enable squeeze backports to get lame packages
if [ "$dist" = "Debian" -a "$code" = "squeeze" ]; then
set +e set +e
grep -E "deb http://backports.debian.org/debian-backports squeeze-backports main" /etc/apt/sources.list grep -E "deb http://backports.debian.org/debian-backports squeeze-backports main" /etc/apt/sources.list
returncode=$? returncode=$?

View file

@ -28,7 +28,8 @@ echo "----------------------------------------------------"
dist=`lsb_release -is` dist=`lsb_release -is`
code=`lsb_release -cs` code=`lsb_release -cs`
if [ "$dist" -eq "Debian" ]; then #enable squeeze backports to get lame packages
if [ "$dist" = "Debian" -a "$code" = "squeeze" ]; then
grep "deb http://backports.debian.org/debian-backports squeeze-backports main" /etc/apt/sources.list grep "deb http://backports.debian.org/debian-backports squeeze-backports main" /etc/apt/sources.list
if [ "$?" -ne "0" ]; then if [ "$?" -ne "0" ]; then
echo "deb http://backports.debian.org/debian-backports squeeze-backports main" >> /etc/apt/sources.list echo "deb http://backports.debian.org/debian-backports squeeze-backports main" >> /etc/apt/sources.list

View file

@ -254,29 +254,15 @@ def output_to(output_type, type, bitrate, host, port, pass, mount_point, url, de
if user == "" then if user == "" then
user_ref := "source" user_ref := "source"
end end
description_ref = ref description
if description == "" then
description_ref := "N/A"
end
genre_ref = ref genre
if genre == "" then
genre_ref := "N/A"
end
url_ref = ref url
if url == "" then
url_ref := "N/A"
end
output.shoutcast_mono = output.shoutcast(id = "shoutcast_stream_#{stream}", output.shoutcast_mono = output.shoutcast(id = "shoutcast_stream_#{stream}",
host = host, host = host,
port = port, port = port,
password = pass, password = pass,
fallible = true, fallible = true,
url = !url_ref, url = url,
genre = !genre_ref, genre = genre,
name = !description_ref, name = description,
user = !user_ref, user = !user_ref,
on_error = on_error, on_error = on_error,
on_connect = on_connect) on_connect = on_connect)
@ -286,9 +272,9 @@ def output_to(output_type, type, bitrate, host, port, pass, mount_point, url, de
port = port, port = port,
password = pass, password = pass,
fallible = true, fallible = true,
url = !url_ref, url = url,
genre = !genre_ref, genre = genre,
name = !description_ref, name = description,
user = !user_ref, user = !user_ref,
on_error = on_error, on_error = on_error,
on_connect = on_connect) on_connect = on_connect)
@ -390,13 +376,6 @@ def add_skip_command(s)
"skip",fun(s) -> begin log("source.skip") skip(s) end) "skip",fun(s) -> begin log("source.skip") skip(s) end)
end end
dyn_out = output.icecast(%wav,
host="localhost",
port=8999,
password=stream_harbor_pass,
mount="test-harbor",
fallible=true)
def set_dynamic_source_id(id) = def set_dynamic_source_id(id) =
current_dyn_id := id current_dyn_id := id
string_of(!current_dyn_id) string_of(!current_dyn_id)
@ -406,123 +385,159 @@ def get_dynamic_source_id() =
string_of(!current_dyn_id) string_of(!current_dyn_id)
end end
#cc-4633
# Function to create a playlist source and output it.
def create_dynamic_source(uri) =
# The playlist source
s = audio_to_stereo(input.http(buffer=2., max=12., uri))
# The output # NOTE
active_dyn_out = dyn_out(s) # A few values are hardcoded and may be dependent:
# - the delay in gracetime is linked with the buffer duration of input.http
# (delay should be a bit less than buffer)
# - crossing duration should be less than buffer length
# (at best, a higher duration will be ineffective)
# We register both source and output # HTTP input with "restart" command that waits for "stop" to be effected
# in the list of sources # before "start" command is issued. Optionally it takes a new URL to play,
dyn_sources := # which makes it a convenient replacement for "url".
list.append([(!current_dyn_id, s),(!current_dyn_id, active_dyn_out)], !dyn_sources) # In the future, this may become a core feature of the HTTP input.
# TODO If we stop and restart quickly several times in a row,
# the data bursts accumulate and create buffer overflow.
# Flushing the buffer on restart could be a good idea, but
# it would also create an interruptions while the buffer is
# refilling... on the other hand, this would avoid having to
# fade using both cross() and switch().
def input.http_restart(~id,~initial_url="http://dummy/url")
source = input.http(buffer=5.,max=15.,id=id,autostart=false,initial_url)
def stopped()
"stopped" == list.hd(server.execute("#{id}.status"))
end
server.register(namespace=id,
"restart",
usage="restart [url]",
fun (url) -> begin
if url != "" then
log(string_of(server.execute("#{id}.url #{url}")))
end
log(string_of(server.execute("#{id}.stop")))
add_timeout(0.5,
{ if stopped() then
log(string_of(server.execute("#{id}.start"))) ;
(-1.)
else 0.5 end})
"OK"
end)
# Dummy output should be useless if HTTP stream is meant
# to be listened to immediately. Otherwise, apply it.
#
# output.dummy(fallible=true,source)
source
notify([("schedule_table_id", !current_dyn_id)])
"Done!"
end end
# Transitions between URL changes in HTTP streams.
def cross_http(~debug=true,~http_input_id,source)
# A function to destroy a dynamic source id = http_input_id
def destroy_dynamic_source(id) = last_url = ref ""
# We need to find the source in the list, change = ref false
# remove it and destroy it. Currently, the language
# lacks some nice operators for that so we do it
# the functional way
# This function is executed on every item in the list def on_m(m)
# of dynamic sources notify_stream(m)
def parse_list(ret, current_element) = changed = m["source_url"] != !last_url
# ret is of the form: (matching_sources, remaining_sources) log("URL now #{m['source_url']} (change: #{changed})")
# We extract those two: if changed then
matching_sources = fst(ret) if !last_url != "" then change := true end
remaining_sources = snd(ret) last_url := m["source_url"]
# current_element is of the form: ("uri", source) so
# we check the first element
current_id = fst(current_element)
if current_id == id then
# In this case, we add the source to the list of
# matched sources
(list.append( [snd(current_element)],
matching_sources),
remaining_sources)
else
# In this case, we put the element in the list of remaining
# sources
(matching_sources,
list.append([current_element],
remaining_sources))
end end
end end
# Now we execute the function:
result = list.fold(parse_list, ([], []), !dyn_sources)
matching_sources = fst(result)
remaining_sources = snd(result)
# We store the remaining sources in dyn_sources # We use both metadata and status to know about the current URL.
dyn_sources := remaining_sources # Using only metadata may be more precise is crazy corner cases,
# but it's also asking too much: the metadata may not pass through
# before the crosser is instantiated.
# Using only status in crosser misses some info, eg. on first URL.
source = on_metadata(on_m,source)
# If no source matched, we return an error cross_d = 3.
if list.length(matching_sources) == 0 then
"Error: no matching sources!" def crosser(a,b)
else url = list.hd(server.execute('#{id}.url'))
# We stop all sources status = list.hd(server.execute('#{id}.status'))
list.iter(source.shutdown, matching_sources) on_m([("source_url",url)])
# And return if debug then
"Done!" log("New track inside HTTP stream")
log(" status: #{status}")
log(" need to cross: #{!change}")
log(" remaining #{source.remaining(a)} sec before, \
#{source.remaining(b)} sec after")
end
if !change then
change := false
# In principle one should avoid crossing on a live stream
# it'd be okay to do it here (eg. use add instead of sequence)
# because it's only once per URL, but be cautious.
sequence([fade.out(duration=cross_d,a),fade.in(b)])
else
# This is done on tracks inside a single stream.
# Do NOT cross here or you'll gradually empty the buffer!
sequence([a,b])
end
end end
# Setting conservative=true would mess with the delayed switch below
cross(duration=cross_d,conservative=false,crosser,source)
end end
# Custom fallback between http and default source with fading of
# beginning and end of HTTP stream.
# It does not take potential URL changes into account, as long as
# they do not interrupt streaming (thanks to the HTTP buffer).
def http_fallback(~http_input_id,~http,~default)
id = http_input_id
# We use a custom switching predicate to trigger switching (and thus,
# transitions) before the end of a track (rather, end of HTTP stream).
# It is complexified because we don't want to trigger switching when
# HTTP disconnects for just an instant, when changing URL: for that
# we use gracetime below.
# A function to destroy a dynamic source def gracetime(~delay=3.,f)
def destroy_dynamic_source_all() = last_true = ref 0.
# We need to find the source in the list, { if f() then
# remove it and destroy it. Currently, the language last_true := gettimeofday()
# lacks some nice operators for that so we do it true
# the functional way else
gettimeofday() < !last_true+delay
# This function is executed on every item in the list end }
# of dynamic sources
def parse_list(ret, current_element) =
# ret is of the form: (matching_sources, remaining_sources)
# We extract those two:
matching_sources = fst(ret)
remaining_sources = snd(ret)
# current_element is of the form: ("uri", source) so
# we check the first element
current_uri = fst(current_element)
# in this case, we add the source to the list of
# matched sources
(list.append( [snd(current_element)],
matching_sources),
remaining_sources)
end end
# now we execute the function:
result = list.fold(parse_list, ([], []), !dyn_sources)
matching_sources = fst(result)
remaining_sources = snd(result)
# we store the remaining sources in dyn_sources def connected()
dyn_sources := remaining_sources status = list.hd(server.execute("#{id}.status"))
not(list.mem(status,["polling","stopped"]))
# if no source matched, we return an error
if list.length(matching_sources) == 0 then
"error: no matching sources!"
else
# we stop all sources
list.iter(source.shutdown, matching_sources)
# And return
"Done!"
end end
connected = gracetime(connected)
def to_live(a,b) =
log("TRANSITION to live")
add(normalize=false,
[fade.initial(b),fade.final(a)])
end
def to_static(a,b) =
log("TRANSITION to static")
sequence([fade.out(a),fade.initial(b)])
end
switch(
track_sensitive=false,
transitions=[to_live,to_static],
[(# make sure it is connected, and not buffering
{connected() and source.is_ready(http) and !webstream_enabled}, http),
({true},default)])
end end

View file

@ -7,11 +7,11 @@ set("server.telnet", true)
set("server.telnet.port", 1234) set("server.telnet.port", 1234)
#Dynamic source list #Dynamic source list
dyn_sources = ref [] #dyn_sources = ref []
webstream_enabled = ref false webstream_enabled = ref false
time = ref string_of(gettimeofday()) time = ref string_of(gettimeofday())
queue = audio_to_stereo(id="queue_src", request.equeue(id="queue", length=0.5)) queue = audio_to_stereo(id="queue_src", mksafe(request.equeue(id="queue", length=0.5)))
queue = cue_cut(queue) queue = cue_cut(queue)
queue = amplify(1., override="replay_gain", queue) queue = amplify(1., override="replay_gain", queue)
@ -35,15 +35,19 @@ s2_namespace = ref ''
s3_namespace = ref '' s3_namespace = ref ''
just_switched = ref false just_switched = ref false
stream_harbor_pass = list.hd(get_process_lines('pwgen -s -N 1 -n 20')) #stream_harbor_pass = list.hd(get_process_lines('pwgen -s -N 1 -n 20'))
%include "ls_lib.liq" %include "ls_lib.liq"
web_stream = input.harbor("test-harbor", port=8999, password=stream_harbor_pass) #web_stream = input.harbor("test-harbor", port=8999, password=stream_harbor_pass)
web_stream = on_metadata(notify_stream, web_stream) #web_stream = on_metadata(notify_stream, web_stream)
output.dummy(fallible=true, web_stream) #output.dummy(fallible=true, web_stream)
http = input.http_restart(id="http")
http = cross_http(http_input_id="http",http)
stream_queue = http_fallback(http_input_id="http",http=http,default=queue)
# the crossfade function controls fade in/out # the crossfade function controls fade in/out
queue = crossfade_airtime(queue) queue = crossfade_airtime(queue)
queue = on_metadata(notify, queue) queue = on_metadata(notify, queue)
@ -51,10 +55,10 @@ queue = map_metadata(update=false, append_title, queue)
output.dummy(fallible=true, queue) output.dummy(fallible=true, queue)
stream_queue = switch(id="stream_queue_switch", track_sensitive=false, #stream_queue = switch(id="stream_queue_switch", track_sensitive=false,
transitions=[transition, transition], # transitions=[transition, transition],
[({!webstream_enabled},web_stream), # [({!webstream_enabled},web_stream),
({true}, queue)]) # ({true}, queue)])
ignore(output.dummy(stream_queue, fallible=true)) ignore(output.dummy(stream_queue, fallible=true))
@ -92,32 +96,33 @@ server.register(namespace="dynamic_source",
fun (s) -> begin log("dynamic_source.output_stop") webstream_enabled := false "disabled" end) fun (s) -> begin log("dynamic_source.output_stop") webstream_enabled := false "disabled" end)
server.register(namespace="dynamic_source", server.register(namespace="dynamic_source",
description="Set the cc_schedule row id", description="Set the streams cc_schedule row id",
usage="id <id>", usage="id <id>",
"id", "id",
fun (s) -> begin log("dynamic_source.id") set_dynamic_source_id(s) end) fun (s) -> begin log("dynamic_source.id") set_dynamic_source_id(s) end)
server.register(namespace="dynamic_source", server.register(namespace="dynamic_source",
description="Get the cc_schedule row id", description="Get the streams cc_schedule row id",
usage="get_id", usage="get_id",
"get_id", "get_id",
fun (s) -> begin log("dynamic_source.get_id") get_dynamic_source_id() end) fun (s) -> begin log("dynamic_source.get_id") get_dynamic_source_id() end)
server.register(namespace="dynamic_source", #server.register(namespace="dynamic_source",
description="Start a new dynamic source.", # description="Start a new dynamic source.",
usage="start <uri>", # usage="start <uri>",
"read_start", # "read_start",
fun (uri) -> begin log("dynamic_source.read_start") create_dynamic_source(uri) end) # fun (uri) -> begin log("dynamic_source.read_start") begin_stream_read(uri) end)
server.register(namespace="dynamic_source", #server.register(namespace="dynamic_source",
description="Stop a dynamic source.", # description="Stop a dynamic source.",
usage="stop <id>", # usage="stop <id>",
"read_stop", # "read_stop",
fun (s) -> begin log("dynamic_source.read_stop") destroy_dynamic_source(s) end) # fun (s) -> begin log("dynamic_source.read_stop") stop_stream_read(s) end)
server.register(namespace="dynamic_source",
description="Stop a dynamic source.", #server.register(namespace="dynamic_source",
usage="stop <id>", # description="Stop a dynamic source.",
"read_stop_all", # usage="stop <id>",
fun (s) -> begin log("dynamic_source.read_stop") destroy_dynamic_source_all() end) # "read_stop_all",
# fun (s) -> begin log("dynamic_source.read_stop") destroy_dynamic_source_all() end)
default = amplify(id="silence_src", 0.00001, noise()) default = amplify(id="silence_src", 0.00001, noise())
default = rewrite_metadata([("artist","Airtime"), ("title", "offline")], default) default = rewrite_metadata([("artist","Airtime"), ("title", "offline")], default)

View file

@ -478,8 +478,8 @@ class PypoPush(Thread):
self.logger.debug(msg) self.logger.debug(msg)
tn.write(msg) tn.write(msg)
#example: dynamic_source.read_start http://87.230.101.24:80/top100station.mp3 #msg = 'dynamic_source.read_start %s\n' % media_item['uri'].encode('latin-1')
msg = 'dynamic_source.read_start %s\n' % media_item['uri'].encode('latin-1') msg = 'http.restart %s\n' % media_item['uri'].encode('latin-1')
self.logger.debug(msg) self.logger.debug(msg)
tn.write(msg) tn.write(msg)
@ -520,7 +520,8 @@ class PypoPush(Thread):
self.telnet_lock.acquire() self.telnet_lock.acquire()
tn = telnetlib.Telnet(LS_HOST, LS_PORT) tn = telnetlib.Telnet(LS_HOST, LS_PORT)
msg = 'dynamic_source.read_stop_all xxx\n' #msg = 'dynamic_source.read_stop_all xxx\n'
msg = 'http.stop\n'
self.logger.debug(msg) self.logger.debug(msg)
tn.write(msg) tn.write(msg)
@ -546,7 +547,8 @@ class PypoPush(Thread):
tn = telnetlib.Telnet(LS_HOST, LS_PORT) tn = telnetlib.Telnet(LS_HOST, LS_PORT)
#dynamic_source.stop http://87.230.101.24:80/top100station.mp3 #dynamic_source.stop http://87.230.101.24:80/top100station.mp3
msg = 'dynamic_source.read_stop %s\n' % media_item['row_id'] #msg = 'dynamic_source.read_stop %s\n' % media_item['row_id']
msg = 'http.stop\n'
self.logger.debug(msg) self.logger.debug(msg)
tn.write(msg) tn.write(msg)

View file

@ -27,14 +27,14 @@ def printUsage():
print " -m mount (default: test) " print " -m mount (default: test) "
print " -h show help menu" print " -h show help menu"
def find_liquidsoap_binary(): def find_liquidsoap_binary():
""" """
Starting with Airtime 2.0, we don't know the exact location of the Liquidsoap Starting with Airtime 2.0, we don't know the exact location of the Liquidsoap
binary because it may have been installed through a debian package. Let's find binary because it may have been installed through a debian package. Let's find
the location of this binary. the location of this binary.
""" """
rv = subprocess.call("which airtime-liquidsoap > /dev/null", shell=True) rv = subprocess.call("which airtime-liquidsoap > /dev/null", shell=True)
if rv == 0: if rv == 0:
return "airtime-liquidsoap" return "airtime-liquidsoap"
@ -78,7 +78,7 @@ for o, a in optlist:
mount = a mount = a
try: try:
print "Protocol: %s " % stream_type print "Protocol: %s " % stream_type
print "Host: %s" % host print "Host: %s" % host
print "Port: %s" % port print "Port: %s" % port
@ -86,35 +86,35 @@ try:
print "Password: %s" % password print "Password: %s" % password
if stream_type == "icecast": if stream_type == "icecast":
print "Mount: %s\n" % mount print "Mount: %s\n" % mount
url = "http://%s:%s/%s" % (host, port, mount) url = "http://%s:%s/%s" % (host, port, mount)
print "Outputting to %s streaming server. You should be able to hear a monotonous tone on '%s'. Press ctrl-c to quit." % (stream_type, url) print "Outputting to %s streaming server. You should be able to hear a monotonous tone on '%s'. Press ctrl-c to quit." % (stream_type, url)
liquidsoap_exe = find_liquidsoap_binary() liquidsoap_exe = find_liquidsoap_binary()
if liquidsoap_exe is None: if liquidsoap_exe is None:
raise Exception("Liquidsoap not found!") raise Exception("Liquidsoap not found!")
if stream_type == "icecast": if stream_type == "icecast":
command = "%s 'output.icecast(%%vorbis, host = \"%s\", port = %s, user= \"%s\", password = \"%s\", mount=\"%s\", sine())'" % (liquidsoap_exe, host, port, user, password, mount) command = "%s 'output.icecast(%%vorbis, host = \"%s\", port = %s, user= \"%s\", password = \"%s\", mount=\"%s\", sine())'" % (liquidsoap_exe, host, port, user, password, mount)
else: else:
command = "%s /usr/lib/airtime/pypo/bin/liquidsoap_scripts/library/pervasives.liq 'output.shoutcast(%%mp3, host=\"%s\", port = %s, user= \"%s\", password = \"%s\", sine())'" \ command = "%s /usr/lib/airtime/pypo/bin/liquidsoap_scripts/library/pervasives.liq 'output.shoutcast(%%mp3, host=\"%s\", port = %s, user= \"%s\", password = \"%s\", sine())'" \
% (liquidsoap_exe, host, port, user, password) % (liquidsoap_exe, host, port, user, password)
if not verbose: if not verbose:
command += " 2>/dev/null | grep \"failed\"" command += " 2>/dev/null | grep \"failed\""
else: else:
print command print command
#print command #print command
rv = subprocess.call(command, shell=True) rv = subprocess.call(command, shell=True)
#if we reach this point, it means that our subprocess exited without the user #if we reach this point, it means that our subprocess exited without the user
#doing a keyboard interrupt. This means there was a problem outputting to the #doing a keyboard interrupt. This means there was a problem outputting to the
#stream server. Print appropriate message. #stream server. Print appropriate message.
print "There was an error with your stream configuration. Please review your configuration " + \ print "There was an error with your stream configuration. Please review your configuration " + \
"and run this program again. Use the -h option for help" "and run this program again. Use the -h option for help"
except KeyboardInterrupt, ki: except KeyboardInterrupt, ki:
print "\nExiting" print "\nExiting"
except Exception, e: except Exception, e: