mirror of
https://github.com/kmein/niveum
synced 2026-03-16 10:11:08 +01:00
feat: radio powered by liquidsoap
This commit is contained in:
@@ -54,12 +54,12 @@ in [
|
|||||||
stream = "https://c3lounge.de/radio/8000/radio.mp3";
|
stream = "https://c3lounge.de/radio/8000/radio.mp3";
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
stream = "https://radio.kmein.de/lyrik/listen.ogg";
|
stream = "https://radio.kmein.de/lyrik.ogg";
|
||||||
station = "Lyrik";
|
station = "Lyrik";
|
||||||
desc = "Lyrik-Lesung rund um die Uhr.";
|
desc = "Lyrik-Lesung rund um die Uhr.";
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
stream = "https://radio.kmein.de/lyrikline/listen.ogg";
|
stream = "https://radio.kmein.de/lyrikline.ogg";
|
||||||
station = "Lyrikline";
|
station = "Lyrikline";
|
||||||
logo = "https://www.lyrikline.org/themes/lyrik/svg/Logo_lyrikline_pure.svg";
|
logo = "https://www.lyrikline.org/themes/lyrik/svg/Logo_lyrikline_pure.svg";
|
||||||
desc = "24/7 zufällige Wiedergaben von lyrikline.org.";
|
desc = "24/7 zufällige Wiedergaben von lyrikline.org.";
|
||||||
|
|||||||
@@ -1,212 +1,101 @@
|
|||||||
{ lib, pkgs, config, ... }:
|
{ lib, pkgs, config, ... }:
|
||||||
let
|
let
|
||||||
inherit (import <niveum/lib>) tmpfilesConfig serveHtml;
|
icecastPassword = "hackme";
|
||||||
|
lyrikline-poem = pkgs.writers.writeDash "lyrikline.sh" ''
|
||||||
|
set -efu
|
||||||
|
|
||||||
radioStore = "/var/lib/radio";
|
html=$(mktemp)
|
||||||
htgenPort = 8080;
|
trap clean EXIT
|
||||||
stations = {
|
clean() {
|
||||||
lyrikline = {
|
rm "$html"
|
||||||
streamPort = 8001;
|
}
|
||||||
mpdPort = 6601;
|
|
||||||
description = ''
|
|
||||||
Weltklang. Welt als ewiges Gedicht, das seine Schallspuren durch Raum und Zeit jagt. Endlose Zufallswiedergabe von <a href="//lyrikline.org">lyrikline</a>. — Listen to the sound of voices and poems permeating linguistic and geographic barriers, 24 hours per day.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
lyrik = {
|
|
||||||
streamPort = 8002;
|
|
||||||
mpdPort = 6602;
|
|
||||||
description = ''
|
|
||||||
Deutsche Lyrik, die du noch nicht gut genug kennst. Tritt in einen Fluss aus Reim und Maß; keine zwei Mal ist er derselbe.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
mpd-add-with-tags = pkgs.writers.writeHaskell "mpd-add-with-tags" {
|
|
||||||
libraries = with pkgs.haskellPackages; [ optparse-generic libmpd ];
|
|
||||||
} ''
|
|
||||||
{-# LANGUAGE DeriveGeneric, OverloadedStrings #-}
|
|
||||||
import Control.Monad (void)
|
|
||||||
import Data.String
|
|
||||||
import Network.MPD
|
|
||||||
import Options.Generic
|
|
||||||
|
|
||||||
data Options = Options { url :: String, artist :: Maybe String, title :: Maybe String }
|
lyrikline=https://www.lyrikline.org
|
||||||
deriving (Generic)
|
random_route="$(${pkgs.curl}/bin/curl -sSL "$lyrikline/index.php/tools/getrandompoem" --data-raw 'lang=de' --compressed | ${pkgs.jq}/bin/jq -r .link)"
|
||||||
|
poem_url="$lyrikline$random_route"
|
||||||
|
|
||||||
instance ParseRecord Options
|
${pkgs.curl}/bin/curl -sSL "$poem_url" > "$html"
|
||||||
|
|
||||||
main :: IO ()
|
poem_file="$(${pkgs.gnugrep}/bin/grep -o 'https://.*\.mp3' "$html" | head -n1)"
|
||||||
main = do
|
|
||||||
options <- getRecord "Add to MPD with tags"
|
author="$(${pkgs.htmlq}/bin/htmlq -f "$html" --text '#gedicht-autor')"
|
||||||
void $ withMPD $ do
|
title="$(${pkgs.htmlq}/bin/htmlq -f "$html" --text .gedicht-originaltitel)"
|
||||||
songId <- addId (fromString $ url options) Nothing
|
|
||||||
maybe (pure ()) (addTagId songId Artist . fromString) $ artist options
|
echo "annotate:title=\"$title\",album=\"$poem_url\",artist=\"$author\":$poem_file"
|
||||||
maybe (pure ()) (addTagId songId Title . fromString) $ title options
|
'';
|
||||||
|
stavenhagen-poem = pkgs.writers.writeDash "stavenhagen.sh" ''
|
||||||
|
base=https://www.deutschelyrik.de
|
||||||
|
author=$(${pkgs.curl}/bin/curl -sSL "$base" | ${pkgs.htmlq}/bin/htmlq option --attribute value | shuf -n1)
|
||||||
|
poem=$(${pkgs.curl}/bin/curl -sSL "$base/$author" | ${pkgs.htmlq}/bin/htmlq '#mnav2 li > a' --attribute href | shuf -n1)
|
||||||
|
|
||||||
|
html=$(mktemp)
|
||||||
|
trap clean EXIT
|
||||||
|
clean() {
|
||||||
|
rm "$html"
|
||||||
|
}
|
||||||
|
|
||||||
|
${pkgs.curl}/bin/curl -sSL "$base/$poem" > "$html"
|
||||||
|
|
||||||
|
printf "annotate:title=\"%s\",album=\"%s\",artist=\"%s\":$base/%s\n" \
|
||||||
|
"$(${pkgs.htmlq}/bin/htmlq --text '.ce_text h1' -f "$html")" \
|
||||||
|
"$base/$poem" \
|
||||||
|
"$(${pkgs.htmlq}/bin/htmlq --text 'h1 + p em' -f "$html")" \
|
||||||
|
"$(${pkgs.htmlq}/bin/htmlq 'audio source' --attribute src -f "$html")"
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
# https://github.com/savonet/liquidsoap/issues/1043#issuecomment-593354427
|
||||||
|
services.liquidsoap.streams.radio = pkgs.writeText "lyrikline.liq" ''
|
||||||
|
set("protocol.external.curl","${pkgs.curl}/bin/curl")
|
||||||
|
|
||||||
|
def random_lyrikline() =
|
||||||
|
uri = list.hd(default="", get_process_lines("${lyrikline-poem}"))
|
||||||
|
request.create(uri, persistent=true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def random_stavenhagen() =
|
||||||
|
uri = list.hd(default="", get_process_lines("${stavenhagen-poem}"))
|
||||||
|
request.create(uri, persistent=true)
|
||||||
|
end
|
||||||
|
|
||||||
|
lyrikline = mksafe(audio_to_stereo(request.dynamic(random_lyrikline)))
|
||||||
|
|
||||||
|
output.icecast(
|
||||||
|
mount = '/lyrikline.ogg',
|
||||||
|
port = ${toString config.services.icecast.listen.port},
|
||||||
|
password = "${icecastPassword}",
|
||||||
|
description = "lyrikline. listen to the poet (unofficial)",
|
||||||
|
%vorbis(quality = 1),
|
||||||
|
lyrikline
|
||||||
|
)
|
||||||
|
|
||||||
|
stavenhagen = mksafe(audio_to_stereo(request.dynamic(random_stavenhagen)))
|
||||||
|
|
||||||
|
output.icecast(
|
||||||
|
mount = '/lyrik.ogg',
|
||||||
|
port = ${toString config.services.icecast.listen.port},
|
||||||
|
password = "${icecastPassword}",
|
||||||
|
description = "Lyrik für alle – Neue Lust auf Lyrik | www.deutschelyrik.de",
|
||||||
|
%vorbis(quality = 1),
|
||||||
|
stavenhagen
|
||||||
|
)
|
||||||
'';
|
'';
|
||||||
|
|
||||||
mpcs = lib.mapAttrs (name: station: pkgs.writers.writeDashBin "mpc-${name}" ''
|
services.icecast = {
|
||||||
MPD_PORT=${toString station.mpdPort} ${pkgs.mpc_cli}/bin/mpc "$@"
|
enable = true;
|
||||||
'') stations;
|
hostname = "radio.kmein.de";
|
||||||
in
|
admin.password = "hackme";
|
||||||
{
|
listen.port = 6457;
|
||||||
imports = [ <stockholm/krebs/3modules/htgen.nix> ];
|
extraConf = ''
|
||||||
nixpkgs.overlays = [
|
<authentication>
|
||||||
(self: super: { htgen = super.callPackage <stockholm/krebs/5pkgs/simple/htgen> {}; })
|
<source-password>${icecastPassword}</source-password>
|
||||||
];
|
</authentication>
|
||||||
|
|
||||||
systemd.tmpfiles.rules = [
|
|
||||||
(tmpfilesConfig {
|
|
||||||
type = "d";
|
|
||||||
path = radioStore;
|
|
||||||
mode = "0755";
|
|
||||||
user = config.users.extraUsers.radio.name;
|
|
||||||
age = "1d";
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
users.extraUsers.radio = {
|
|
||||||
isSystemUser = true;
|
|
||||||
group = "radio";
|
|
||||||
};
|
|
||||||
users.groups.radio = {};
|
|
||||||
|
|
||||||
krebs.htgen.radio = {
|
|
||||||
port = htgenPort;
|
|
||||||
user.name = "radio";
|
|
||||||
script = ''. ${pkgs.writers.writeDash "meinskript" ''
|
|
||||||
send200() {
|
|
||||||
printf 'HTTP/1.1 200 OK\r\n'
|
|
||||||
printf 'Content-Type: text/html; charset=UTF-8\r\n'
|
|
||||||
printf 'Connection: close\r\n'
|
|
||||||
printf '\r\n'
|
|
||||||
}
|
|
||||||
|
|
||||||
case "$Method $Request_URI" in
|
|
||||||
"GET /lyrik/status")
|
|
||||||
send200
|
|
||||||
video_id="$(
|
|
||||||
${mpcs.lyrik}/bin/mpc-lyrik status -f %file% \
|
|
||||||
| head -n1 \
|
|
||||||
| grep -o 'id=[^&]*' \
|
|
||||||
| sed 's/^id=//g'
|
|
||||||
)"
|
|
||||||
|
|
||||||
${pkgs.youtube-dl}/bin/youtube-dl -j "https://www.youtube.com/watch?v=$video_id" \
|
|
||||||
| ${pkgs.jq}/bin/jq -r '"% [\(.title)](\(.webpage_url))\n\n\(.description)"' \
|
|
||||||
| sed 's/$/ /g' \
|
|
||||||
| ${pkgs.pandoc}/bin/pandoc -s
|
|
||||||
|
|
||||||
exit
|
|
||||||
;;
|
|
||||||
"GET /lyrikline/status")
|
|
||||||
send200
|
|
||||||
|
|
||||||
hash="$(
|
|
||||||
${mpcs.lyrikline}/bin/mpc-lyrikline status -f '%file%' \
|
|
||||||
| head -n 1 \
|
|
||||||
| md5sum \
|
|
||||||
| cut -d' ' -f 1
|
|
||||||
)"
|
|
||||||
url="$(cat ${radioStore}/$hash)"
|
|
||||||
|
|
||||||
echo "<html><body style='margin:0'><iframe style='width:100%;height:100%;border:0' src="$url"></iframe></body></html>"
|
|
||||||
exit
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
''}'';
|
|
||||||
};
|
|
||||||
|
|
||||||
containers = lib.mapAttrs (name: station: {
|
|
||||||
autoStart = true;
|
|
||||||
config = {config, pkgs, ...}: {
|
|
||||||
services.mpd = {
|
|
||||||
enable = true;
|
|
||||||
network.port = station.mpdPort;
|
|
||||||
extraConfig = ''
|
|
||||||
log_level "default"
|
|
||||||
|
|
||||||
audio_output {
|
|
||||||
name "${name}"
|
|
||||||
type "httpd"
|
|
||||||
encoder "vorbis"
|
|
||||||
port "${toString station.streamPort}"
|
|
||||||
bitrate "128"
|
|
||||||
format "44100:16:2"
|
|
||||||
always_on "yes"
|
|
||||||
tags "yes"
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}) stations;
|
|
||||||
|
|
||||||
environment.systemPackages = lib.attrValues mpcs;
|
|
||||||
|
|
||||||
systemd.services.lyrikline = {
|
|
||||||
after = [ "container@lyrikline.service" ];
|
|
||||||
wantedBy = [ "container@lyrikline.service" ];
|
|
||||||
startAt = "*:00/5";
|
|
||||||
serviceConfig.User = config.users.extraUsers.radio.name;
|
|
||||||
preStart = "${mpcs.lyrikline}/bin/mpc-lyrikline crop || :";
|
|
||||||
script = ''
|
|
||||||
set -efu
|
|
||||||
|
|
||||||
lyrikline=https://www.lyrikline.org
|
|
||||||
for _ in $(seq 1 10); do
|
|
||||||
random_route="$(${pkgs.curl}/bin/curl -sSL "$lyrikline/index.php/tools/getrandompoem" --data-raw 'lang=de' --compressed | ${pkgs.jq}/bin/jq -r .link)"
|
|
||||||
poem_url="$lyrikline$random_route"
|
|
||||||
|
|
||||||
poem_file="$(
|
|
||||||
${pkgs.curl}/bin/curl -sSL "$poem_url" \
|
|
||||||
| grep -o 'https://.*\.mp3' \
|
|
||||||
| head -n1
|
|
||||||
)"
|
|
||||||
|
|
||||||
hash="$(echo "$poem_file" | md5sum | cut -d' ' -f 1)"
|
|
||||||
echo "$poem_file ($hash) -> $poem_url"
|
|
||||||
echo "$poem_url" > "${radioStore}/$hash"
|
|
||||||
|
|
||||||
${mpcs.lyrikline}/bin/mpc-lyrikline add "$poem_file"
|
|
||||||
done
|
|
||||||
|
|
||||||
${mpcs.lyrikline}/bin/mpc-lyrikline play
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.lyrik = {
|
|
||||||
after = [ "container@lyrik.service" ];
|
|
||||||
wantedBy = [ "container@lyrik.service" ];
|
|
||||||
preStart = "${mpcs.lyrik}/bin/mpc-lyrik crop || :";
|
|
||||||
restartIfChanged = true;
|
|
||||||
serviceConfig.User = config.users.extraUsers.radio.name;
|
|
||||||
script =
|
|
||||||
let
|
|
||||||
invidious = "https://invidious.silkky.cloud";
|
|
||||||
videoIds = import <niveum/lib/hot-rotation/lyrik.nix>;
|
|
||||||
streams = lib.concatMapStringsSep "\n" (id: "${invidious}/latest_version?id=${id}&itag=251") videoIds;
|
|
||||||
streamsFile = pkgs.writeText "hotrot" streams;
|
|
||||||
in ''
|
|
||||||
set -efu
|
|
||||||
${mpcs.lyrik}/bin/mpc-lyrik add < ${toString streamsFile}
|
|
||||||
|
|
||||||
${mpcs.lyrik}/bin/mpc-lyrik crossfade 5
|
|
||||||
${mpcs.lyrik}/bin/mpc-lyrik random on
|
|
||||||
${mpcs.lyrik}/bin/mpc-lyrik repeat on
|
|
||||||
${mpcs.lyrik}/bin/mpc-lyrik play
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
services.nginx.virtualHosts."radio.kmein.de" = {
|
services.nginx.virtualHosts."radio.kmein.de" = {
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
locations = lib.mkMerge (
|
locations."/".proxyPass = "http://127.0.0.1:${toString config.services.icecast.listen.port}";
|
||||||
[
|
|
||||||
{ "/".extraConfig = serveHtml (import ./station-list.nix { inherit pkgs lib stations; }) pkgs; }
|
|
||||||
] ++ (lib.mapAttrsToList (name: station: {
|
|
||||||
"= /${name}/status".proxyPass = "http://127.0.0.1:${toString htgenPort}";
|
|
||||||
"= /${name}/listen.ogg".proxyPass = "http://127.0.0.1:${toString station.streamPort}";
|
|
||||||
"= /${name}.ogg".return = "301 http://radio.xn--kiern-0qa.de/${name}/listen.ogg"; # legacy
|
|
||||||
}) stations)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ let
|
|||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
imports = [ <stockholm/krebs/3modules/htgen.nix> ];
|
||||||
|
|
||||||
krebs.htgen.tarot = {
|
krebs.htgen.tarot = {
|
||||||
port = tarotPort;
|
port = tarotPort;
|
||||||
user.name = "radio";
|
user.name = "radio";
|
||||||
|
|||||||
Reference in New Issue
Block a user