Skip to content
teuk edited this page May 30, 2026 · 1 revision

Radio integration

This page documents the current Mediabot v3 radio integration.

The radio stack is built around:

  • Icecast status/public stream information;
  • Liquidsoap telnet queue control;
  • a local MP3 cache;
  • cache-first playback through m play;
  • optional non-blocking yt-dlp downloads;
  • safe production defaults.

The current implementation is a safe foundation. YouTube authenticated downloads are supported, but cookies/session handling remains operationally fragile and should not be treated as a guaranteed permanent mechanism.


Scope

The radio work provides:

  • Liquidsoap queue control from IRC;
  • Icecast status and listener commands;
  • MP3 cache diagnostics and imports;
  • cache-first m play;
  • non-blocking yt-dlp downloads;
  • a download enable/disable toggle;
  • cleaner IRC messages for common yt-dlp failures;
  • safer formatting for m song and m listeners.

Most administrative radio commands are Master-only during rollout.

m play is also kept Master-only for now.


Recommended deployment model

Use two different policies depending on the instance.

Development instance

RADIO_DOWNLOAD_ENABLED=1

The dev instance may download missing tracks through yt-dlp, populate the local MP3 cache, and validate Liquidsoap/Icecast behavior.

Production or public instances

RADIO_DOWNLOAD_ENABLED=0

Production instances should normally remain cache-only.

In this mode:

  • m play searches the MP3 cache;
  • cache hits are pushed to Liquidsoap;
  • cache misses return a clean message;
  • no yt-dlp process is started.

This prevents public bots from becoming accidental remote download endpoints.


Configuration

Typical development configuration:

[radio]
YOUTUBEDL_INCOMING=/home/mediabot/mediabot_v3/mp3

RADIO_DOWNLOAD_ENABLED=1

YTDLP_PATH=/usr/local/bin/yt-dlp
YTDLP_TIMEOUT=180
YTDLP_COOKIES_FILE=/home/mediabot/.config/yt-dlp/youtube.cookies.txt
YTDLP_REMOTE_COMPONENTS=ejs:github

LIQUIDSOAP_TELNET_HOST=127.0.0.1
LIQUIDSOAP_TELNET_PORT=1235
LIQUIDSOAP_QUEUE_ID=mediabot_queue

RADIO_ICECAST_STATUS_BASE_URL=http://127.0.0.1:8000
RADIO_ICECAST_PUBLIC_BASE_URL=http://teuk.org:8000
RADIO_ICECAST_TIMEOUT=5
RADIO_ICECAST_PRIMARY_MOUNT=/radio.mp3

For cache-only production use:

RADIO_DOWNLOAD_ENABLED=0

Icecast settings

Key Purpose
RADIO_ICECAST_STATUS_BASE_URL Local/private Icecast base URL used by the bot to fetch status.
RADIO_ICECAST_PUBLIC_BASE_URL Public base URL shown in IRC output.
RADIO_ICECAST_TIMEOUT HTTP timeout for Icecast status requests.
RADIO_ICECAST_PRIMARY_MOUNT Main mount, currently /radio.mp3 in the dev stack.

The old historical fallback /radio160.mp3 should not be used as the default for the new dev stack.


Liquidsoap settings

Key Purpose
LIQUIDSOAP_TELNET_HOST Liquidsoap telnet host, usually 127.0.0.1.
LIQUIDSOAP_TELNET_PORT Liquidsoap telnet port, currently 1235 on the dev stack.
LIQUIDSOAP_QUEUE_ID Queue id, currently mediabot_queue.

The currently verified Liquidsoap commands are:

mediabot_queue.push <uri>
mediabot_queue.queue
mediabot_queue.skip
mediabot_queue.flush_and_skip

There is no known mediabot_queue.status command in the current Liquidsoap setup, so Mediabot does not depend on one.


MP3 cache-first playback

The playback logic lives in:

Mediabot/Radio/Request.pm

When using:

m play <query>

Mediabot follows this order:

  1. search the local MP3 table/cache;
  2. verify that the file referenced by the row is readable;
  3. push the cached file to Liquidsoap if available;
  4. only then consider yt-dlp;
  5. start yt-dlp only if RADIO_DOWNLOAD_ENABLED=1.

This protects production and keeps playback fast when the cache already contains the track.


MP3 cache commands

Import one MP3

m radioimport /absolute/path/file.mp3 [artist - title]

Example:

m radioimport /home/mp3/TeuK/ab.mp3 TeuK - ab

The command verifies the file, accepts MP3 files only, reads nearby .info.json metadata when available, and inserts or updates the MP3 table without creating avoidable duplicates.

Import a directory

m radioimportdir [directory]

Without an argument, the command uses YOUTUBEDL_INCOMING.

The directory import scans recursively and applies the same import/update logic as radioimport.

Diagnostics

m radiocache

This reports DB rows, readable rows, broken rows, local MP3 files, and examples of missing DB entries.

Pruning

Dry-run:

m radiocacheprune

Actual cleanup:

m radiocacheprune confirm

radiocacheprune confirm only deletes broken rows from the MP3 table. It does not delete MP3 files from disk.


Queue control commands

m radioqueue
m radiopush /absolute/path/file.mp3
m radioskip
m radioflush

radiopush expects an absolute path to a readable MP3 file. Paths containing spaces are intentionally avoided for now because the current download/import workflow uses restricted filenames.


Download commands

Status

m radiodlstatus

Shows whether a non-blocking yt-dlp job is running.

