Skip to content

Commit

Permalink
Add doc 2.2.3
Browse files Browse the repository at this point in the history
  • Loading branch information
toots committed Dec 15, 2023
1 parent c6c618f commit 5d9ba82
Show file tree
Hide file tree
Showing 89 changed files with 44,919 additions and 2 deletions.
4 changes: 2 additions & 2 deletions website/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ GENERATED_INTERPRETER_DIR := $(PWD)/../liquidsoap/_build/default/src/js
NODE := $(shell which node)

# Versions for which documentation should be built and included
VERSIONS := dev 1.4.4 2.0.7 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.2.0 2.2.1 2.2.2
DEFAULT_VERSION := 2.2.2
VERSIONS := dev 1.4.4 2.0.7 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.2.0 2.2.1 2.2.2 2.2.3
DEFAULT_VERSION := 2.2.3
# DEFAULT_VERSION := dev

# Static content
Expand Down
3 changes: 3 additions & 0 deletions website/content/doc-2.2.3/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
reference.md
protocols.md
settings.md
179 changes: 179 additions & 0 deletions website/content/doc-2.2.3/beets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# Integrating a music library: an example with Beets

Liquidsoap's native sources can read from files and folders,
but if your radio uses an important music library
(more than a thousand tracks)
sorting by folders may not be enough.
You will also need to adjust the playout gain per track (ReplayGain).
In that case you would better have a music library
queried by Liquidsoap.
In this section we'll do this with [Beets](http://beets.io/).
Beets holds your music catalog,
cleans tracks' tags before importing,
can compute each track's ReplayGain,
and most importantly has a command-line interface we can leverage from Liquidsoap.
The following examples may also inspire you to integrate another library or your own scripts.

