denpa-radio/config/liquidsoap.liq

140 lines
4 KiB
Text

# denpa-radio liquidsoap script
# adding a station: copy a station block, rename, restart liquidsoap.
settings.log.stdout.set(true)
settings.log.level.set(3)
source_pw = environment.get(default="", "SOURCE_PW")
# deck-shuffle: full shuffle once, hand out in order, reshuffle on exhaustion.
# returns a record with .source (request.dynamic) and .peek (returns next N filenames).
def make_deck(dir) =
files = ref([])
cursor = ref(0)
def reload_and_shuffle() =
files := list.shuffle(file.ls(dir, recursive=true, absolute=true))
cursor := 0
end
def next_file() =
if !cursor >= list.length(!files) then reload_and_shuffle() end
f = list.nth(default="", !files, !cursor)
cursor := !cursor + 1
f
end
def next_request() =
f = next_file()
if f == "" then null() else request.create(f) end
end
def peek(n) =
needed = !cursor + n
if needed > list.length(!files) and list.length(!files) > 0 then
# would exhaust the deck during peek; not worth pre-reshuffling.
# return whatever's left, the caller pads.
()
end
list.init(
min(n, list.length(!files) - !cursor),
fun(i) -> list.nth(default="", !files, !cursor + i)
)
end
reload_and_shuffle()
{ source = request.dynamic(next_request), peek = peek }
end
def emit_now_playing(station, m) =
filename = m["filename"]
if filename != "" then
# decoder-reported metadata may be empty; fall back to request.duration which
# reads it directly from the file. -1.0 means unknown.
meta_dur = m["duration"]
dur_str =
if meta_dur != "" then
meta_dur
else
# request.duration returns float? in liquidsoap 2.3 — unwrap with default -1.
d = null.get(default=-1., request.duration(filename))
if d > 0. then string(int_of_float(d)) else "" end
end
payload = json()
payload.add("station", station)
payload.add("artist", m["artist"])
payload.add("title", m["title"])
payload.add("album", m["album"])
payload.add("filename", filename)
payload.add("duration", dur_str)
payload.add("started_at", string(int_of_float(time())))
file.write(data=payload.stringify(), atomic=true, temp_dir="/now-playing", "/now-playing/#{station}.json")
end
end
def append_history(station, m) =
filename = m["filename"]
if filename != "" then
history_path = "/now-playing/#{station}.history.json"
entry = {
title = m["title"],
artist = m["artist"],
album = m["album"],
filename = filename,
started_at = string(int_of_float(time()))
}
arr =
if file.exists(history_path) then
existing = file.contents(history_path)
try
let json.parse (parsed : [{
title: string,
artist: string,
album: string,
filename: string,
started_at: string
}]) = existing
parsed
catch _ do
[]
end
else
[]
end
new_arr = list.add(entry, list.prefix(49, arr))
out_str = json.stringify(new_arr)
file.write(data=out_str, atomic=true, temp_dir="/now-playing", history_path)
end
end
# === station: minecraft ===
minecraft_deck = make_deck("/library/minecraft/tracks")
minecraft = minecraft_deck.source
minecraft.on_track(fun (m) -> begin
emit_now_playing("minecraft", m, minecraft_deck.peek(4))
append_history("minecraft", m)
end)
minecraft = crossfade(minecraft)
minecraft = mksafe(minecraft)
output.icecast(
%mp3(bitrate=192, stereo=true),
host="icecast", port=8000, password=source_pw,
mount="/minecraft.mp3",
name="Denpa — Minecraft",
description="Minecraft soundtracks",
genre="game-ost",
url="https://denpa.femboy.page",
minecraft
)
output.icecast(
%opus(bitrate=96, vbr="constrained", samplerate=48000, channels=2),
host="icecast", port=8000, password=source_pw,
mount="/minecraft.opus",
name="Denpa — Minecraft",
description="Minecraft soundtracks",
genre="game-ost",
url="https://denpa.femboy.page",
minecraft
)