It reports the query, PID, age, timeout, and temporary stdout/stderr files when a job exists.

Cancel

m radiodlcancel

Cancels the current yt-dlp job, clears the internal state, and removes temporary job files when possible.


yt-dlp behavior

Mediabot starts yt-dlp in a child process and monitors it from the event loop.

Safeguards:

  • only one active download per bot instance;
  • configurable timeout with YTDLP_TIMEOUT;
  • stdout/stderr captured to temporary files;
  • completion updates/inserts the MP3 row;
  • successful downloads are pushed to Liquidsoap;
  • failures are classified into short IRC messages.

Current useful settings:

YTDLP_PATH=/usr/local/bin/yt-dlp
YTDLP_TIMEOUT=180
YTDLP_COOKIES_FILE=/home/mediabot/.config/yt-dlp/youtube.cookies.txt
YTDLP_REMOTE_COMPONENTS=ejs:github

YouTube cookies

YouTube cookies are optional but often required for YouTube requests.

The cookies file should be outside the repository:

/home/mediabot/.config/yt-dlp/youtube.cookies.txt

Recommended permissions:

chown mediabot:mediabot /home/mediabot/.config/yt-dlp/youtube.cookies.txt
chmod 600 /home/mediabot/.config/yt-dlp/youtube.cookies.txt

Do not commit this file.

Do not put it in public snapshots.

Do not paste it into tickets, forums, or IRC.

radiocheck can verify that the file exists and is readable. It cannot guarantee that YouTube will accept the session.

If YouTube rejects the cookies, Mediabot should return a short message explaining that cookies.txt may be stale or rotated.


EJS / YouTube challenge support

Recent yt-dlp versions may need remote components for YouTube challenge solving.

Recommended setting:

YTDLP_REMOTE_COMPONENTS=ejs:github

Manual check:

/usr/local/bin/yt-dlp \
  --cookies /home/mediabot/.config/yt-dlp/youtube.cookies.txt \
  --remote-components ejs:github \
  --skip-download \
  --no-playlist \
  --print "%(title)s" \
  "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

If this fails with cookie/session errors, refresh cookies first.

If it fails with challenge/EJS errors, check the JavaScript runtime and YTDLP_REMOTE_COMPONENTS.


Deferred Chromium/browser-profile workflow

A future direction may be:

yt-dlp --cookies-from-browser chromium:/home/mediabot/.config/chromium-mediabot-youtube

and a future config key such as:

YTDLP_COOKIES_FROM_BROWSER=chromium:/home/mediabot/.config/chromium-mediabot-youtube

This is not implemented yet.

A fully automatic headless Google login is not a reliable target because Google can require 2FA, CAPTCHA, browser verification, or session rotation.

For now, keep production cache-only and treat cookies as an operational dependency for dev downloads.


Status and display commands

m radiocheck
m radiostatus
m radiomounts
m song
m listeners

m song and m listeners use IRC formatting that avoids unsafe background colors and should be readable on both dark and light IRC clients.

m song does not print a [ LIVE ! ] badge.

m listeners uses English wording.


radiocheck

Use:

m radiocheck

Expected checks include:

  • incoming directory readability/writability;
  • download enabled/disabled state;
  • yt-dlp path/version;
  • cookies file readability;
  • remote components setting;
  • timeout;
  • Liquidsoap host/port/queue;
  • Icecast status/public URLs.

Example:

Radio check:
incoming: OK writable (/home/mediabot/mediabot_v3/mp3)
downloads: enabled
yt-dlp: OK 2026.03.17 (/usr/local/bin/yt-dlp)
cookies: OK readable (/home/mediabot/.config/yt-dlp/youtube.cookies.txt)
yt-dlp remote components: ejs:github
yt-dlp timeout: 180 seconds
Liquidsoap: OK 127.0.0.1:1235 queue=mediabot_queue
Icecast status base: http://127.0.0.1:8000
Icecast public stream: http://teuk.org:8000/radio.mp3

Help

Radio commands can be discovered with:

m help commands radio
m help play

Large help output is paced to reduce flood risk.


Validation checklist

From IRC on the dev instance:

m check
m radiocheck
m radiocache
m radiodlstatus
m radiostatus
m radiomounts
m song
m listeners
m radioqueue
m radioimport /home/mp3/TeuK/ab.mp3 TeuK - ab
m play ab
m radioskip
m radioflush
m play Rick Astley Never Gonna Give You Up
m radiodlstatus
m help commands radio

Also test a blank or whitespace-only IRC message. The bot should ignore it without warning noise.


Troubleshooting quick map

Symptom Likely cause First check
Cache hit does not play Liquidsoap queue issue or unreadable file m radioqueue, m radiocheck
Cache miss does not download RADIO_DOWNLOAD_ENABLED=0 m radiocheck
YouTube rejects cookies stale/rotated cookies refresh youtube.cookies.txt
403/player-token challenge cookies/EJS/YouTube policy change check cookies and YTDLP_REMOTE_COMPONENTS
yt-dlp job stuck slow/hung download m radiodlstatus, then m radiodlcancel
No Icecast info wrong Icecast status URL or mount m radiostatus, m radiomounts

Current limits

  • YouTube cookies are fragile and may expire.
  • Chromium/browser-profile cookie extraction is not implemented yet.
  • m play remains Master-only during rollout.
  • Production instances should stay cache-only until deliberately changed.
  • No DB schema change was made for this integration.

Clone this wiki locally