After installing Beets,
enable the `random` plug-in
(see [Beets documentation on plug-ins](https://beets.readthedocs.io/en/stable/plugins/index.html#using-plugins)).
To enable gain normalization, install and configure the
[`replaygain`](https://beets.readthedocs.io/en/stable/plugins/replaygain.html) plug-in.
To easily add single tracks to you library,
you might also be interested in the
[drop2beets](https://github.com/martinkirch/drop2beets#drop2beets) plug-in.
The following examples suppose you defined a `BEET` constant,
which contains the complete path to your `beet` executable (on UNIX systems, find it with `which beet`). For example:

```
BEET = "/home/radio/.local/bin/beet"
```

Before creating a Liquidsoap source,
let's see why Beets queries are interesting for a radio.

## Beets queries

Queries are parameters that you usually provide to the `beet ls` command :
Beets will find matching tracks.
The `random` plug-in works the same, except that it returns only one track matching the query
(see [the plug-in's documentation](https://beets.readthedocs.io/en/stable/plugins/random.html)).
Once your library is imported,
you can try the following queries on the command line
by typing `beet ls [query]` or `beet random [query]`.
To test quickly, add the `-t 60` option to `beet random`
so it will select an hour worth of tracks matching your query.

Without selectors, queries search in a track’s title, artist, album name,
album artist, genre and comments. Typing an artist name or a complete title
usually match the exact track, and you could do a lovely playlist just by querying `love`.

But in a radio you'll usually query on other fields.
You can select tracks by genre with the `genre:` selector.
Be careful that `genre:Rock` also matches `Indie Rock`, `Punk Rock`, etc.
To select songs having english lyrics, use `language:eng`.
Or pick 80s songs with `year:1980..1990`.

Beets also holds internal meta-data, like `added`:
the date and time when you imported each song.
You can use it to query tracks inserted over the past month with `added:-1m..`.
Or you can query track imported more than a year ago with `added:..-1y`.
Beets also lets you
[set your own tags](https://beets.readthedocs.io/en/stable/guides/advanced.html#store-any-data-you-like).

You can use the `info` plug-in to see everything Beets knows about title(s) matching a query
by typing `beet info -l [query]`.
See also [the Beets' documentation](https://beets.readthedocs.io/en/stable/reference/query.html)
for more details on queries operators.
All these options should allow you to create both general and specialiazed Liquidsoap sources.

## A source querying each next track from Beets

As of Liquidsoap 2.x we can create a function that creates a dynamic source,
given its `id` and a Beet query.
We rely on `request.dynamic` to call `beet random`
(with `-f '$path'` option so beets only returns the matching track's path)
every time the source must prepare a new track:

```liquidsoap
def beets(id, query) =
beets_src =
request.dynamic(id=id, retry_delay=1., {
request.create(
string.trim(
process.read("#{BEET} random -f '$path' #{query}")
)
)
})
(beets_src:source)
end
all_music = beets("all_music", "")
recent_music = beets("recent_music", "added:-1m..")
rock_music = beets("rock_music", "genre:Rock")
```

Note that

- `query` can be empty, it will match all tracks in the library.
- we set `retry_delay` to a second, to avoid looping on `beet` calls if something goes wrong.
- The final type hint (`:source`) will avoid false typing errors when the source is integrated in complex operators.

## Applying ReplayGain

When the [`replaygain` plug-in](https://beets.readthedocs.io/en/stable/plugins/replaygain.html)
is enabled, all tracks will have an additional metadata field called `replaygain_track_gain`.
Check that Beet is configured to
[write ID3 tags](https://beets.readthedocs.io/en/stable/reference/config.html#importer-options)
so Liquidsoap will be able to read this metadata -
your Beet configuration should include something like:

```
import:
write: yes
```

Then we only need to add `amplify` to our source creation function. In the example below we also add `blank.eat`, to automatically cut silence at the beginning or end of tracks.

```liquidsoap
def beets(id, query) =
beets_src =
blank.eat(id="#{id}_", start_blank=true, max_blank=1.0, threshold=-45.0,
amplify(override="replaygain_track_gain", 1.0,
request.dynamic(id=id, retry_delay=1., {
request.create(
string.trim(
process.read("#{BEET} random -f '$path' #{query}")
)
)
})
)
)
(beets_src:source)
end
```

This is the recommended Beets integration ;
such source will provide music continuously,
at a regular volume.

## Beets as a requests protocol

If you're queueing tracks with `request.queue`,
you may prefer to integrate Beets as a protocol.
In that case,
the list of paths returned by `beet random -f '$path'` fits directly
what's needed by protocol resolution:

```liquidsoap
def beets_protocol(~rlog,~maxtime,arg) =
timeout = maxtime - time()
command = "#{BEET} random -f '$path' #{arg}"
p = process.run(timeout=timeout, command)
if p.status == "exit" and p.status.code == 0 then
[string.trim(p.stdout)]
else
rlog("Failed to execute #{command}: #{p.status} (#{p.status.code}) #{p.stderr}")
[]
end
end
protocol.add("beets", beets_protocol,
syntax = "same arguments as beet's random module, see https://beets.readthedocs.io/en/stable/reference/query.html"
)
```

Once this is done,
you can push a beets query from [the telnet server](server.html):
if you created `request.queue(id="userrequested")`,
the server command
`userrequested.push beets:All along the watchtower`
will push the Jimi Hendrix's song.

With this method, you can benefit from replay gain metadata too, by wrapping
the recipient queue in an `amplify` operator, like

```liquidsoap
userrequested = amplify(override="replaygain_track_gain", 1.0,
request.queue(id="userrequested")
)
```
50 changes: 50 additions & 0 deletions website/content/doc-2.2.3/blank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Blank detection

[Liquidsoap](index.html) has three operators for dealing with blanks.

On GeekRadio, we play many files, some of which include bonus tracks, which
means that they end with a very long blank and then a little extra music. It's
annoying to get that on air. The `blank.skip` operator skips the
current track when a too long blank is detected, which avoids that. The typical
usage is simple:

```liquidsoap
# Wrap it with a blank skipper
source = blank.skip(source)
```

At [RadioPi](http://www.radiopi.org/) they have another problem: sometimes they
have technical problems, and while they think they are doing a live show,
they're making noise only in the studio, while only blank is on air; sometimes,
the staff has so much fun (or is it something else ?) doing live shows that they
leave at the end of the show without thinking to turn off the live, and the
listeners get some silence again. To avoid that problem we made the
`blank.strip` operators which hides the stream when it's too blank
(i.e. declare it as unavailable), which perfectly suits the typical setup used
for live shows:

```liquidsoap
interlude = single("/path/to/sorryfortheblank.ogg")
# After 5 sec of blank the microphone stream is ignored,
# which causes the stream to fallback to interlude.
# As soon as noise comes back to the microphone the stream comes
# back to the live -- thanks to track_sensitive=false.
stream = fallback(track_sensitive=false,
[ blank.strip(max_blank=5.,live) , interlude ])
# Put that stream to a local file
output.file(%vorbis, "/tmp/hop.ogg", stream)
```

If you don't get the difference between these two operators, you should learn
more about liquidsoap's notion of [source](sources.html).

Finally, if you need to do some custom action when there's too much blank, we
have `blank.detect`:

```liquidsoap
def handler()
system("/path/to/your/script to do whatever you want")
end
source = blank.detect(handler,source)
```
11 changes: 11 additions & 0 deletions website/content/doc-2.2.3/book.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# The Liquidsoap book

Together with the release of Liquidsoap 2.0, we have written _the Liquidsoap
book_ which covers in details the language and the process of building a
radio. It complements the online documentation by providing a homogeneous and
progressive presentation of Liquidsoap.

[![The Liquidsoap book](/assets/img/book.svg){height=600px}](https://www.amazon.com/dp/B095PVTYR3)

It can be [ordered from Amazon](https://www.amazon.com/dp/B095PVTYR3)
(or [read online](http://www.liquidsoap.info/book/book.pdf)).
112 changes: 112 additions & 0 deletions website/content/doc-2.2.3/build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Building Liquidsoap

## Forewords

Installing liquidsoap can be a difficult task. The software relies on a up-to date
OCaml compiler, as well as a bunch of OCaml modules and, for most of them, corresponding
C library dependencies.

Our recommended way of installing liquidsoap is via [opam](http://opam.ocaml.org/). `opam` can take
care of installing the correct OCaml compiler, optional and required dependencies as well as system-specific
package dependencies.

The `opam` method is described in details in the [documentation](doc/content/install.md).
We recommend that any interested user head over to this link to install the software via `opam`.

The remainder of this document describes how to compile liquidsoap locally for developers.

## Overview

Liquidsoap is compiled using [dune](https://dune.readthedocs.io/en/stable/), which is the most popular
OCaml build system at the moment. `dune` is tightly integrated with `opam` so, even if you are installing
from source using `dune`, `opam` remains an important tool.

Generally speaking, compiling from source may require the latest version of the liquidsoap code as well as its
dependencies. Some of its dependencies are optional and can be ignored at first and some are not.

Keep in mind that, although `opam` is generally aware of required minimal version for dependencies, `dune` is not.
If a dependency is outdated, `dune` compilation will simply fail, at which point your may have to figure out if
you need to update a dependency.

Each branch of liquidsoap is compiled using [github actions](https://github.com/savonet/liquidsoap/actions). When trying
to build a specific branch, if the CI passes with it then, most likely, you are missing a dependency or it is not
the latest version.

## `opam` pinning

`opam` pinning is a mechanism to update `opam` with the latest version of a package, even before it is published to
the official opam repository. This is the easiest way to update a dependency to its latest version.

You can pin directly from a local git repository checkout:

```shell
git clone https://github.com/savonet/ocaml-metadata.git
cd ocaml-metadata
opam pin -ny .
```

You can also pin directly using a git url:

```shell
opam pin -ny git+https://github.com/savonet/ocaml-cry
```

See `opam pin --help` for more defails about the available options.

## Dependencies

The best way to figure out what dependencies are required or optional and their versions is to use the latest `opam`
package. Since `liquidsoap` development is using `dune` and `opam`, the dependencies are kept in sync via the
local liquidsoap opam package(s) and this serves as the de-facto list of dependencies and their versions.

First, you should pin the latest liquidsoap code:

```shell
opam pin -ny git+https://github.com/savonet/liquidsoap
```

Then, ask `opam` to list all the dependencies for `liquidsoap`:

```shell
opam info liquidsoap
opam info liquidsoap-core
opam info liquidsoa-lang
```

This should give you a (long!) list of all dependencies. Then, you can query `opam` to see
what each dependency does. This is particularly useful for optional dependencies on `liquidsoap-core`
which provide opt-in features. For instance `opam info soundtouch` will let you know that this
package provides functions for changing pitch and timestretching audio data.

Lastly, there are two types of dependencies:

- Dependencies maintained by us
- Dependencies not maintained by us

For dependencies not maintained by us, most of the time, we rely on the latest published version. Very rarely should you
have to fetch/pin the latest version of these dependencies.

For dependencies maintained by us, we may break their API during our development cycle and you maybe have to fetch/pin
the latest version when compilign the latest `liquidsoap` code. You may also have to check out a specific
branch when compiling `liquidsoap` from a specific development branch when the changes in the liquidsoap code are paired with
changes in one of our dependencies. Typically, this happens a lof with the `ffmpeg` binding.

## Compiling

Once you have all dependencies installed, you should be able to compile via:

```shell
dune build
```

If an error occurs, you may need to see if you need to update a dependency. Hopefully, with a short iteration of this cycle,
you will end up with a successful build!

Once you have a successful build, you can also use the top-level `liquidsoap` script. This script builds the latest code and
executes it right away. It works as if you were calling the `liquidsoap` binary after installing it:

```shell
./liquidsoap -h output.ao
```

From here, you can start changing code, testing script etc. Happy hacking!
Loading

0 comments on commit 5d9ba82

Please sign in to comment.