From 3dc1380fab544a35fea1d6c585af1c6ead16e514 Mon Sep 17 00:00:00 2001 From: martin Date: Wed, 6 Jul 2011 11:14:51 -0400 Subject: [PATCH] -updated liquidsoap scripts + library to be liquidsoap-beta2 compliant --- .../library/external-todo.liq | 10 +- .../liquidsoap_scripts/library/externals.liq | 4 +- .../pypo/liquidsoap_scripts/library/flows.liq | 113 ++++++++++++++++++ .../library/interactive.screen | 15 --- .../library/liquidsoap.gentoo.initd | 43 ------- .../library/liquidsoap.initd | 63 ---------- .../library/liquidsoap.logrotate | 15 --- .../pypo/liquidsoap_scripts/library/liquidtts | 11 -- .../liquidsoap_scripts/library/pervasives.liq | 1 + .../liquidsoap_scripts/library/shoutcast.liq | 4 +- .../library/tests/BUG403.liq | 21 ++++ .../library/tests/LS268.liq | 10 ++ .../library/tests/LS354-1.liq | 11 ++ .../library/tests/LS354-2.liq | 15 +++ .../library/tests/LS460.liq | 17 +++ .../library/tests/LS503.liq | 13 ++ .../liquidsoap_scripts/library/tests/Makefile | 16 +++ .../liquidsoap_scripts/library/tests/eval.liq | 43 +++++++ .../library/tests/type_errors.pl | 82 +++++++++++++ .../library/tests/typing.liq | 112 +++++++++++++++++ .../library/type_printing.liq | 61 ++++++++++ .../pypo/liquidsoap_scripts/library/utils.liq | 89 +++++++++----- .../pypo/liquidsoap_scripts/liquidsoap.cfg | 2 +- .../pypo/liquidsoap_scripts/ls_script.liq | 14 ++- 24 files changed, 593 insertions(+), 192 deletions(-) create mode 100644 python_apps/pypo/liquidsoap_scripts/library/flows.liq delete mode 100644 python_apps/pypo/liquidsoap_scripts/library/interactive.screen delete mode 100644 python_apps/pypo/liquidsoap_scripts/library/liquidsoap.gentoo.initd delete mode 100644 python_apps/pypo/liquidsoap_scripts/library/liquidsoap.initd delete mode 100644 python_apps/pypo/liquidsoap_scripts/library/liquidsoap.logrotate delete mode 100755 python_apps/pypo/liquidsoap_scripts/library/liquidtts create mode 100644 python_apps/pypo/liquidsoap_scripts/library/tests/BUG403.liq create mode 100644 python_apps/pypo/liquidsoap_scripts/library/tests/LS268.liq create mode 100755 python_apps/pypo/liquidsoap_scripts/library/tests/LS354-1.liq create mode 100755 python_apps/pypo/liquidsoap_scripts/library/tests/LS354-2.liq create mode 100644 python_apps/pypo/liquidsoap_scripts/library/tests/LS460.liq create mode 100644 python_apps/pypo/liquidsoap_scripts/library/tests/LS503.liq create mode 100644 python_apps/pypo/liquidsoap_scripts/library/tests/Makefile create mode 100644 python_apps/pypo/liquidsoap_scripts/library/tests/eval.liq create mode 100755 python_apps/pypo/liquidsoap_scripts/library/tests/type_errors.pl create mode 100644 python_apps/pypo/liquidsoap_scripts/library/tests/typing.liq create mode 100644 python_apps/pypo/liquidsoap_scripts/library/type_printing.liq diff --git a/python_apps/pypo/liquidsoap_scripts/library/external-todo.liq b/python_apps/pypo/liquidsoap_scripts/library/external-todo.liq index ddd9029b9..24ae18cdc 100644 --- a/python_apps/pypo/liquidsoap_scripts/library/external-todo.liq +++ b/python_apps/pypo/liquidsoap_scripts/library/external-todo.liq @@ -71,7 +71,7 @@ def output.icecast.lame( ~host="localhost",~port=8000, ~user="source",~password="hackme", ~genre="Misc",~url="http://savonet.sf.net/", - ~description="OCaml Radio!",~public=true, + ~description="Liquidsoap Radio!",~public=true, ~dumpfile="",~mount="Use [name]", ~name="Use [mount]",~protocol="http", ~lame="lame",~bitrate=128,~swap=false, @@ -130,7 +130,7 @@ def output.shoutcast.lame( ~host="localhost",~port=8000, ~user="source",~password="hackme", ~genre="Misc",~url="http://savonet.sf.net/", - ~description="OCaml Radio!",~public=true, + ~description="Liquidsoap Radio!",~public=true, ~dumpfile="",~name="Use [mount]",~icy_reset=true, ~lame="lame",~aim="",~icq="",~irc="", ~fallible=false,~on_start={()},~on_stop={()}, @@ -177,7 +177,7 @@ def output.icecast.flac( ~host="localhost",~port=8000, ~user="source",~password="hackme", ~genre="Misc",~url="http://savonet.sf.net/", - ~description="OCaml Radio!",~public=true, + ~description="Liquidsoap Radio!",~public=true, ~dumpfile="",~mount="Use [name]", ~name="Use [mount]",~protocol="http", ~flac="flac",~quality=6, @@ -240,7 +240,7 @@ def output.icecast.aacplusenc( ~host="localhost",~port=8000, ~user="source",~password="hackme", ~genre="Misc",~url="http://savonet.sf.net/", - ~description="OCaml Radio!",~public=true, + ~description="Liquidsoap Radio!",~public=true, ~dumpfile="",~mount="Use [name]", ~name="Use [mount]",~protocol="http", ~aacplusenc="aacplusenc",~bitrate=64, @@ -288,7 +288,7 @@ def output.shoutcast.aacplusenc( ~host="localhost",~port=8000, ~user="source",~password="hackme", ~genre="Misc",~url="http://savonet.sf.net/", - ~description="OCaml Radio!",~public=true, + ~description="Liquidsoap Radio!",~public=true, ~fallible=false,~on_start={()},~on_stop={()}, ~dumpfile="",~name="Use [mount]",~icy_reset=true, ~aim="",~icq="",~irc="",~aacplusenc="aacplusenc", diff --git a/python_apps/pypo/liquidsoap_scripts/library/externals.liq b/python_apps/pypo/liquidsoap_scripts/library/externals.liq index 1a50f4b95..78f48197f 100644 --- a/python_apps/pypo/liquidsoap_scripts/library/externals.liq +++ b/python_apps/pypo/liquidsoap_scripts/library/externals.liq @@ -28,8 +28,6 @@ def enable_external_flac_decoder() = # If the value is not an int, this returns 0 and we are ok :) int_of_string(channels) else - # Try to detect using mime test.. - mime = get_mime(file) if string.match(pattern="flac",file) then # We do not know the number of audio channels # so setting to -1 @@ -76,7 +74,7 @@ end %ifdef add_oblivious_decoder # Enable or disable external FAAD (AAC/AAC+/M4A) decoders. -# Requires faad binary in the path for audio decoding and +# Requires faad binary in the path for audio decoding and # metaflac binary for metadata. Does not work on Win32. # Please note that built-in support for faad is available # in liquidsoap if compiled and should be preferred over diff --git a/python_apps/pypo/liquidsoap_scripts/library/flows.liq b/python_apps/pypo/liquidsoap_scripts/library/flows.liq new file mode 100644 index 000000000..7828d2b9b --- /dev/null +++ b/python_apps/pypo/liquidsoap_scripts/library/flows.liq @@ -0,0 +1,113 @@ +# BIG TODO: +# - Check for errors +# - Unregister radio and streams + +# Register a radio on Liquidsoap Flows. +# @category Liquidsoap +# @param ~radio Name of the radio. +# @param ~website URL of the website of the radio. +# @param ~description Description of the radio. +# @param ~genre Genre of the radio (rock or rap or etc.). +# @param ~streams List of streams for the radio described by \ +# a pair of strings consisting of the format of the stream \ +# and the url of the stream. The format should be \ +# of the form "ogg/128k" consisting of the codec and \ +# the bitrate, separated by "/". +def register_flow(~server="",~user="default",~password="default", + ~email="",~radio,~website,~description,~genre, + ~streams,s) + + # If the server is "", we get the server from sf.net + server = + if server == "" then + server = http.get("http://savonet.sourceforge.net/flows_server") + html_status = snd(fst(fst(fst(server)))) + if html_status == 200 then + snd(server) + else + # If sf is down, we use the hardcoded server + "http://savonet.rastageeks.org/liqflows.py" + end + else + server + end + log(level=4,"Flows server: #{server}") + + # Initial variables + ping_period = 600. # Pinging period in seconds + + # Fix default parameters + # and set request function. + base_params = [("v", "0.0"), + ("user",user), + ("password",password), + ("email",email), + ("radio",radio)] + def request(~cmd,~params) = + log = log(label=radio) + log(level=4,"Processing command #{cmd} with arguments:") + def log_arg(x) = + label = fst(x) + value = snd(x) + log(level=4," #{label}: #{value}") + end + list.iter(log_arg,params) + + cmd = url.encode(cmd) + params = list.append(base_params,params) + def f(z) = + x = fst(z) + y = url.encode(snd(z)) + "#{x}=#{y}" + end + params = string.concat(separator="&",list.map(f,params)) + url = "#{server}?cmd=#{cmd}&#{params}" + + # TODO: do something with errors! + answer = http.get(url) + x = fst(answer) + status = fst(x) + y = fst(status) + protocol = fst(y) + code = snd(y) + desc = snd(status) + headers = snd(x) + data = snd(answer) + log(level=4,"Response status: #{protocol} #{code} #{desc}") + log(level=4,"Response headers:") + list.iter(log_arg,headers) + log(level=4,"Response content: #{data}") + end + + # Register radio + params = [("radio_website",website), + ("radio_description",description), + ("radio_genre",genre)] + request(cmd="add radio",params=params) + + # Ping + def ping() = + ignore(request(cmd="ping radio",params=[])) + ping_period + end + add_timeout(fast=false,ping_period,ping) + + # Register streams + def register_stream(format_url) + format = fst(format_url); + url = snd(format_url); + params = [("stream_format",format),("stream_url",url)] + request(cmd="add stream",params=params) + end + request(cmd="clear streams",params=[]) + list.iter(register_stream,streams) + + # Metadata update + def metadata(m) = + artist = m["artist"] + title = m["title"] + params = [("m_title",title),("m_artist",artist)] + request(cmd="metadata",params=params) + end + on_metadata(metadata,s) +end diff --git a/python_apps/pypo/liquidsoap_scripts/library/interactive.screen b/python_apps/pypo/liquidsoap_scripts/library/interactive.screen deleted file mode 100644 index 0466001c0..000000000 --- a/python_apps/pypo/liquidsoap_scripts/library/interactive.screen +++ /dev/null @@ -1,15 +0,0 @@ -# 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/python_apps/pypo/liquidsoap_scripts/library/liquidsoap.gentoo.initd b/python_apps/pypo/liquidsoap_scripts/library/liquidsoap.gentoo.initd deleted file mode 100644 index 075e27bdc..000000000 --- a/python_apps/pypo/liquidsoap_scripts/library/liquidsoap.gentoo.initd +++ /dev/null @@ -1,43 +0,0 @@ -#!/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/python_apps/pypo/liquidsoap_scripts/library/liquidsoap.initd b/python_apps/pypo/liquidsoap_scripts/library/liquidsoap.initd deleted file mode 100644 index 02651ead3..000000000 --- a/python_apps/pypo/liquidsoap_scripts/library/liquidsoap.initd +++ /dev/null @@ -1,63 +0,0 @@ -#!/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/python_apps/pypo/liquidsoap_scripts/library/liquidsoap.logrotate b/python_apps/pypo/liquidsoap_scripts/library/liquidsoap.logrotate deleted file mode 100644 index 93075be92..000000000 --- a/python_apps/pypo/liquidsoap_scripts/library/liquidsoap.logrotate +++ /dev/null @@ -1,15 +0,0 @@ -/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/python_apps/pypo/liquidsoap_scripts/library/liquidtts b/python_apps/pypo/liquidsoap_scripts/library/liquidtts deleted file mode 100755 index ad17a248c..000000000 --- a/python_apps/pypo/liquidsoap_scripts/library/liquidtts +++ /dev/null @@ -1,11 +0,0 @@ -#!/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 | /usr/bin/text2wave -f 44100 > $2.tmp.wav && /usr/bin/sox $2.tmp.wav -t wav -c 2 -r 44100 $2 2> /dev/null > /dev/null -return=$? -/bin/rm $2.tmp.wav -false $2 2> /dev/null > /dev/null -exit $return diff --git a/python_apps/pypo/liquidsoap_scripts/library/pervasives.liq b/python_apps/pypo/liquidsoap_scripts/library/pervasives.liq index eecf31e23..93ad95202 100644 --- a/python_apps/pypo/liquidsoap_scripts/library/pervasives.liq +++ b/python_apps/pypo/liquidsoap_scripts/library/pervasives.liq @@ -2,3 +2,4 @@ %include "externals.liq" %include "shoutcast.liq" %include "lastfm.liq" +%include "flows.liq" diff --git a/python_apps/pypo/liquidsoap_scripts/library/shoutcast.liq b/python_apps/pypo/liquidsoap_scripts/library/shoutcast.liq index 2ac370440..e7dc167c9 100644 --- a/python_apps/pypo/liquidsoap_scripts/library/shoutcast.liq +++ b/python_apps/pypo/liquidsoap_scripts/library/shoutcast.liq @@ -14,7 +14,7 @@ # @param ~on_stop Callback executed when outputting stops. # @param ~on_connect Callback executed when connection starts. # @param ~on_disconnect Callback executed when connection stops. -# @param ~icy_metadata Send new metadata using the ICY protocol. One of: "guess", "true", "false" +# @param ~icy_metadata Send new metadata using the ICY protocol. One of: "guess", "true", "false" # @param ~format Format, e.g. "audio/ogg". When empty, the encoder is used to guess. # @param e Endoding format. For shoutcast, should be mp3 or AAC(+). # @param s The source to output @@ -24,7 +24,7 @@ def output.shoutcast( ~host="localhost",~port=8000, ~user="source",~password="hackme", ~genre="Misc",~url="http://savonet.sf.net/", - ~name="OCaml Radio!",~public=true, ~format="", + ~name="Liquidsoap Radio!",~public=true, ~format="", ~dumpfile="", ~icy_metadata="guess", ~on_connect={()}, ~on_disconnect={()}, ~aim="",~icq="",~irc="",~icy_reset=true, diff --git a/python_apps/pypo/liquidsoap_scripts/library/tests/BUG403.liq b/python_apps/pypo/liquidsoap_scripts/library/tests/BUG403.liq new file mode 100644 index 000000000..c86eaac08 --- /dev/null +++ b/python_apps/pypo/liquidsoap_scripts/library/tests/BUG403.liq @@ -0,0 +1,21 @@ +# This is the test for bug #403 from our old trac. +# +# Make a switch() declare itself ready and arrange to use it for the next +# frame where it isn't ready anymore. +# +# Two switches A and B +# A is only ready for a short period of time due to its predicates. +# B reselects at the end of a frame just before A becomes unavailable +# as a result, B has selected = A, attempts to stream it +# but A finds itself not ready anymore. +# In other words, B committed but A did not. + +r = ref false +pred = { v=!r ; r:=false ; v } +add_timeout(2.,{ r := true ; (-1.) }) + +mixer = fallback(id="mixer", track_sensitive=false, + [at(pred, sine(duration=3.)), blank()]) + +output.dummy(mixer) +add_timeout(3.,{ print("TEST PASSED") ; shutdown() ; (-1.) }) diff --git a/python_apps/pypo/liquidsoap_scripts/library/tests/LS268.liq b/python_apps/pypo/liquidsoap_scripts/library/tests/LS268.liq new file mode 100644 index 000000000..195a1d9a2 --- /dev/null +++ b/python_apps/pypo/liquidsoap_scripts/library/tests/LS268.liq @@ -0,0 +1,10 @@ +# In LS-268 we realized that an incorrect assumption had +# been made in code from LS-394, resulting in a crash in +# case of source re-awakening. + +p = input.http("http://localhost:8000/nonexistent") +o = output.dummy(fallible=true,p) + +add_timeout(2.,{ source.shutdown(o) ; (-1.) }) +add_timeout(3.,{ output.dummy(fallible=true,p) ; (-1.) }) +add_timeout(4.,{ shutdown() ; (-1.) }) diff --git a/python_apps/pypo/liquidsoap_scripts/library/tests/LS354-1.liq b/python_apps/pypo/liquidsoap_scripts/library/tests/LS354-1.liq new file mode 100755 index 000000000..2e30612a4 --- /dev/null +++ b/python_apps/pypo/liquidsoap_scripts/library/tests/LS354-1.liq @@ -0,0 +1,11 @@ +s = on_track( + fun(_)-> begin print("TEST PASSED") shutdown() end, + blank(duration=1.)) + +r = ref false +d = source.dynamic({ if !r then [s] else [] end }) + +output.dummy(mksafe(d)) + +add_timeout(2.,{r:=true;(-1.)}) +add_timeout(4.,{print("TEST FAILED");shutdown();(-1.)}) diff --git a/python_apps/pypo/liquidsoap_scripts/library/tests/LS354-2.liq b/python_apps/pypo/liquidsoap_scripts/library/tests/LS354-2.liq new file mode 100755 index 000000000..ff9ca3817 --- /dev/null +++ b/python_apps/pypo/liquidsoap_scripts/library/tests/LS354-2.liq @@ -0,0 +1,15 @@ +s1 = fail() +s2 = on_track( + fun(_)-> begin print("TEST PASSED") shutdown() end, + blank(duration=1.)) + +r = ref 0 +d = source.dynamic({ if !r==1 then [s1] + elsif !r==2 then [s2] + else [] end }) + +output.dummy(mksafe(d)) + +add_timeout(2.,{r:=1;(-1.)}) +add_timeout(3.,{r:=2;(-1.)}) +add_timeout(5.,{print("TEST FAILED");shutdown();(-1.)}) diff --git a/python_apps/pypo/liquidsoap_scripts/library/tests/LS460.liq b/python_apps/pypo/liquidsoap_scripts/library/tests/LS460.liq new file mode 100644 index 000000000..5c7e530f8 --- /dev/null +++ b/python_apps/pypo/liquidsoap_scripts/library/tests/LS460.liq @@ -0,0 +1,17 @@ +# Scenario: +# Let foo start using q, then stop it, skip in q. +# When foo restarts it doesn't know that q isn't ready anymore, +# which can lead to a crash. + +q = once(sine(duration=10.)) +output.dummy(id="bar",mksafe(q)) +output.dummy(id="foo",fallback([amplify(1.,q),blank(duration=1.)])) + +def at(t,s) + add_timeout(t,{ignore(server.execute(s));(-1.)}) +end + +at(3.,"foo.stop") +at(4.,"bar.skip") +at(5.,"foo.start") +add_timeout(6.,{print("TEST PASSED");shutdown();(-1.)}) diff --git a/python_apps/pypo/liquidsoap_scripts/library/tests/LS503.liq b/python_apps/pypo/liquidsoap_scripts/library/tests/LS503.liq new file mode 100644 index 000000000..49dadf1a4 --- /dev/null +++ b/python_apps/pypo/liquidsoap_scripts/library/tests/LS503.liq @@ -0,0 +1,13 @@ +# In LS-503 we realized that a source may throw an +# exception during output_get_ready call in the initial +# main phase. This code reproduces the issue by throwing +# an exception in output.icecast. + +# Reopen stderr to /dev/null to +# disable printing expected exception +reopen.stderr("/dev/null") + +p = input.http("http://localhost:8000/nonexistent") +o = output.icecast(%wav,fallible=true,host="nonexistent", + mount="test",p) + diff --git a/python_apps/pypo/liquidsoap_scripts/library/tests/Makefile b/python_apps/pypo/liquidsoap_scripts/library/tests/Makefile new file mode 100644 index 000000000..e9cda96a8 --- /dev/null +++ b/python_apps/pypo/liquidsoap_scripts/library/tests/Makefile @@ -0,0 +1,16 @@ + +DISTFILES = Makefile $(wildcard *.liq) $(wildcard *.pl) + +top_srcdir = ../.. +include $(top_srcdir)/Makefile.rules + +test: + @for i in $(wildcard *.liq) ; do \ + echo -n "$$i... " ; $(top_srcdir)/src/liquidsoap -q - < ./$$i | head -n 1 ; \ + done + @echo -n "type_errors.pl... " ; \ + if (./type_errors.pl > /dev/null 2> /dev/null) ; then \ + echo "TEST PASSED (check manually the prettiness of messages)" ; \ + else \ + echo "TEST FAILED" ; \ + fi diff --git a/python_apps/pypo/liquidsoap_scripts/library/tests/eval.liq b/python_apps/pypo/liquidsoap_scripts/library/tests/eval.liq new file mode 100644 index 000000000..6a930daff --- /dev/null +++ b/python_apps/pypo/liquidsoap_scripts/library/tests/eval.liq @@ -0,0 +1,43 @@ +count = ref 1 +fail = ref false + +def echo(s) + # system("echo "^quote(s)) + if s != string_of(!count) then + fail := true + end + count := !count + 1 + () +end + +def test(lbl,f) + if f() then echo(lbl) else 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) +echo(string_of(f(14))) + +if !fail then print("TEST FAILED") else print("TEST PASSED") end diff --git a/python_apps/pypo/liquidsoap_scripts/library/tests/type_errors.pl b/python_apps/pypo/liquidsoap_scripts/library/tests/type_errors.pl new file mode 100755 index 000000000..f832eaed7 --- /dev/null +++ b/python_apps/pypo/liquidsoap_scripts/library/tests/type_errors.pl @@ -0,0 +1,82 @@ +#!/usr/bin/perl -w + +use strict ; + +my $liquidsoap = "../../src/liquidsoap"; +die unless -f $liquidsoap ; + +$liquidsoap = "$liquidsoap -c"; + +sub section { + print "\n*** $_[0] ***\n\n" ; +} + +sub incorrect { + my $expr = pop ; + print "Incorrect expression $expr...\n" ; + system "$liquidsoap '$expr'" ; + die unless (($?>>8)==1) ; + print "\n" ; +} + +sub correct { + my $expr = pop ; + print "Correct expression $expr...\n" ; + system "$liquidsoap -i '$expr'" ; + die unless (($?>>8)==0) ; + print "\n"; +} + +section("BASIC"); +incorrect('[1]==["1"]'); +incorrect('1==["1"]'); +incorrect('1==(1,"1")'); +# In some of those examples the type error could be reported for a +# sub-expression since we have location information. +# With the concise error, it's still pretty good currently. +incorrect('(1,1)==(1,"1")'); +incorrect('(1,1)==("1",1)'); +incorrect('1==request.create("")'); +incorrect('fun(x)->x(snd(x))'); + +section("SUBTYPING"); +incorrect('(1:unit)'); +correct('((blank():source(1,1,1)):source(*,*,*))'); +incorrect('((blank():source(*,*,*)):source(1,1,1))'); +# Next one requires the inference of a subtype (fixed vs. variable arity) +correct('audio_to_stereo(add([]))'); + +section("CONSTRAINTS"); +incorrect('"bl"+"a"'); +incorrect('(fun(a,b)->a+b)==(fun(a,b)->a+b)'); +incorrect('fun(x)->x(x)'); # TODO is it an accident that we get same varname +incorrect('def f(x) y=snd(x) y(x) end'); + +section("LET GENERALIZATION"); +correct('def f(x) = y=x ; y end f(3)+snd(f((1,2)))'); +incorrect('def f(x) = y=x ; y end f(3)+"3"'); + +section("ARGUMENTS"); +# The errors should be about the type of the param, not of the function. +incorrect('1+"1"'); +# Also, a special simple error is expected for obvious labelling mistakes. +incorrect('fallback(transitions=[],xxxxxxxxxxx=[])'); +incorrect('fallback(transitions=[],transitions=[])'); + +section("FUNCTIONS"); +incorrect('fallback(transitions=[fun(~l)->1])'); +incorrect('fallback(transitions=[fun(~l=1)->1])'); +incorrect('fallback(transitions=[fun(x,y=blank())->y])'); +incorrect('fallback(transitions=[fun(x,y)->0])'); +correct('f=fallback(transitions=[fun(x,y,a=2)->x])'); +incorrect('fallback(transitions=[fun(x,y)->y+1])'); +correct('x=fun(f)->f(3) y=x(fun(f,u="1")->u)'); + +section("CONTENT KIND"); +incorrect('output.file(%vorbis(stereo),"foo",mean(blank()))'); +incorrect('output.file(%vorbis(stereo),"foo",video.add_image(blank()))'); +incorrect('def f(x) = output.file(%vorbis(stereo),"",x) output.file(%vorbis(mono),"",x) end'); +incorrect('add([output.file(%vorbis(stereo),"",blank()),output.file(%vorbis(mono),"",blank())])'); +incorrect('add([mean(blank()),audio_to_stereo(add([]))])'); + +print "Everything's good!\n" ; diff --git a/python_apps/pypo/liquidsoap_scripts/library/tests/typing.liq b/python_apps/pypo/liquidsoap_scripts/library/tests/typing.liq new file mode 100644 index 000000000..8162ebaed --- /dev/null +++ b/python_apps/pypo/liquidsoap_scripts/library/tests/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. + +# 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))}") + +ignore("bla") +ignore((1,3.12)) +ignore(1 + 1) +ignore(1. + 2.14) + +# string is not a Num +# echo("bl"+"a") + +ignore(1 <= 2) +ignore((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 + +print("TEST PASSED") diff --git a/python_apps/pypo/liquidsoap_scripts/library/type_printing.liq b/python_apps/pypo/liquidsoap_scripts/library/type_printing.liq new file mode 100644 index 000000000..805734b6d --- /dev/null +++ b/python_apps/pypo/liquidsoap_scripts/library/type_printing.liq @@ -0,0 +1,61 @@ +# run this file through "liquidsoap -c -i" +# to see if pretty printing looks pretty enough +# for more checks, pass --twidth 78 + +x = (blank() : source(2,3,4)) +pair = (x,x) +x = (x,x) +x = (x,x) +x = (x,x) + +def output.shoutcast( + ~id="output.shoutcast",~start=true, + ~restart=false,~restart_delay=3, + ~host="localhost",~port=8000, + ~user="source",~password="hackme", + ~genre="Misc",~url="http://savonet.sf.net/", + ~name="OCaml Radio!",~public=true, ~format="", + ~dumpfile="", ~icy_metadata="guess", + ~on_connect={()}, ~on_disconnect={()}, + ~aim="",~icq="",~irc="",~icy_reset=true, + ~fallible=false,~on_start={()},~on_stop={()}, + e,s) += + 42 +end + +x = (output.shoutcast,pair) + +def output.shoutcast( + ~id="output.shoutcast",~start=true, + ~restart=false,~restart_delay=3, + ~host="localhost",~port=8000, + ~user="source",~password="hackme", + ~genre="Misc",~url="http://savonet.sf.net/", + ~name="OCaml Radio!",~public=true, ~format="", + ~dumpfile="", ~icy_metadata="guess", + ~on_connect={()}, ~on_disconnect={()}, + ~aim="",~icq="",~irc="",~icy_reset=true, + ~fallible=false,~on_start={()},~on_stop={()}, + e,s) += + (pair,42) +end + +def output.shoutcast( + ~id="output.shoutcast",~start=true, + ~restart=false,~restart_delay=3, + ~host="localhost",~port=8000, + ~user="source",~password="hackme", + ~genre="Misc",~url="http://savonet.sf.net/", + ~name="OCaml Radio!",~public=true, ~format="", + ~dumpfile="", ~icy_metadata="guess", + ~on_connect={()}, ~on_disconnect={()}, + ~aim="",~icq="",~irc="",~icy_reset=true, + ~fallible=false,~on_start={()},~on_stop={()}, + e,s) += + (blank() : source(3,0,0)) +end + +x = (output.shoutcast,blank()) diff --git a/python_apps/pypo/liquidsoap_scripts/library/utils.liq b/python_apps/pypo/liquidsoap_scripts/library/utils.liq index fe1963100..a28e9c8d8 100644 --- a/python_apps/pypo/liquidsoap_scripts/library/utils.liq +++ b/python_apps/pypo/liquidsoap_scripts/library/utils.liq @@ -97,41 +97,63 @@ def merge_tracks(s) sequence(merge=true,[s]) end +# Default inputs and outpus +# +# They are called "prefered" but it's not a user preference, +# just a view of what's generally preferable among the available +# modules. +# It is important that input and output preferences are in the +# same order: the chosen I/O should work in the same clock, we don't +# want an ALSA input and OSS output. The only exception is AO: +# it is the default output after dummy, so the input will be a dummy +# when AO is used for output. + output.prefered=output.dummy -%ifdef output.oss - output.prefered=output.oss +%ifdef output.ao + output.prefered=output.ao %endif %ifdef output.alsa output.prefered=output.alsa %endif +%ifdef output.oss + output.prefered=output.oss +%endif +%ifdef output.portaudio + output.prefered = output.portaudio +%endif %ifdef output.pulseaudio output.prefered=output.pulseaudio %endif -%ifdef output.ao - output.prefered=output.ao -%endif -# Output to local audio card using the first available driver in this list: -# ao, pulseaudio, alsa, oss, dummy +# Output to local audio card using the first available driver in +# pulseaudio, portaudio, oss, alsa, ao, dummy. # @category Source / Output -def output.prefered(~id="",s) - output.prefered(id=id,s) +def output.prefered(~id="",~fallible=false, + ~on_start={()},~on_stop={()},~start=true,s) + output.prefered(id=id,fallible=fallible, + start=start,on_start=on_start,on_stop=on_stop, + s) end -in = fun () -> blank() -%ifdef input.oss - in = fun () -> input.oss(id="oss_mic") -%endif +def in(~id="",~start=true,~on_start={()},~on_stop={()},~fallible=false) + blank(id=id) +end %ifdef input.alsa - in = fun () -> input.alsa(id="alsa_mic") + in = input.alsa +%endif +%ifdef input.oss + in = input.oss %endif %ifdef input.portaudio - in = fun () -> input.portaudio(id="pa_mic") + in = input.portaudio %endif -# Create a source from the first available input driver in this list: -# portaudio, alsa, oss, blank. +%ifdef input.pulseaudio + in = input.pulseaudio +%endif +# Create a source from the first available input driver in +# pulseaudio, portaudio, oss, alsa, blank. # @category Source / Input -def in() - in() +def in(~id="",~start=true,~on_start={()},~on_stop={()},~fallible=false) + in(id=id,start=start,on_start=on_start,on_stop=on_stop,fallible=fallible) end # Output a stream using the 'output.prefered' operator. The input source does @@ -362,6 +384,11 @@ def read(~hide=false) s end +file.mime_default = fun (_) -> "" +%ifdef file.mime +file.mime_default = file.mime +%endif + # Generic mime test. First try to use file.mime if it exist. # Otherwise try to get the value using the file binary. # Returns "" (empty string) if no value can be find. @@ -376,15 +403,8 @@ def get_mime(file) = "" end end - def mime_method(file) = - ret = "" - %ifdef file.mime - ret = file.mime(file) - %endif - ret - end # First try mime method - ret = mime_method(file) + ret = file.mime_default(file) if ret != "" then ret else @@ -403,8 +423,8 @@ end # Creates a source that fails to produce anything. # @category Source / Input -def fail() - fallback([]) +def fail(~id="") + fallback(id=id,[]) end # Creates a source that plays only one track of the input source. @@ -677,6 +697,17 @@ def enable_replaygain_metadata( add_metadata_resolver("replay_gain", replaygain_metadata) end +# Assign a new clock to the given source (and to other time-dependent +# sources) and return the source. It is a conveniency wrapper around +# clock.assign_new(), allowing more concise scripts in some cases. +# @category Liquidsoap +# @param ~sync Do not synchronize the clock on regular wallclock time, \ +# but try to run as fast as possible (CPU burning mode). +def clock(~sync=true,~id="",s) + clock.assign_new(sync=sync,id=id,[s]) + s +end + # Create a log of clock times for all the clocks initially present. # The log is in a simple format which you can directly use with gnuplot. # @category Liquidsoap diff --git a/python_apps/pypo/liquidsoap_scripts/liquidsoap.cfg b/python_apps/pypo/liquidsoap_scripts/liquidsoap.cfg index b0b13b0bc..9b85f4098 100644 --- a/python_apps/pypo/liquidsoap_scripts/liquidsoap.cfg +++ b/python_apps/pypo/liquidsoap_scripts/liquidsoap.cfg @@ -14,7 +14,7 @@ output_shoutcast = false # Logging settings # ########################################### log_file = "/var/log/airtime/pypo-liquidsoap/