Liquidsoap: Difference between revisions
(110 intermediate revisions by 2 users not shown) | |||
Line 5: | Line 5: | ||
|Media=Streaming media | |Media=Streaming media | ||
|Format=OGG, MP3 | |Format=OGG, MP3 | ||
|Interface= | |Interface=Specialised programming language | ||
|Wikipedia=http://en.wikipedia.org/wiki/Draft:Liquidsoap | |Wikipedia=http://en.wikipedia.org/wiki/Draft:Liquidsoap | ||
|Thumbnail=File:Liquidsoap-logo.png | |Thumbnail=File:Liquidsoap-logo.png | ||
}} | }} | ||
NB: The version of liquidsoap currently installed via apt is 1.3.3 (while the latest version is 1.4.2). The links on this page are for this older version. | |||
https://www.liquidsoap.info/doc-1.3.7/ | |||
== Getting started == | |||
The documentation for liquidsoap (v. 1.3.3) can be found at: | |||
https://www.liquidsoap.info/doc-1.3.7/ | |||
Also, a good place to start is the [https://www.liquidsoap.info/doc-1.3.7/quick_start.html Quick start] and the [https://www.liquidsoap.info/doc-1.3.7/cookbook.html cookbook]. | |||
== Installation == | |||
apt install liquidsoap | |||
Weirdly there's a bug in debian (x86) that gives a floating point error when you run it, I fixed mine by uninstalling: | |||
sudo apt purge frei0r-plugins | |||
== Disable the log file == | |||
When running a liquidsoap as your normal user, you'll want to disable the log file or you'll probably get a permission error. | |||
<source lang="bash"> | |||
set("log.file",false) | |||
set("log.stdout",true) | |||
</source> | |||
== Hello noise == | |||
=== Output to laptop speakers/headphones === | |||
If you have liquidsoap installed on your laptop, some simple tests to see that it works can be: | |||
liquidsoap -v 'out(noise())' | |||
This should send some noise (warning: might be loud!) to your laptop speaker or headphones. Notice that liquidsoap is in fact a programming language, and a program makes use of functions that you run or call by using parenthesis notation -- just like in javascript, C, or python. | |||
So in fact what is happening is that you first call: | |||
* [https://www.liquidsoap.info/doc-1.3.7/reference.html#noise noise]() -> Which makes an "audio stream" of white noise and then... | |||
* [https://www.liquidsoap.info/doc-1.3.7/reference.html#output.prefered out]( ) -> Finds the sound card on your computer and sends the noise there to be heard. | |||
This is an example of a [[nested function call]], where the result of the noise function gets passed to the input of the out function. | |||
Like with any scripting language, it's often more convenient for writing and debugging your liquidsoap program to save it in its own file. The convention is to use the extension ".liq" for a liquidsoap script. '''NB: When you make a script, you have to add two special lines that tell liquidsoap where to write message (the "log" file).''' Otherwise you get a permissions error when liquidsoap tries to use a location for the log file that probably isn't possible for your user account to write to. | |||
<source lang="bash"> | |||
set("log.file",false) | |||
set("log.stdout",true) | |||
myradio = noise() | |||
out(myradio) | |||
</source> | |||
You would then run the script (in this case saved with the name ''myscript.liq''): | |||
liquidsoap -v myscript.liq | |||
=== Output to an icecast server === | |||
Output to an icecast server uses the [https://www.liquidsoap.info/doc-1.3.7/reference.html#output.icecast output.icecast] function. Notice also the use of %vorbis. This is a format and describes in this case the %vorbis compression format used in an OGG file. You could also use %mp3. | |||
<source lang="bash"> | |||
set("log.file",false) | |||
set("log.stdout",true) | |||
myradio = noise() | |||
%include "passwords.liq" | |||
output.icecast(%vorbis, | |||
host = ICECAST_SERVER_HOST, port = ICECAST_SERVER_PORT, | |||
password = ICECAST_SERVER_PASSWORD, mount = "myradio.ogg", | |||
mksafe(myradio)) | |||
</source> | |||
NB: You need to make a passwords.liq file a la: | |||
<source> | |||
ICECAST_SERVER_HOST="icecast.myserver.org" | |||
ICECAST_SERVER_PORT=8888 | |||
ICECAST_SERVER_PASSWORD="HACKME" | |||
</source> | |||
It's a really ''good practice'' to make a separate file for your passwords and make sure these aren't ever published (via a web or git for instance). You may even want/need to put the passwords file in a different directory to keep it private, in which case you could use something like: | |||
%include "/srv/radio/passwords.liq" | |||
=== Output to a file === | |||
<source lang="bash"> | |||
set("log.file",false) | |||
set("log.stdout",true) | |||
src = audio_to_stereo(once(single("si17.mono.mp3"))) | |||
src = nrj(normalize(src)) | |||
#out(src) | |||
output.file(%mp3, "si17.nrj.mp3", src, fallible=true) | |||
</source> | |||
Also, you can add to the end of a file by using append=true... | |||
<source lang="bash"> | |||
set("log.file",false) | |||
set("log.stdout",true) | |||
myradio = sine(440.0,amplitude=0.02,duration=1.0) | |||
output.file(append=true, %mp3, "myradio.mp3", myradio) | |||
</source> | |||
== Editing == | |||
=== Setting in / out points === | |||
Setting in and out points on a track can be done in two steps: | |||
# Inserting [https://www.liquidsoap.info/doc-1.3.7/metadata.html metadata] | |||
# Filtering a stream with the a filter that then actually does the work, based on the values in the metadata. | |||
The filters and related metadata are: | |||
* [https://www.liquidsoap.info/doc-1.3.7/reference.html#cue_cut cue_cut] uses ''liq_cue_in'' and ''liq_cue_out'' | |||
* [https://www.liquidsoap.info/doc-1.3.7/reference.html#crossfade crossfade] and [https://www.liquidsoap.info/doc-1.3.7/reference.html#smart_crossfade smart_crossfade] uses: ''liq_fade_in'' and ''liq_fade_out'' | |||
Metadata can be inserted using | |||
* Adding the metadata tags to your audio files with a tool like [[vorbiscomment]] | |||
* annotate: protocol. | |||
* [https://www.liquidsoap.info/doc-1.3.7/reference.html#map_metadata map_metadata] | |||
* playlist prefix | |||
See this helpful [http://mcfiredrill.github.io/blog/liquidsoap-cue-and-cross-fade-metadata blog post] | |||
So to take the first five seconds of a stream, add: | |||
<source lang="bash"> | |||
def add_cue_out (m) = | |||
[("liq_cue_in","0"),("liq_cue_out","5")] | |||
end | |||
radio = map_metadata(add_cue_out, radio) | |||
radio = cue_cut(radio) | |||
</source> | |||
=== skim.liq === | |||
Play the first 5 seconds of each track of a playlist, to stream, and eventually to file. | |||
<source lang="bash"> | |||
set("log.file",false) | |||
set("log.stdout",true) | |||
%include "/srv/radio/passwords.liq" | |||
radio = playlist.once("skip.m3u") | |||
radio = audio_to_stereo(radio) | |||
# use replay gain (or not) | |||
enable_replaygain_metadata() | |||
radio = amplify(1.,override="replay_gain",radio) | |||
def add_cue_out (m) = | |||
[("liq_cue_in","0"),("liq_cue_out","5")] | |||
end | |||
radio = map_metadata(add_cue_out, radio) | |||
radio = cue_cut(radio) | |||
# enable this for file output | |||
# output.file(%vorbis, "archive.ogg", radio, append=true, fallible=true) | |||
output.icecast(%vorbis, | |||
host = ICECAST_SERVER_HOST, port = ICECAST_SERVER_PORT, | |||
password = ICECAST_SERVER_PASSWORD, mount = "radioimplicancies_live.ogg", | |||
mksafe(radio)) | |||
</source> | |||
== Adding filters == | |||
Use a [https://www.liquidsoap.info/doc-1.3.7/reference.html#normalize normalize] filter ... | |||
# Listen to your playlist, but normalize the volume | |||
liquidsoap 'out(normalize(playlist("playlist_file")))' | |||
or [https://www.liquidsoap.info/doc-1.3.7/smartcrossfade.html "smart" cross fade] | |||
liquidsoap 'out(smart_crossfade( | |||
normalize(playlist("playlist_file"))))' | |||
== Using replay gain == | |||
Two steps: | |||
# Add the metadata with mp3gain / vorbisgain | |||
# Enable it in your liq | |||
Once on the commandline: | |||
vorbisgain *.ogg | |||
Check your files metadata with: | |||
vorbiscomment -l my.ogg | |||
then in your liq | |||
enable_replaygain_metadata () | |||
s = amplify(1.,override="replay_gain",s) | |||
See [https://www.liquidsoap.info/doc-1.3.7/replay_gain.html replay_gain] | |||
== Make a liq script executable == | |||
If you want to make your script fully executable, you can add a "shebang" as the first line of the file, as in: | |||
<source lang="bash"> | |||
#!/usr/bin/liquidsoap -v | |||
</source> | |||
And then | |||
chmod +x myscript.liq | |||
And finally run it either by putting the script in your path, or directly with: | |||
./myscript.liq | |||
== Use a log file == | |||
When you finally run your radio script for a longer time, you may well want to have liquidsoap write messages to a log file so that you can eventually debug things later. You can also then turn off stdout (though when using liquidsoap with [[tmux]] for instance, it might still be useful to keep this on. | |||
<source lang="bash"> | |||
set("log.file.path","myradio.log") | |||
set("log.stdout",false) | |||
</source> | |||
== Live interruption with fallback == | |||
A ''very useful'' model is to make a stream that plays from will take its content from another stream, and [https://www.liquidsoap.info/doc-1.3.7/reference.html#fallback fallback] to a "safety" program when that stream isn't available. This allows for a program to be remotely stopped and (re)started, without breaking the continuit of the main stream. A good example is given in the [https://www.liquidsoap.info/doc-1.3.7/complete_case.html "complete case"] of a program that uses the fallback function to allow a live program to cut-in and interrupt an otherwise automated program. | |||
<source lang="bash"> | |||
#!/usr/bin/liquidsoap -v | |||
set("log.file.path","/tmp/<script>.log") | |||
set("log.stdout",true) | |||
%include "passwords.liq" | |||
# Add the ability to relay live shows | |||
liveorstatic = | |||
fallback(track_sensitive=false, | |||
[input.http("http://echo.lurk.org:999/prototyping_live.ogg"), | |||
single("brownnoise.mp3")]) | |||
# out(liveorstatic) | |||
output.icecast(%vorbis, | |||
host = ICECAST_SERVER_HOST, port = ICECAST_SERVER_PORT, | |||
password = ICECAST_SERVER_PASSWORD, mount = "prototyping.ogg", | |||
mksafe(liveorstatic)) | |||
</source> | |||
== Sound processing == | == Sound processing == | ||
Line 29: | Line 285: | ||
mode (of type string, which defaults to "randomize"): Play the files in the playlist either in the order (“normal” mode), or shuffle the playlist each time it is loaded, and play it in this order for a whole round (“randomize” mode), or pick a random file in the playlist each time (“random” mode). | mode (of type string, which defaults to "randomize"): Play the files in the playlist either in the order (“normal” mode), or shuffle the playlist each time it is loaded, and play it in this order for a whole round (“randomize” mode), or pick a random file in the playlist each time (“random” mode). | ||
</blockquote> | </blockquote> | ||
[https://www.liquidsoap.info/doc-1.3.7/reference.html#playlist.once playlist.once] | |||
To render a playlist as a file: | |||
<source lang="bash"> | |||
set("log.file",false) | |||
set("log.stdout",true) | |||
myradio = audio_to_stereo(playlist.once("counting.m3u")) | |||
out(myradio) | |||
output.file(%mp3, "counting.mp3", myradio, fallible=true) | |||
</source> | |||
== say_metadata == | == say_metadata == | ||
[https://www.liquidsoap.info/doc-1.4.2/reference.html#say_metadata say_metadata] filter | |||
== on_metadata == | |||
<source lang="bash"> | |||
scripts="/home/mmurtaugh/public_html/player/" | |||
def on_meta (meta) | |||
# call recordmeta.py with json_of(meta) | |||
data = json_of(compact=true,meta) | |||
system (scripts^"recordmeta.py "^quote(data)) | |||
# PRINT (debugging) | |||
print("---STARTMETA---") | |||
list.iter(fun (i) -> print("meta:"^fst(i)^": "^snd(i)), meta) | |||
# data = string.concat(separator="\n",list.map(fun (i) -> fst(i)^":"^snd(i), meta)) | |||
print ("---ENDMETA---") | |||
end | |||
radio = on_metadata(on_meta, radio) | |||
</source> | |||
recordmeta.py | |||
<source lang="python"> | |||
#!/usr/bin/env python3 | |||
import sys, argparse | |||
import requests, json | |||
# todo: add timestamp | |||
ap = argparse.ArgumentParser("") | |||
ap.add_argument("data") | |||
ap.add_argument("--post") | |||
ap.add_argument("--logfile") | |||
args = ap.parse_args() | |||
= | if args.logfile: | ||
with open (args.logfile, "a") as f: | |||
print (args.data, file=f) | |||
if args.post: | |||
data = json.loads(args.data) | |||
r = requests.post(args.post, json=args.data) | |||
resp = r.content.decode("utf-8").strip() | |||
print ("POST response ({}): {}".format(r.status_code, resp)) | |||
</source> | |||
== say_metadata == | |||
== examples == | == examples == | ||
Line 50: | Line 357: | ||
<source lang="bash"> | <source lang="bash"> | ||
liquidsoap 'out(playlist("playlist.pls"))' | liquidsoap 'out(playlist("playlist.pls"))' | ||
liquidsoap 'output.prefered(mksafe(playlist("playlist.pls")))' | |||
liquidsoap 'output.file(%mp3,"output.mp3",mksafe(playlist("playlist.pls")))' | |||
</source> | </source> | ||
Line 56: | Line 365: | ||
<source lang="text"> | <source lang="text"> | ||
brown.ogg | brown.ogg | ||
</ | </source> | ||
Observations: | |||
* WAV files don't seem to work, but ogg + mp3 seem OK! (or was this the stereo problem) | |||
* explicit output modules (like output.file) seem to require the use of mksafe (out doesn't need this). | |||
Doesn't work with mono source material -- default seems to be stereo... | |||
[decoder:3] Unable to decode "./speech0001.mp3" as {audio=2;video=0;midi=0}! | |||
2020/05/11 17:42:04 [decoder.mad:3] File "./speech0003.mp3" has an incompatible number of channels. | |||
WAV file "speech0001.wav" has content type {audio=1;video=0;midi=0} but {audio=2;video=0;midi=0} was expected. | |||
How would you output a mono audio stream? | |||
== Presentation == | |||
[https://fosdem.org/2020/schedule/event/om_liquidsoap/ Recently presented at Fosdem2020] also on [https://vimeo.com/388951779 vimeo] | |||
== Documentation conventions == | |||
The docs have cryptic syntax, for instance the say_metadata filter is described with the signature: | |||
(source(?A), ?pattern : string) -> source(?A) | |||
Which means you can call this function like: | |||
say_metadata(mysource) | |||
or | |||
say_metadata(mysource, pattern="") | |||
== Notes == | |||
<blockquote>A stream is built with Liquidsoap by using or creating sources. A source is an annotated audio stream. </blockquote> | |||
<img src="https://www.liquidsoap.info/doc-1.3.7/images/stream.png" /> | |||
[https://www.liquidsoap.info/doc-1.3.7/quick_start.html#that-source-is-fallible Fallibility] | |||
<blockquote>Finally, if you do not care about failures, you can pass the parameter fallible=true to most outputs. In that case, the output will accept a fallible source, and stop whenever the source fails, to restart when it is ready to emit a stream again. This is usually done if you are not emitting a radio-like stream, but for example capturing or relaying another stream, or encoding files.</blockquote> | |||
== Random stuff == | |||
https://www.liquidsoap.info/doc-1.3.7/images/liqgraph.png | |||
* [https://www.liquidsoap.info/doc-1.3.7/reference.html#playlist playlist] | |||
* [https://www.liquidsoap.info/doc-1.3.7/reference.html#playlist.once playlist.once] | |||
* [https://www.liquidsoap.info/doc-1.3.7/reference.html#sequence sequence] | |||
* [https://www.liquidsoap.info/doc-1.3.7/reference.html#rotate rotate] | |||
some audio filters... | |||
* [https://www.liquidsoap.info/doc-1.3.7/reference.html#echo echo] | |||
* [https://www.liquidsoap.info/doc-1.3.7/reference.html#flanger flanger] | |||
* [https://www.liquidsoap.info/doc-1.3.7/reference.html#add add] | |||
Liquidsoap's so-called [https://www.liquidsoap.info/doc-1.3.7/complete_case.html complete case] combines many interesting functionalities of this domain-specific programming language: | |||
The use of [https://www.liquidsoap.info/doc-1.3.7/reference.html#switch switch] and [https://www.liquidsoap.info/doc-1.3.7/language.html#time-intervals time intervals] | |||
The use of [https://www.liquidsoap.info/doc-1.3.7/reference.html#fallback fallback] and a [https://www.liquidsoap.info/doc-1.3.7/reference.html#request.queue request.queue] (and the [https://www.liquidsoap.info/doc-1.3.7/server.html telnet server]) | |||
The use of [https://www.liquidsoap.info/doc-1.3.7/reference.html#random random] and [https://www.liquidsoap.info/doc-1.3.7/reference.html#add add] to combine multiple playlists with weights. | |||
And, finally the feature we've been making use of all trimester, another fallback to allow a second icecast stream (using [https://www.liquidsoap.info/doc-1.3.7/reference.html#input.http input.http]) to interupt a "safety" file (in our case brownnoise). | |||
=Related pages= | |||
* [[Icecast]] | |||
* [[:Category:Implicancies]] | |||
[[Category:Implicancies]] |
Latest revision as of 12:18, 26 March 2022
Website | http://liquidsoap.info/ |
---|---|
License | GPL |
OS | GNU/Linux, OS X, Windows |
Media | Streaming media |
Format | OGG, MP3 |
Interface | Specialised programming language |
Wikipedia | http://en.wikipedia.org/wiki/Draft:Liquidsoap |
Thumbnail |
NB: The version of liquidsoap currently installed via apt is 1.3.3 (while the latest version is 1.4.2). The links on this page are for this older version.
https://www.liquidsoap.info/doc-1.3.7/
Getting started
The documentation for liquidsoap (v. 1.3.3) can be found at:
https://www.liquidsoap.info/doc-1.3.7/
Also, a good place to start is the Quick start and the cookbook.
Installation
apt install liquidsoap
Weirdly there's a bug in debian (x86) that gives a floating point error when you run it, I fixed mine by uninstalling:
sudo apt purge frei0r-plugins
Disable the log file
When running a liquidsoap as your normal user, you'll want to disable the log file or you'll probably get a permission error.
set("log.file",false)
set("log.stdout",true)
Hello noise
Output to laptop speakers/headphones
If you have liquidsoap installed on your laptop, some simple tests to see that it works can be:
liquidsoap -v 'out(noise())'
This should send some noise (warning: might be loud!) to your laptop speaker or headphones. Notice that liquidsoap is in fact a programming language, and a program makes use of functions that you run or call by using parenthesis notation -- just like in javascript, C, or python. So in fact what is happening is that you first call:
- noise() -> Which makes an "audio stream" of white noise and then...
- out( ) -> Finds the sound card on your computer and sends the noise there to be heard.
This is an example of a nested function call, where the result of the noise function gets passed to the input of the out function.
Like with any scripting language, it's often more convenient for writing and debugging your liquidsoap program to save it in its own file. The convention is to use the extension ".liq" for a liquidsoap script. NB: When you make a script, you have to add two special lines that tell liquidsoap where to write message (the "log" file). Otherwise you get a permissions error when liquidsoap tries to use a location for the log file that probably isn't possible for your user account to write to.
set("log.file",false)
set("log.stdout",true)
myradio = noise()
out(myradio)
You would then run the script (in this case saved with the name myscript.liq):
liquidsoap -v myscript.liq
Output to an icecast server
Output to an icecast server uses the output.icecast function. Notice also the use of %vorbis. This is a format and describes in this case the %vorbis compression format used in an OGG file. You could also use %mp3.
set("log.file",false)
set("log.stdout",true)
myradio = noise()
%include "passwords.liq"
output.icecast(%vorbis,
host = ICECAST_SERVER_HOST, port = ICECAST_SERVER_PORT,
password = ICECAST_SERVER_PASSWORD, mount = "myradio.ogg",
mksafe(myradio))
NB: You need to make a passwords.liq file a la:
ICECAST_SERVER_HOST="icecast.myserver.org"
ICECAST_SERVER_PORT=8888
ICECAST_SERVER_PASSWORD="HACKME"
It's a really good practice to make a separate file for your passwords and make sure these aren't ever published (via a web or git for instance). You may even want/need to put the passwords file in a different directory to keep it private, in which case you could use something like:
%include "/srv/radio/passwords.liq"
Output to a file
set("log.file",false)
set("log.stdout",true)
src = audio_to_stereo(once(single("si17.mono.mp3")))
src = nrj(normalize(src))
#out(src)
output.file(%mp3, "si17.nrj.mp3", src, fallible=true)
Also, you can add to the end of a file by using append=true...
set("log.file",false)
set("log.stdout",true)
myradio = sine(440.0,amplitude=0.02,duration=1.0)
output.file(append=true, %mp3, "myradio.mp3", myradio)
Editing
Setting in / out points
Setting in and out points on a track can be done in two steps:
- Inserting metadata
- Filtering a stream with the a filter that then actually does the work, based on the values in the metadata.
The filters and related metadata are:
- cue_cut uses liq_cue_in and liq_cue_out
- crossfade and smart_crossfade uses: liq_fade_in and liq_fade_out
Metadata can be inserted using
- Adding the metadata tags to your audio files with a tool like vorbiscomment
- annotate: protocol.
- map_metadata
- playlist prefix
See this helpful blog post
So to take the first five seconds of a stream, add:
def add_cue_out (m) =
[("liq_cue_in","0"),("liq_cue_out","5")]
end
radio = map_metadata(add_cue_out, radio)
radio = cue_cut(radio)
skim.liq
Play the first 5 seconds of each track of a playlist, to stream, and eventually to file.
set("log.file",false)
set("log.stdout",true)
%include "/srv/radio/passwords.liq"
radio = playlist.once("skip.m3u")
radio = audio_to_stereo(radio)
# use replay gain (or not)
enable_replaygain_metadata()
radio = amplify(1.,override="replay_gain",radio)
def add_cue_out (m) =
[("liq_cue_in","0"),("liq_cue_out","5")]
end
radio = map_metadata(add_cue_out, radio)
radio = cue_cut(radio)
# enable this for file output
# output.file(%vorbis, "archive.ogg", radio, append=true, fallible=true)
output.icecast(%vorbis,
host = ICECAST_SERVER_HOST, port = ICECAST_SERVER_PORT,
password = ICECAST_SERVER_PASSWORD, mount = "radioimplicancies_live.ogg",
mksafe(radio))
Adding filters
Use a normalize filter ...
# Listen to your playlist, but normalize the volume liquidsoap 'out(normalize(playlist("playlist_file")))'
liquidsoap 'out(smart_crossfade( normalize(playlist("playlist_file"))))'
Using replay gain
Two steps:
- Add the metadata with mp3gain / vorbisgain
- Enable it in your liq
Once on the commandline:
vorbisgain *.ogg
Check your files metadata with:
vorbiscomment -l my.ogg
then in your liq
enable_replaygain_metadata () s = amplify(1.,override="replay_gain",s)
See replay_gain
Make a liq script executable
If you want to make your script fully executable, you can add a "shebang" as the first line of the file, as in:
#!/usr/bin/liquidsoap -v
And then
chmod +x myscript.liq
And finally run it either by putting the script in your path, or directly with:
./myscript.liq
Use a log file
When you finally run your radio script for a longer time, you may well want to have liquidsoap write messages to a log file so that you can eventually debug things later. You can also then turn off stdout (though when using liquidsoap with tmux for instance, it might still be useful to keep this on.
set("log.file.path","myradio.log")
set("log.stdout",false)
Live interruption with fallback
A very useful model is to make a stream that plays from will take its content from another stream, and fallback to a "safety" program when that stream isn't available. This allows for a program to be remotely stopped and (re)started, without breaking the continuit of the main stream. A good example is given in the "complete case" of a program that uses the fallback function to allow a live program to cut-in and interrupt an otherwise automated program.
#!/usr/bin/liquidsoap -v
set("log.file.path","/tmp/<script>.log")
set("log.stdout",true)
%include "passwords.liq"
# Add the ability to relay live shows
liveorstatic =
fallback(track_sensitive=false,
[input.http("http://echo.lurk.org:999/prototyping_live.ogg"),
single("brownnoise.mp3")])
# out(liveorstatic)
output.icecast(%vorbis,
host = ICECAST_SERVER_HOST, port = ICECAST_SERVER_PORT,
password = ICECAST_SERVER_PASSWORD, mount = "prototyping.ogg",
mksafe(liveorstatic))
Sound processing
liquidsoap can be used to process / filter sound either in a streaming pipeline, or used "offline" to directly produce an audio output (file).
See https://www.liquidsoap.info/doc-1.4.2/reference.html#source-sound-processing
Links / Projects / Radio streams that use liquidsoap
- Data Radio. Here a script created a day and night playlist, with a "fallback" to allow a live stream to interrupt.
playlist
playlist source
mode (of type string, which defaults to "randomize"): Play the files in the playlist either in the order (“normal” mode), or shuffle the playlist each time it is loaded, and play it in this order for a whole round (“randomize” mode), or pick a random file in the playlist each time (“random” mode).
To render a playlist as a file:
set("log.file",false)
set("log.stdout",true)
myradio = audio_to_stereo(playlist.once("counting.m3u"))
out(myradio)
output.file(%mp3, "counting.mp3", myradio, fallible=true)
say_metadata
say_metadata filter
on_metadata
scripts="/home/mmurtaugh/public_html/player/"
def on_meta (meta)
# call recordmeta.py with json_of(meta)
data = json_of(compact=true,meta)
system (scripts^"recordmeta.py "^quote(data))
# PRINT (debugging)
print("---STARTMETA---")
list.iter(fun (i) -> print("meta:"^fst(i)^": "^snd(i)), meta)
# data = string.concat(separator="\n",list.map(fun (i) -> fst(i)^":"^snd(i), meta))
print ("---ENDMETA---")
end
radio = on_metadata(on_meta, radio)
recordmeta.py
#!/usr/bin/env python3
import sys, argparse
import requests, json
# todo: add timestamp
ap = argparse.ArgumentParser("")
ap.add_argument("data")
ap.add_argument("--post")
ap.add_argument("--logfile")
args = ap.parse_args()
if args.logfile:
with open (args.logfile, "a") as f:
print (args.data, file=f)
if args.post:
data = json.loads(args.data)
r = requests.post(args.post, json=args.data)
resp = r.content.decode("utf-8").strip()
print ("POST response ({}): {}".format(r.status_code, resp))
say_metadata
examples
liquidsoap 'out(noise())'
liquidsoap 'out(playlist("playlist.pls"))'
liquidsoap 'output.prefered(mksafe(playlist("playlist.pls")))'
liquidsoap 'output.file(%mp3,"output.mp3",mksafe(playlist("playlist.pls")))'
with playlist.pls:
brown.ogg
Observations:
- WAV files don't seem to work, but ogg + mp3 seem OK! (or was this the stereo problem)
- explicit output modules (like output.file) seem to require the use of mksafe (out doesn't need this).
Doesn't work with mono source material -- default seems to be stereo...
[decoder:3] Unable to decode "./speech0001.mp3" as {audio=2;video=0;midi=0}! 2020/05/11 17:42:04 [decoder.mad:3] File "./speech0003.mp3" has an incompatible number of channels.
WAV file "speech0001.wav" has content type {audio=1;video=0;midi=0} but {audio=2;video=0;midi=0} was expected.
How would you output a mono audio stream?
Presentation
Recently presented at Fosdem2020 also on vimeo
Documentation conventions
The docs have cryptic syntax, for instance the say_metadata filter is described with the signature:
(source(?A), ?pattern : string) -> source(?A)
Which means you can call this function like:
say_metadata(mysource)
or
say_metadata(mysource, pattern="")
Notes
A stream is built with Liquidsoap by using or creating sources. A source is an annotated audio stream.
Finally, if you do not care about failures, you can pass the parameter fallible=true to most outputs. In that case, the output will accept a fallible source, and stop whenever the source fails, to restart when it is ready to emit a stream again. This is usually done if you are not emitting a radio-like stream, but for example capturing or relaying another stream, or encoding files.
Random stuff
some audio filters...
Liquidsoap's so-called complete case combines many interesting functionalities of this domain-specific programming language:
The use of switch and time intervals
The use of fallback and a request.queue (and the telnet server)
The use of random and add to combine multiple playlists with weights.
And, finally the feature we've been making use of all trimester, another fallback to allow a second icecast stream (using input.http) to interupt a "safety" file (in our case brownnoise).