Liquidsoap

From XPUB & Lens-Based wiki
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
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.3/

Getting started

The documentation for liquidsoap (v. 1.3.3) can be found at:

https://www.liquidsoap.info/doc-1.3.3/

Also, a good place to start is the Quick start

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)

myradio = noise()
output.file(%mp3, "myradio.mp3", myradio)

Adding filters

Use a normalize filter ...

 # Listen to your playlist, but normalize the volume
 liquidsoap 'out(normalize(playlist("playlist_file")))'

or "smart" cross fade

 liquidsoap 'out(smart_crossfade(
                 normalize(playlist("playlist_file"))))'

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)

Cookbook

From: https://www.liquidsoap.info/doc-1.3.3/cookbook.html

The recipes show how to build a source with a particular feature. You can try short snippets by wrapping the code in an out(..) operator and passing it directly to liquidsoap:

 liquidsoap -v 'out(recipe)'

For longer recipes, you might want to create a short script:

#!/usr/bin/liquidsoap -v

set("log.file.path","/tmp/<script>_YOURNAME.log")
set("log.stdout",true)

recipe = single("/my/default.ogg")
out(recipe)

Now a version that sends to an icecast server... (NB: YOU NEED A passwords.liq file with the appropriate passwords...

# %include "passwords.liq"
ICECAST_SERVER_HOST="echo.lurk.org"
ICECAST_SERVER_PORT=9999
ICECAST_SERVER_PASSWORD="hackme"
#!/usr/bin/liquidsoap -v

set("log.file.path","/tmp/<script>_YOURNAME.log")
set("log.stdout",true)
%include "passwords.liq"

recipe = single("/my/default.ogg")
output.icecast(%vorbis,
      host = ICECAST_SERVER_HOST, port = ICECAST_SERVER_PORT,
      password = ICECAST_SERVER_PASSWORD, mount = "prototyping.ogg",
      mksafe(recipe))


See the quickstart guide for more information on how to run Liquidsoap, on what is this out(..) operator, etc.


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).

say_metadata

say_metadata filter

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="")