diff --git a/pypo/install/pypo-install.py b/pypo/install/pypo-install.py index ffd0afe1f..a69ca01f6 100644 --- a/pypo/install/pypo-install.py +++ b/pypo/install/pypo-install.py @@ -54,14 +54,11 @@ def copy_dir(src_dir, dest_dir): try: # Create users create_user("pypo") - #create_user("pypo-logger") print "Creating log directories" create_path("/var/log/pypo") os.system("chmod -R 755 /var/log/pypo") os.system("chown -R pypo:pypo /var/log/pypo") - #os.mkdirs("/var/log/liquidsoap") - #os.system("chown -R liquidsoap:liquidsoap /var/log/liquidsoap") create_path(BASE_PATH) create_path(BASE_PATH+"bin") @@ -87,17 +84,7 @@ try: print "Unknown system architecture." sys.exit(1) - #shutil.copy("../pypo-cli.py", BASE_PATH+"bin") - #shutil.copy("../pypo-notify.py", BASE_PATH+"bin") - #shutil.copy("../logging.cfg", BASE_PATH+"bin") - #shutil.copy("../config.cfg", BASE_PATH+"bin") - #shutil.copy("../pypo-log.sh", BASE_PATH+"bin") copy_dir("..", BASE_PATH+"bin/") - #copy_dir("../util", BASE_PATH+"bin/") - #copy_dir("../api_clients", BASE_PATH+"bin/api_clients") - #copy_dir("../scripts", BASE_PATH+"bin/scripts") - #copy_dir("../dls", BASE_PATH+"bin/dls") - #copy_dir("../dls", BASE_PATH+"bin/dls") print "Setting permissions" os.system("chmod -R 755 "+BASE_PATH) @@ -120,8 +107,7 @@ try: os.system("chown -R pypo:pypo /etc/service/pypo-push") print "Installing daemontool script pypo-liquidsoap" - os.system("svc -dk /etc/service/pypo-liquidsoap > /dev/null 2>&1") - os.system("killall liquidsoap") + os.system("svc -dx /etc/service/pypo-liquidsoap 1>/dev/null 2>&1") create_path("/etc/service/pypo-liquidsoap") create_path("/etc/service/pypo-liquidsoap/log") shutil.copy("pypo-daemontools-liquidsoap.sh", "/etc/service/pypo-liquidsoap/run") @@ -131,6 +117,7 @@ try: print "Waiting for processes to start..." time.sleep(5) + os.system("killall liquidsoap") os.system("python ./pypo-start.py") time.sleep(2) @@ -148,9 +135,7 @@ try: p = Popen('svstat /etc/service/pypo-liquidsoap', shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) output = p.stdout.read() print output - -#os.symlink(BASE_PATH+"bin/pypo-log.sh", "/usr/local/bin/") - + print "Install complete." except Exception, e: print "exception:" + str(e) diff --git a/pypo/install/pypo-start.py b/pypo/install/pypo-start.py index abc80bbc5..03c914e3f 100644 --- a/pypo/install/pypo-start.py +++ b/pypo/install/pypo-start.py @@ -19,4 +19,4 @@ try: os.system("svc -t /etc/service/pypo-liquidsoap") except Exception, e: - print "exception:" + str(e) \ No newline at end of file + print "exception:" + str(e) diff --git a/pypo/install/pypo-uninstall.py b/pypo/install/pypo-uninstall.py index abee9ee81..d69ea9cb1 100644 --- a/pypo/install/pypo-uninstall.py +++ b/pypo/install/pypo-uninstall.py @@ -21,7 +21,7 @@ def remove_user(username): print "Waiting for processes to close..." time.sleep(5) - os.system("deluser --remove-home " + username + " > /dev/null") + os.system("deluser --remove-home " + username + " 1>/dev/null") try: os.system("python ./pypo-stop.py") @@ -44,4 +44,4 @@ try: remove_user("pypo") print "Uninstall complete." except Exception, e: - print "exception:" + str(e) \ No newline at end of file + print "exception:" + str(e) diff --git a/pypo/liquidsoap/liquidsoap64 b/pypo/liquidsoap/liquidsoap64 index 41ccb8c6b..986911326 100755 Binary files a/pypo/liquidsoap/liquidsoap64 and b/pypo/liquidsoap/liquidsoap64 differ diff --git a/pypo/scripts/library/Makefile b/pypo/scripts/library/Makefile new file mode 100644 index 000000000..cc4c77cda --- /dev/null +++ b/pypo/scripts/library/Makefile @@ -0,0 +1,6 @@ + +DISTFILES = $(wildcard *.in) Makefile ask-liquidsoap.rb ask-liquidsoap.pl \ + $(wildcard *.liq) extract-replaygain + +top_srcdir = .. +include $(top_srcdir)/Makefile.rules diff --git a/pypo/scripts/library/ask-liquidsoap.pl b/pypo/scripts/library/ask-liquidsoap.pl new file mode 100755 index 000000000..a2f10206e --- /dev/null +++ b/pypo/scripts/library/ask-liquidsoap.pl @@ -0,0 +1,12 @@ +#!/usr/bin/perl -w + +use strict ; +use Net::Telnet ; + +my $telnet = new Net::Telnet ( Timeout=>10, Errmode=>'die', Port=>1234) ; +$telnet->open('localhost') ; + +die "Usage: $0 \n" unless @ARGV ; +$telnet->print($ARGV[0]) ; +my ($output,$end) = $telnet->waitfor('/END$/') ; +print $output; diff --git a/pypo/scripts/library/ask-liquidsoap.rb b/pypo/scripts/library/ask-liquidsoap.rb new file mode 100755 index 000000000..2dc65e6f8 --- /dev/null +++ b/pypo/scripts/library/ask-liquidsoap.rb @@ -0,0 +1,13 @@ +#!/usr/bin/ruby + +require 'net/telnet' + +liq_host = "localhost" +liq_port = 1234 + +conn = Net::Telnet::new("Host" => liq_host, "Port" => liq_port) + +conn.puts(ARGV[0]) +conn.waitfor("Match" => /^END$/) do |data| + puts data.sub(/\nEND\n/,"") +end diff --git a/pypo/scripts/library/external-todo.liq b/pypo/scripts/library/external-todo.liq new file mode 100644 index 000000000..ddd9029b9 --- /dev/null +++ b/pypo/scripts/library/external-todo.liq @@ -0,0 +1,314 @@ +# These operators need to be updated.. + + +# Stream data from mplayer +# @category Source / Input +# @param s data URI. +# @param ~restart restart on exit. +# @param ~restart_on_error restart on exit with error. +# @param ~buffer Duration of the pre-buffered data. +# @param ~max Maximum duration of the buffered data. +def input.mplayer(~id="input.mplayer", + ~restart=true,~restart_on_error=false, + ~buffer=0.2,~max=10.,s) = + input.external(id=id,restart=restart, + restart_on_error=restart_on_error, + buffer=buffer,max=max, + "mplayer -really-quiet -ao pcm:file=/dev/stdout \ + -vc null -vo null #{quote(s)} 2>/dev/null") +end + + +# Output the stream using aplay. +# Using this turns "root.sync" to false +# since aplay will do the synchronisation +# @category Source / Output +# @param ~id Output's ID +# @param ~device Alsa pcm device name +# @param ~restart_on_crash Restart external process on crash. If false, liquidsoap will stop. +# @param ~fallible Allow the child source to fail, in which case the output will be (temporarily) stopped. +# @param ~on_start Callback executed when outputting starts. +# @param ~on_stop Callback executed when outputting stops. +# @param s Source to play +def output.aplay(~id="output.aplay",~device="default", + ~fallible=false,~on_start={()},~on_stop={()}, + ~restart_on_crash=false,s) + def aplay_p(m) = + "aplay -D #{device}" + end + log(label=id,level=3,"Setting root.sync to false") + set("root.sync",false) + output.pipe.external(id=id, + fallible=fallible,on_start=on_start,on_stop=on_stop, + restart_on_crash=restart_on_crash, + restart_on_new_track=false, + process=aplay_p,s) +end + +%ifdef output.icecast.external +# Output to icecast using the lame command line encoder. +# @category Source / Output +# @param ~id Output's ID +# @param ~start Start output threads on operator initialization. +# @param ~restart Restart output after a failure. By default, liquidsoap will stop if the output failed. +# @param ~restart_delay Delay, in seconds, before attempting new connection, if restart is enabled. +# @param ~restart_on_crash Restart external process on crash. If false, liquidsoap will stop. +# @param ~restart_on_new_track Restart encoder upon new track. +# @param ~restart_encoder_delay Restart the encoder after this delay, in seconds. +# @param ~user User for shout source connection. Useful only in special cases, like with per-mountpoint users. +# @param ~lame The lame binary +# @param ~bitrate Encoder bitrate +# @param ~swap Swap audio samples. Depends on local machine's endianess and lame's version. Test this parameter if you experience garbaged mp3 audio data. On intel 32 and 64 architectures, the parameter should be "true" for lame version >= 3.98. +# @param ~dumpfile Dump stream to file, for debugging purpose. Disabled if empty. +# @param ~protocol Protocol of the streaming server: 'http' for Icecast, 'icy' for Shoutcast. +# @param ~fallible Allow the child source to fail, in which case the output will be (temporarily) stopped. +# @param ~on_start Callback executed when outputting starts. +# @param ~on_stop Callback executed when outputting stops. +# @param s The source to output +def output.icecast.lame( + ~id="output.icecast.lame",~start=true, + ~restart=false,~restart_delay=3, + ~host="localhost",~port=8000, + ~user="source",~password="hackme", + ~genre="Misc",~url="http://savonet.sf.net/", + ~description="OCaml Radio!",~public=true, + ~dumpfile="",~mount="Use [name]", + ~name="Use [mount]",~protocol="http", + ~lame="lame",~bitrate=128,~swap=false, + ~fallible=false,~on_start={()},~on_stop={()}, + ~restart_on_crash=false,~restart_on_new_track=false, + ~restart_encoder_delay=3600,~headers=[],s) + samplerate = get(default=44100,"frame.samplerate") + samplerate = float_of_int(samplerate) / 1000. + channels = get(default=2,"frame.channels") + swap = if swap then "-x" else "" end + mode = + if channels == 2 then + "j" # Encoding in joint stereo.. + else + "m" + end + # Metadata update is set by ICY with icecast + def lame_p(m) + "#{lame} -b #{bitrate} -r --bitwidth 16 -s #{samplerate} \ + --signed -m #{mode} --nores #{swap} -t - -" + end + output.icecast.external(id=id, + process=lame_p,bitrate=bitrate,start=start, + restart=restart,restart_delay=restart_delay, + host=host,port=port,user=user,password=password, + genre=genre,url=url,description=description, + public=public,dumpfile=dumpfile,restart_encoder_delay=restart_encoder_delay, + name=name,mount=mount,protocol=protocol, + header=false,restart_on_crash=restart_on_crash, + restart_on_new_track=restart_on_new_track,headers=headers, + fallible=fallible,on_start=on_start,on_stop=on_stop, + s) +end + +# Output to shoutcast using the lame encoder. +# @category Source / Output +# @param ~id Output's ID +# @param ~start Start output threads on operator initialization. +# @param ~restart Restart output after a failure. By default, liquidsoap will stop if the output failed. +# @param ~restart_delay Delay, in seconds, before attempting new connection, if restart is enabled. +# @param ~restart_on_crash Restart external process on crash. If false, liquidsoap will stop. +# @param ~restart_on_new_track Restart encoder upon new track. +# @param ~restart_encoder_delay Restart the encoder after this delay, in seconds. +# @param ~user User for shout source connection. Useful only in special cases, like with per-mountpoint users. +# @param ~lame The lame binary +# @param ~bitrate Encoder bitrate +# @param ~icy_reset Reset shoutcast source buffer upon connecting (necessary for NSV). +# @param ~dumpfile Dump stream to file, for debugging purpose. Disabled if empty. +# @param ~fallible Allow the child source to fail, in which case the output will be (temporarily) stopped. +# @param ~on_start Callback executed when outputting starts. +# @param ~on_stop Callback executed when outputting stops. +# @param s The source to output +def output.shoutcast.lame( + ~id="output.shoutcast.mp3",~start=true, + ~restart=false,~restart_delay=3, + ~host="localhost",~port=8000, + ~user="source",~password="hackme", + ~genre="Misc",~url="http://savonet.sf.net/", + ~description="OCaml Radio!",~public=true, + ~dumpfile="",~name="Use [mount]",~icy_reset=true, + ~lame="lame",~aim="",~icq="",~irc="", + ~fallible=false,~on_start={()},~on_stop={()}, + ~restart_on_crash=false,~restart_on_new_track=false, + ~restart_encoder_delay=3600,~bitrate=128,s) = + icy_reset = if icy_reset then "1" else "0" end + headers = [("icy-aim",aim),("icy-irc",irc), + ("icy-icq",icq),("icy-reset",icy_reset)] + output.icecast.lame( + id=id, headers=headers, lame=lame, + bitrate=bitrate, start=start, + restart=restart, restart_encoder_delay=restart_encoder_delay, + host=host, port=port, user=user, password=password, + genre=genre, url=url, description=description, + public=public, dumpfile=dumpfile, + restart_on_crash=restart_on_crash, + restart_on_new_track=restart_on_new_track, + name=name, mount="/", protocol="icy", + fallible=fallible,on_start=on_start,on_stop=on_stop, + s) +end + +# Output to icecast using the flac command line encoder. +# @category Source / Output +# @param ~id Output's ID +# @param ~start Start output threads on operator initialization. +# @param ~restart Restart output after a failure. By default, liquidsoap will stop if the output failed. +# @param ~restart_delay Delay, in seconds, before attempting new connection, if restart is enabled. +# @param ~restart_on_crash Restart external process on crash. If false, liquidsoap will stop. +# @param ~restart_on_new_track Restart encoder upon new track. If false, the resulting stream will have a single track. +# @param ~restart_encoder_delay Restart the encoder after this delay, in seconds. +# @param ~user User for shout source connection. Useful only in special cases, like with per-mountpoint users. +# @param ~flac The flac binary +# @param ~quality Encoder quality (0..8) +# @param ~dumpfile Dump stream to file, for debugging purpose. Disabled if empty. +# @param ~protocol Protocol of the streaming server: 'http' for Icecast, 'icy' for Shoutcast. +# @param ~fallible Allow the child source to fail, in which case the output will be (temporarily) stopped. +# @param ~on_start Callback executed when outputting starts. +# @param ~on_stop Callback executed when outputting stops. +# @param s The source to output +def output.icecast.flac( + ~id="output.icecast.flac",~start=true, + ~restart=false,~restart_delay=3, + ~host="localhost",~port=8000, + ~user="source",~password="hackme", + ~genre="Misc",~url="http://savonet.sf.net/", + ~description="OCaml Radio!",~public=true, + ~dumpfile="",~mount="Use [name]", + ~name="Use [mount]",~protocol="http", + ~flac="flac",~quality=6, + ~restart_on_crash=false, + ~restart_on_new_track=true, + ~restart_encoder_delay=(-1), + ~fallible=false,~on_start={()},~on_stop={()}, + s) + # We will use raw format, to + # bypass input length value in WAV + # header (input length is not known) + channels = get(default=2,"frame.channels") + samplerate = get(default=44100,"frame.samplerate") + def flac_p(m)= + def option(x) = + "-T #{quote(fst(x))}=#{quote(snd(x))}" + end + m = list.map(option,m) + m = string.concat(separator=" ",m) + "#{flac} --force-raw-format --endian=little --channels=#{channels} \ + --bps=16 --sample-rate=#{samplerate} --sign=signed #{m} \ + -#{quality} --ogg -c -" + end + output.icecast.external(id=id, + process=flac_p,bitrate=(-1),start=start, + restart=restart,restart_delay=restart_delay, + host=host,port=port,user=user,password=password, + genre=genre,url=url,description=description, + public=public,dumpfile=dumpfile, + name=name,mount=mount,protocol=protocol, + fallible=fallible,on_start=on_start,on_stop=on_stop, + restart_on_new_track=restart_on_new_track, + format="ogg",header=false,icy_metadata=false, + restart_on_crash=restart_on_crash, + restart_encoder_delay=restart_encoder_delay, + s) +end + +# Output to icecast using the aacplusenc command line encoder. +# @category Source / Output +# @param ~id Output's ID +# @param ~start Start output threads on operator initialization. +# @param ~restart Restart output after a failure. By default, liquidsoap will stop if the output failed. +# @param ~restart_delay Delay, in seconds, before attempting new connection, if restart is enabled. +# @param ~restart_on_crash Restart external process on crash. If false, liquidsoap will stop. +# @param ~restart_on_new_track Restart encoder upon new track. +# @param ~restart_encoder_delay Restart the encoder after this delay, in seconds. +# @param ~user User for shout source connection. Useful only in special cases, like with per-mountpoint users. +# @param ~aacplusenc The aacplusenc binary +# @param ~bitrate Encoder bitrate +# @param ~dumpfile Dump stream to file, for debugging purpose. Disabled if empty. +# @param ~protocol Protocol of the streaming server: 'http' for Icecast, 'icy' for Shoutcast. +# @param ~fallible Allow the child source to fail, in which case the output will be (temporarily) stopped. +# @param ~on_start Callback executed when outputting starts. +# @param ~on_stop Callback executed when outputting stops. +# @param s The source to output +def output.icecast.aacplusenc( + ~id="output.icecast.aacplusenc",~start=true, + ~restart=false,~restart_delay=3, + ~host="localhost",~port=8000, + ~user="source",~password="hackme", + ~genre="Misc",~url="http://savonet.sf.net/", + ~description="OCaml Radio!",~public=true, + ~dumpfile="",~mount="Use [name]", + ~name="Use [mount]",~protocol="http", + ~aacplusenc="aacplusenc",~bitrate=64, + ~fallible=false,~on_start={()},~on_stop={()}, + ~restart_on_crash=false,~restart_on_new_track=false, + ~restart_encoder_delay=3600,~headers=[],s) + # Metadata update is set by ICY with icecast + def aacplusenc_p(m) + "#{aacplusenc} - - #{bitrate}" + end + output.icecast.external(id=id, + process=aacplusenc_p,bitrate=bitrate,start=start, + restart=restart,restart_delay=restart_delay, + host=host,port=port,user=user,password=password, + genre=genre,url=url,description=description, + public=public,dumpfile=dumpfile, + name=name,mount=mount,protocol=protocol, + fallible=fallible,on_start=on_start,on_stop=on_stop, + header=true,restart_on_crash=restart_on_crash, + restart_on_new_track=restart_on_new_track,headers=headers, + restart_encoder_delay=restart_encoder_delay,format="audio/aacp",s) +end + +# Output to shoutcast using the aacplusenc encoder. +# @category Source / Output +# @param ~id Output's ID +# @param ~start Start output threads on operator initialization. +# @param ~restart Restart output after a failure. By default, liquidsoap will stop if the output failed. +# @param ~restart_delay Delay, in seconds, before attempting new connection, if restart is enabled. +# @param ~restart_on_crash Restart external process on crash. If false, liquidsoap will stop. +# @param ~restart_on_new_track Restart encoder upon new track. +# @param ~restart_encoder_delay Restart the encoder after this delay, in seconds. +# @param ~user User for shout source connection. Useful only in special cases, like with per-mountpoint users. +# @param ~aacplusenc The aacplusenc binary +# @param ~bitrate Encoder bitrate +# @param ~icy_reset Reset shoutcast source buffer upon connecting (necessary for NSV). +# @param ~dumpfile Dump stream to file, for debugging purpose. Disabled if empty. +# @param ~fallible Allow the child source to fail, in which case the output will be (temporarily) stopped. +# @param ~on_start Callback executed when outputting starts. +# @param ~on_stop Callback executed when outputting stops. +# @param s The source to output +def output.shoutcast.aacplusenc( + ~id="output.shoutcast.aacplusenc",~start=true, + ~restart=false,~restart_delay=3, + ~host="localhost",~port=8000, + ~user="source",~password="hackme", + ~genre="Misc",~url="http://savonet.sf.net/", + ~description="OCaml Radio!",~public=true, + ~fallible=false,~on_start={()},~on_stop={()}, + ~dumpfile="",~name="Use [mount]",~icy_reset=true, + ~aim="",~icq="",~irc="",~aacplusenc="aacplusenc", + ~restart_on_crash=false,~restart_on_new_track=false, + ~restart_encoder_delay=3600,~bitrate=64,s) = + icy_reset = if icy_reset then "1" else "0" end + headers = [("icy-aim",aim),("icy-irc",irc), + ("icy-icq",icq),("icy-reset",icy_reset)] + output.icecast.aacplusenc( + id=id, headers=headers, aacplusenc=aacplusenc, + bitrate=bitrate, start=start, + restart=restart, restart_delay=restart_delay, + host=host, port=port, user=user, password=password, + genre=genre, url=url, description=description, + public=public, dumpfile=dumpfile, + fallible=fallible,on_start=on_start,on_stop=on_stop, + restart_on_crash=restart_on_crash, restart_encoder_delay=restart_encoder_delay, + restart_on_new_track=restart_on_new_track, + name=name, mount="/", protocol="icy", + s) +end +%endif + diff --git a/pypo/scripts/library/externals.liq b/pypo/scripts/library/externals.liq index 9a3ed28eb..ede1d2e3d 100644 --- a/pypo/scripts/library/externals.liq +++ b/pypo/scripts/library/externals.liq @@ -35,7 +35,7 @@ if test_process("which flac") then add_decoder(name="FLAC",description="Decode files using the flac \ decoder binary.", test=test_flac,flac_p) else - log(level=3,"flac binary not found: flac decoder disabled.") + log(level=3,"Did not find flac binary: flac decoder disabled.") end %endif @@ -63,15 +63,16 @@ if os.type != "Win32" then end add_metadata_resolver("FLAC",flac_meta) else - log(level=3,"metaflac binary not found: flac metadata resolver disabled.") + log(level=3,"Did not find metaflac binary: flac metadata resolver disabled.") end end # A list of know extensions and content-type for AAC. # Values from http://en.wikipedia.org/wiki/Advanced_Audio_Coding # TODO: can we register a setting for that ?? -aac_mimes = ["audio/aac", "audio/aacp", "audio/3gpp", "audio/3gpp2", "audio/mp4", - "audio/MP4A-LATM", "audio/mpeg4-generic", "audio/x-hx-aac-adts"] +aac_mimes = + ["audio/aac", "audio/aacp", "audio/3gpp", "audio/3gpp2", "audio/mp4", + "audio/MP4A-LATM", "audio/mpeg4-generic", "audio/x-hx-aac-adts"] aac_filexts = ["m4a", "m4b", "m4p", "m4v", "m4r", "3gp", "mp4", "aac"] @@ -143,7 +144,48 @@ if os.type != "Win32" then end add_metadata_resolver("FAAD",faad_meta) else - log(level=3,"faad binary not found: faad decoder disabled.") + log(level=3,"Did not find faad binary: faad decoder disabled.") end end +# Standard function for displaying metadata. +# Shows artist and title, using "Unknown" when a field is empty. +# @param m Metadata packet to be displayed. +def string_of_metadata(m) + artist = m["artist"] + title = m["title"] + artist = if ""==artist then "Unknown" else artist end + title = if ""==title then "Unknown" else title end + "#{artist} -- #{title}" +end + +# Use X On Screen Display to display metadata info. +# @param ~color Color of the text. +# @param ~position Position of the text (top|middle|bottom). +# @param ~font Font used (xfontsel is your friend...) +# @param ~display Function used to display a metadata packet. +def osd_metadata(~color="green",~position="top", + ~font="-*-courier-*-r-*-*-*-240-*-*-*-*-*-*", + ~display=string_of_metadata, + s) + osd = 'osd_cat -p #{position} --font #{quote(font)}' + ^ ' --color #{color}' + def feedback(m) + system("echo #{quote(display(m))} | #{osd} &") + end + on_metadata(feedback,s) +end + +# Use notify to display metadata info. +# @param ~urgency Urgency (low|normal|critical). +# @param ~icon Icon filename or stock icon to display. +# @param ~time Timeout in milliseconds. +# @param ~display Function used to display a metadata packet. +# @param ~title Title of the notification message. +def notify_metadata(~urgency="low",~icon="stock_smiley-22",~time=3000, + ~display=string_of_metadata, + ~title="Liquidsoap: new track",s) + send = 'notify-send -i #{icon} -u #{urgency}' + ^ ' -t #{time} #{quote(title)} ' + on_metadata(fun (m) -> system(send^quote(display(m))),s) +end diff --git a/pypo/scripts/library/extract-replaygain b/pypo/scripts/library/extract-replaygain index 726fa5928..20afbc179 100755 --- a/pypo/scripts/library/extract-replaygain +++ b/pypo/scripts/library/extract-replaygain @@ -15,7 +15,7 @@ if (($file =~ /\.mp3$/i) || (test_mime($file) =~ /audio\/mpeg/)) { if (`which mp3gain`) { - my $out = `nice -n 20 mp3gain -q "$file" 2> /dev/null` ; + my $out = `mp3gain -q "$file" 2> /dev/null` ; $out =~ /Recommended "Track" dB change: (.*)$/m || die ; print "$1 dB\n" ; @@ -29,7 +29,7 @@ if (($file =~ /\.mp3$/i) || (test_mime($file) =~ /audio\/mpeg/)) { if ((`which vorbisgain`) && (`which ogginfo`)) { - system("nice -n 20 vorbisgain -q -f \"$file\" 2>/dev/null >/dev/null") ; + system("vorbisgain -q -f \"$file\" 2>/dev/null >/dev/null") ; my $info = `ogginfo "$file"` ; $info =~ /REPLAYGAIN_TRACK_GAIN=(.*) dB/ || die ; print "$1 dB\n" ; @@ -52,7 +52,7 @@ if (($file =~ /\.mp3$/i) || (test_mime($file) =~ /audio\/mpeg/)) { } else { - system("nice -n 20 metaflac --add-replay-gain \"$file\" \ + system("metaflac --add-replay-gain \"$file\" \ 2>/dev/null >/dev/null") ; $info = `metaflac --show-tag=REPLAYGAIN_TRACK_GAIN "$file"` ; $info =~ /REPLAYGAIN_TRACK_GAIN=(.*) dB/ || die "Error in $file" ; diff --git a/pypo/scripts/library/interactive.screen b/pypo/scripts/library/interactive.screen new file mode 100644 index 000000000..0466001c0 --- /dev/null +++ b/pypo/scripts/library/interactive.screen @@ -0,0 +1,15 @@ +# Launch with: screen -c interactive.screen +screen -t Liquidsoap liquidsoap --interactive 'set("log.file.path","/tmp/interactive.log") system("echo \"setenv PID #{getpid()}\" > /tmp/interactive.env")' +verbose +# Yeah, this is a trick +# to wait for interactive.env +# to be created +logfile /dev/null +log +source /tmp/interactive.env +screen -t Log tail --pid=$PID -f /tmp/interactive.log +split -v +select 0 +focus +select 1 +focus diff --git a/pypo/scripts/library/liquidsoap.gentoo.initd b/pypo/scripts/library/liquidsoap.gentoo.initd new file mode 100644 index 000000000..075e27bdc --- /dev/null +++ b/pypo/scripts/library/liquidsoap.gentoo.initd @@ -0,0 +1,43 @@ +#!/sbin/runscript + +user=liquidsoap +group=liquidsoap +prefix=/usr/local +exec_prefix=${prefix} +confdir=${prefix}/etc/liquidsoap +liquidsoap=${exec_prefix}/bin/liquidsoap +rundir=${prefix}/var/run/liquidsoap + +depend() { + after net icecast +} + +start() { + cd $confdir + for liq in *.liq ; do + if test $liq != '*.liq' ; then + ebegin "Starting $liq" + start-stop-daemon --start --quiet --pidfile $rundir/${liq%.liq}.pid \ + --chuid $user:$group --exec $liquidsoap -- -d $confdir/$liq + eend $? + fi + done +} + +stop() { + cd $rundir + for liq in *.pid ; do + if test $liq != '*.pid' ; then + ebegin "Stopping $liq" + start-stop-daemon --stop --quiet --pidfile $liq + eend $? + fi + done +} + +restart() { + svc_stop + einfo "Sleeping 4 seconds ..." + sleep 4 + svc_start +} diff --git a/pypo/scripts/library/liquidsoap.gentoo.initd.in b/pypo/scripts/library/liquidsoap.gentoo.initd.in new file mode 100755 index 000000000..3281fe925 --- /dev/null +++ b/pypo/scripts/library/liquidsoap.gentoo.initd.in @@ -0,0 +1,43 @@ +#!/sbin/runscript + +user=@install_user@ +group=@install_group@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +confdir=@sysconfdir@/liquidsoap +liquidsoap=@bindir@/liquidsoap +rundir=@localstatedir@/run/liquidsoap + +depend() { + after net icecast +} + +start() { + cd $confdir + for liq in *.liq ; do + if test $liq != '*.liq' ; then + ebegin "Starting $liq" + start-stop-daemon --start --quiet --pidfile $rundir/${liq%.liq}.pid \ + --chuid $user:$group --exec $liquidsoap -- -d $confdir/$liq + eend $? + fi + done +} + +stop() { + cd $rundir + for liq in *.pid ; do + if test $liq != '*.pid' ; then + ebegin "Stopping $liq" + start-stop-daemon --stop --quiet --pidfile $liq + eend $? + fi + done +} + +restart() { + svc_stop + einfo "Sleeping 4 seconds ..." + sleep 4 + svc_start +} diff --git a/pypo/scripts/library/liquidsoap.initd b/pypo/scripts/library/liquidsoap.initd new file mode 100644 index 000000000..02651ead3 --- /dev/null +++ b/pypo/scripts/library/liquidsoap.initd @@ -0,0 +1,63 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: liquidsoap +# Required-Start: $remote_fs $network $time +# Required-Stop: $remote_fs $network $time +# Should-Start: +# Should-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Starts the liquidsoap daemon +# Description: +### END INIT INFO + +user=liquidsoap +group=liquidsoap +prefix=/usr/local +exec_prefix=${prefix} +confdir=${prefix}/etc/liquidsoap +liquidsoap=${exec_prefix}/bin/liquidsoap +rundir=${prefix}/var/run/liquidsoap + +# Test if $rundir exists +if [ ! -d $rundir ]; then + mkdir -p $rundir; + chown $user:$group $rundir +fi + +case "$1" in + stop) + echo -n "Stopping channels: " + cd $rundir + for liq in *.pid ; do + if test $liq != '*.pid' ; then + echo -n "$liq " + start-stop-daemon --stop --quiet --pidfile $liq --retry 4 + fi + done + echo "OK" + ;; + + start) + echo -n "Starting channels: " + cd $confdir + for liq in *.liq ; do + if test $liq != '*.liq' ; then + echo -n "$liq " + start-stop-daemon --start --quiet --pidfile $rundir/${liq%.liq}.pid \ + --chuid $user:$group --exec $liquidsoap -- -d $confdir/$liq + fi + done + echo "OK" + ;; + + restart|force-reload) + $0 stop + $0 start + ;; + + *) + echo "Usage: $0 {start|stop|restart|force-reload}" + exit 1 + ;; +esac diff --git a/pypo/scripts/library/liquidsoap.initd.in b/pypo/scripts/library/liquidsoap.initd.in new file mode 100755 index 000000000..9b447a159 --- /dev/null +++ b/pypo/scripts/library/liquidsoap.initd.in @@ -0,0 +1,63 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: liquidsoap +# Required-Start: $remote_fs $network $time +# Required-Stop: $remote_fs $network $time +# Should-Start: +# Should-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Starts the liquidsoap daemon +# Description: +### END INIT INFO + +user=@install_user@ +group=@install_group@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +confdir=@sysconfdir@/liquidsoap +liquidsoap=@bindir@/liquidsoap +rundir=@localstatedir@/run/liquidsoap + +# Test if $rundir exists +if [ ! -d $rundir ]; then + mkdir -p $rundir; + chown $user:$group $rundir +fi + +case "$1" in + stop) + echo -n "Stopping channels: " + cd $rundir + for liq in *.pid ; do + if test $liq != '*.pid' ; then + echo -n "$liq " + start-stop-daemon --stop --quiet --pidfile $liq --retry 4 + fi + done + echo "OK" + ;; + + start) + echo -n "Starting channels: " + cd $confdir + for liq in *.liq ; do + if test $liq != '*.liq' ; then + echo -n "$liq " + start-stop-daemon --start --quiet --pidfile $rundir/${liq%.liq}.pid \ + --chuid $user:$group --exec $liquidsoap -- -d $confdir/$liq + fi + done + echo "OK" + ;; + + restart|force-reload) + $0 stop + $0 start + ;; + + *) + echo "Usage: $0 {start|stop|restart|force-reload}" + exit 1 + ;; +esac diff --git a/pypo/scripts/library/liquidsoap.logrotate b/pypo/scripts/library/liquidsoap.logrotate new file mode 100644 index 000000000..93075be92 --- /dev/null +++ b/pypo/scripts/library/liquidsoap.logrotate @@ -0,0 +1,15 @@ +/usr/local/var/log/liquidsoap/*.log { + compress + rotate 5 + size 300k + missingok + notifempty + sharedscripts + postrotate + for liq in /usr/local/var/run/liquidsoap/*.pid ; do + if test $liq != '/usr/local/var/run/liquidsoap/*.pid' ; then + start-stop-daemon --stop --signal USR1 --quiet --pidfile $liq + fi + done + endscript +} diff --git a/pypo/scripts/library/liquidsoap.logrotate.in b/pypo/scripts/library/liquidsoap.logrotate.in new file mode 100644 index 000000000..9b154ae45 --- /dev/null +++ b/pypo/scripts/library/liquidsoap.logrotate.in @@ -0,0 +1,15 @@ +@localstatedir@/log/liquidsoap/*.log { + compress + rotate 5 + size 300k + missingok + notifempty + sharedscripts + postrotate + for liq in @localstatedir@/run/liquidsoap/*.pid ; do + if test $liq != '@localstatedir@/run/liquidsoap/*.pid' ; then + start-stop-daemon --stop --signal USR1 --quiet --pidfile $liq + fi + done + endscript +} diff --git a/pypo/scripts/library/liquidtts.in b/pypo/scripts/library/liquidtts.in new file mode 100644 index 000000000..c7e2f906f --- /dev/null +++ b/pypo/scripts/library/liquidtts.in @@ -0,0 +1,11 @@ +#!/bin/sh + +# This script is called from liquidsoap for generating a file +# for "say:voice/text" URIs. +# Usage: liquidtts text output_file voice + +echo $1 | @TEXT2WAVE@ -f 44100 > $2.tmp.wav && @SOX@ 2> /dev/null > /dev/null +return=$? +@RM@ $2.tmp.wav +@NORMALIZE@ $2 2> /dev/null > /dev/null +exit $return diff --git a/pypo/scripts/library/shoutcast.liq b/pypo/scripts/library/shoutcast.liq index eacd7b7a9..2ac370440 100644 --- a/pypo/scripts/library/shoutcast.liq +++ b/pypo/scripts/library/shoutcast.liq @@ -41,7 +41,7 @@ def output.shoutcast( restart=restart, restart_delay=restart_delay, host=host, port=port, user=user, password=password, genre=genre, url=url, description="UNUSED", - public=public, dumpfile=dumpfile, + public=public, dumpfile=dumpfile,encoding="ISO-8859-1", name=name, mount="/", protocol="icy", fallible=fallible,on_start=on_start,on_stop=on_stop, s) diff --git a/pypo/scripts/library/test.liq b/pypo/scripts/library/test.liq new file mode 100644 index 000000000..ac78f2037 --- /dev/null +++ b/pypo/scripts/library/test.liq @@ -0,0 +1,33 @@ +set("log.file",false) + +echo = fun (x) -> system("echo "^quote(x)) + +def test(lbl,f) + if f() then echo(lbl) else system("echo fail "^lbl) end +end + +test("1",{ 1==1 }) +test("2",{ 1+1==2 }) +test("3",{ (-1)+2==1 }) +test("4",{ (-1)+2 <= 3*2 }) +test("5",{ true }) +test("6",{ true and true }) +test("7",{ 1==1 and 1==1 }) +test("8",{ (1==1) and (1==1) }) +test("9",{ true and (-1)+2 <= 3*2 }) + +l = [ ("bla",""), ("bli","x"), ("blo","xx"), ("blu","xxx"), ("dix","10") ] +echo(l["dix"]) +test("11",{ 2 == list.length(string.split(separator="",l["blo"])) }) + +%ifdef foobarbaz + if = if is not a well-formed expression, and we do not care... +%endif + +echo("1#{1+1}") +echo(string_of(int_of_float(float_of_string(default=13.,"blah")))) + +f=fun(x)->x +# Checking that the following is not recursive: +f=fun(x)->f(x) +print(f(14)) diff --git a/pypo/scripts/library/typing.liq b/pypo/scripts/library/typing.liq new file mode 100644 index 000000000..2a4c7eaa7 --- /dev/null +++ b/pypo/scripts/library/typing.liq @@ -0,0 +1,112 @@ +# Check these examples with: liquidsoap --no-libs -i -c typing.liq + +# TODO Throughout this file, parsing locations displayed in error messages +# are often much too inaccurate. + +set("log.file",false) + +# Check that some polymorphism is allowed. +# id :: (string,'a)->'a +def id(a,b) + log(a) + b +end +ignore("bla"==id("bla","bla")) +ignore(0==id("zero",0)) + +# Reporting locations for the next error is non-trivial, because it is about +# an instantiation of the type of id. The deep error doesn't have relevant +# information about why the int should be a string, the outer one has. +# id(0,0) + +# Polymorphism is limited to outer generalizations, this is not system F. +# apply :: ((string)->'a)->'a +def apply(f) + f("bla") + # f is not polymorphic, the following is forbidden: + # f(0) + # f(f) +end + +# The level checks forbid abusive generalization. +# id' :: ('a)->'a +def id'(x) + # If one isn't careful about levels/scoping, f2 gets the type ('a)->'b + # and so does twisted_id. + def f2(y) + x + end + f2(x) +end + +# More errors... +# 0=="0" +# [3,""] + +# Subtyping, functions and lists. +f1 = fun () -> 3 +f2 = fun (a=1) -> a + +# This is OK, l1 is a list of elements of type f1. +l1 = [f1,f2] +list.iter(fun (f) -> log(string_of(f())), l1) +# Forbidden. Indeed, f1 doesn't accept any argument -- although f2 does. +# Here the error message may even be too detailed since it goes back to the +# definition of l1 and requires that f1 has type (int)->int. +# list.iter(fun (f) -> log(string_of(f(42))), l1) + +# Actually, this is forbidden too, but the reason is more complex... +# The infered type for the function is ((int)->int)->unit, +# and (int)->int is not a subtype of (?int)->int. +# There's no most general answer here since (?int)->int is not a +# subtype of (int)->int either. +# list.iter(fun (f) -> log(string_of(f(42))), [f2]) + +# Unlike l1, this is not OK, since we don't leave open subtyping constraints +# while infering types. +# I hope we can make the inference smarter in the future, without obfuscating +# the error messages too much. +# The type error here shows the use of all the displayed positions: +# f1 has type t1, f2 has type t2, t1 should be <: t2 +# l2 = [ f2, f1 ] + +# An error where contravariance flips the roles of both sides.. +# [fun (x) -> x+1, fun (y) -> y^"."] + +# An error without much locations.. +# TODO An explaination about the missing label would help a lot here. +# def f(f) +# f(output.icecast.vorbis) +# f(output.icecast.mp3) +# end + +# This causes an occur-check error. +# TODO The printing of the types breaks the sharing of one EVAR +# across two types. Here the sharing is actually the origin of the occur-check +# error. And it's not easy to understand.. +# omega = fun (x) -> x(x) + +# Now let's test ad-hoc polymorphism. + +echo = fun(x) -> system("echo #{quote(string_of(x))}") + +echo("bla") +echo((1,3.12)) +echo(1 + 1) +echo(1. + 2.14) + +# string is not a Num +# echo("bl"+"a") + +echo(1 <= 2) +echo((1,2) == (1,3)) + +# float <> int +# echo(1 == 2.) + +# source is not an Ord +# echo(blank()==blank()) + +def sum_eq(a,b) + a+b == a +end diff --git a/pypo/scripts/library/utils.liq b/pypo/scripts/library/utils.liq index fba616846..da4224dc0 100644 --- a/pypo/scripts/library/utils.liq +++ b/pypo/scripts/library/utils.liq @@ -2,7 +2,7 @@ # Turn a source into an infaillible source. # by adding blank when the source is not available. # @param s the source to turn infaillible -# @category Source / Input +# @category Source / Track Processing def mksafe(s) fallback(id="mksafe",track_sensitive=false,[s,blank(id="safe_blank")]) end @@ -87,11 +87,14 @@ end # Removes all metadata coming from a source # @category Source / Track Processing -def clear_metadata(s) - def map(m) - [] - end - map_metadata(map,update=false,strip=true,s) +def drop_metadata(s) + map_metadata(fun(_)->[],update=false,strip=true,insert_missing=false,s) +end + +# Merge all tracks from a source, provided that it does not fail +# @category Source / Track Processing +def merge_tracks(s) + sequence(merge=true,[s]) end output.prefered=output.dummy @@ -125,7 +128,7 @@ in = fun () -> blank() in = fun () -> input.portaudio(id="pa_mic") %endif # Create a source from the first available input driver in this list: -# portaudio, alsa, oss, blank +# portaudio, alsa, oss, blank. # @category Source / Input def in() in() @@ -208,12 +211,6 @@ def say_metadata interactive=false)) end -# Relay the audio stream of Dolebraï, a libre music netradio running liquidsoap. -# @category Source / Input -def dolebrai () - input.http(id="dolebrai","http://dolebrai.net:8000/dolebrai.ogg") -end - %ifdef soundtouch # Increases the pitch, making voices sound like on helium. # @category Source / Sound Processing @@ -237,6 +234,72 @@ def test_process(command) end end +# Split an url of the form foo?arg=bar&arg2=bar2 +# into ("foo",[("arg","bar"),("arg2","bar2")] +# @category String +# @param uri Url to split +def url.split(uri) = + ret = string.extract(pattern="([^\?]*)\?(.*)",uri) + args = ret["2"] + if args != "" then + l = string.split(separator="&",args) + def f(x) = + ret = string.split(separator="=",x) + (url.decode(list.nth(ret,0)), + url.decode(list.nth(ret,1))) + end + l = list.map(f,l) + (ret["1"],l) + else + (uri,[]) + end +end + +# Register a server/telnet command to +# update a source's metadata. Returns +# a new source, which will receive the +# updated metadata. Semantics is the +# same as pre 1.0 insert_metadata operator, +# i.e. @insert key1="val1",key2="val2",..@ +# @category Source / Track Processing +# @param ~id Force the value of the source ID. +def server.insert_metadata(~id="",s) = + x = insert_metadata(id=id,s) + insert = fst(x) + s = snd(x) + def insert(s) = + l = string.split(separator='([^=]+\s*=\s*"(\\"|[^"])*")\s*,\s*',s) + def f(l,x) = + sub = fun (s) -> string.replace(pattern='\\"',fun (_) -> '"',s) + if x != "" then + ret = string.extract(pattern='([^=]+)\s*=\s*"((?:\\"|[^"])*)"',x) + if ret["1"] != "" then + list.append(l,[(ret["1"], + sub(ret["2"]))]) + else + l + end + else + l + end + end + meta = list.fold(f,[],l) + if meta != [] then + insert(meta) + "Done" + else + "Syntax error or no metadata given. \ + Use key1=\"val1\",key2=\"val2\",.." + end + end + id = source.id(s) + server.register(namespace="#{id}", + description="Insert a metadata chunk.", + usage="insert key1=\"val1\",key2=\"val2\",..", + "insert",insert) + s +end + # Get the base name of a path. # Implemented using the corresponding shell command. # @category System @@ -559,14 +622,18 @@ def enable_replaygain_metadata( end # Create a log of clock times for all the clocks initially present. -# The log is in simple format, which you can notably directly use with gnuplot. +# The log is in a simple format which you can directly use with gnuplot. # @category Liquidsoap # @param ~interval Polling interval. -def log_clocks(~interval=1.,logfile) +# @param ~delay Delay before setting up the clock logger. This should \ +# be used to ensure that the logger starts only after \ +# the clocks are created. +# @param unlabeled Path of the log file. +def log_clocks(~delay=0.,~interval=1.,logfile) # Get the current clocks clocks = list.map(fst,get_clock_status()) # Column headers - system("echo \# #{string.concat(separator=' ',clocks)} > #{logfile}") + system("echo \# #{string.concat(separator=' ',clocks)} > #{(logfile:string)}") def report() status = get_clock_status() status = list.map(fun (x) -> (fst(x),string_of(snd(x))), status) @@ -574,5 +641,9 @@ def log_clocks(~interval=1.,logfile) system("echo #{string.concat(separator=' ',status)} >> #{logfile}") interval end - add_timeout(interval,report) + if delay<=0. then + add_timeout(interval,report) + else + add_timeout(delay,{add_timeout(interval,report) (-1.)}) + end end diff --git a/pypo/scripts/ls_script.liq b/pypo/scripts/ls_script.liq index f6804a14b..b4de4d692 100644 --- a/pypo/scripts/ls_script.liq +++ b/pypo/scripts/ls_script.liq @@ -7,6 +7,7 @@ # include configuration # ######################################## +%include "library/pervasives.liq" %include "ls_config.liq" %include "library.liq" %include "include_dynamic_vars.liq" @@ -49,8 +50,9 @@ end ####################################################################### silence = single("/opt/pypo/files/basic/silence.mp3") jingles_cc = playlist("/opt/pypo/files/jingles/jcc") -fallback_couchcaster = playlist("/opt/pypo/files/fallback_couchcaster") -fallback_couchcaster = audio_to_stereo(fallback_couchcaster) +fallback_airtime = playlist("/opt/pypo/files/basic/silence-playlist.lsp") +fallback_airtime = audio_to_stereo(fallback_airtime) + # default default = silence @@ -69,7 +71,7 @@ source = fallback(track_sensitive=false,transitions=[dp_to_scheduler],[strip_bla %include "include_live_in.liq" -live = fallback(track_sensitive=false,[strip_blank(threshold=silence_threshold,length=silence_time,live),fallback_couchcaster]) +live = fallback(track_sensitive=false,[strip_blank(threshold=silence_threshold,length=silence_time,live),fallback_airtime]) live = switch(track_sensitive=false, [({!live_active},live)]) source = fallback(track_sensitive=false,transitions=[to_live_s, to_scheduler_s],[live, source])