Add a second playback source and a weighted scheduler mixing it with Navidrome: - Scheduler picks a provider by SOURCE_WEIGHTS, falling through to the others when one has nothing ready, so no source can stall playback. - YtdlpProvider reads a hand-maintained config/urls.txt; container URLs (playlist/album/label/artist) are flat-extracted and one entry is drawn at random, honouring the anti-repeat window. Adds Track.source_url. - YtdlpFetcher downloads bestaudio via the yt-dlp library, reusing the atomic hidden-temp-then-rename pattern; Liquidsoap decodes the result. - Queue now dispatches to a fetcher registry keyed by backend. - Sweep orphaned download temp files on daemon startup (leftovers from a killed container otherwise pile up and trip the stream fallback). Verified end-to-end: yt-dlp opus decoded and served as 192 kbps MP3, and the 3:1 default mix observed in play history. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
59 lines
2.8 KiB
Python
59 lines
2.8 KiB
Python
"""Runtime configuration, read from the environment.
|
|
|
|
Kept intentionally small; later milestones will add source credentials,
|
|
weights and retention settings here (or in a config file).
|
|
"""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
# Directory shared with the stream container. Paths returned to Liquidsoap must
|
|
# be valid inside *that* container, so both mount the cache at the same path.
|
|
CACHE_DIR = Path(os.environ.get("RADIEO_CACHE_DIR", "/cache"))
|
|
|
|
# Persistent state (SQLite). Kept out of the ephemeral cache directory.
|
|
STATE_DIR = Path(os.environ.get("RADIEO_STATE_DIR", "/state"))
|
|
STATE_DB = STATE_DIR / "radieo.db"
|
|
|
|
HTTP_HOST = os.environ.get("RADIEO_HTTP_HOST", "0.0.0.0")
|
|
HTTP_PORT = int(os.environ.get("RADIEO_HTTP_PORT", "8080"))
|
|
|
|
# --- Prefetching / retention ---
|
|
# How many downloaded tracks to keep ready ahead of playback.
|
|
PREFETCH = int(os.environ.get("RADIEO_PREFETCH", "3"))
|
|
# Seconds between prefetch-loop wake-ups.
|
|
PREFETCH_INTERVAL = float(os.environ.get("RADIEO_PREFETCH_INTERVAL", "2.0"))
|
|
# Keep the N most recently played files on disk; evict older ones (LRU).
|
|
RETENTION_KEEP = int(os.environ.get("RADIEO_RETENTION_KEEP", "20"))
|
|
# Do not replay a track seen among the last N plays, when avoidable.
|
|
ANTIREPEAT_WINDOW = int(os.environ.get("RADIEO_ANTIREPEAT_WINDOW", "50"))
|
|
|
|
# --- Navidrome / OpenSubsonic source ---
|
|
# Left empty means the provider is disabled (the stream then plays its own
|
|
# local-cache fallback). Credentials are expected to come from a .env file.
|
|
NAVIDROME_URL = os.environ.get("RADIEO_NAVIDROME_URL", "").strip()
|
|
NAVIDROME_USER = os.environ.get("RADIEO_NAVIDROME_USER", "").strip()
|
|
NAVIDROME_PASSWORD = os.environ.get("RADIEO_NAVIDROME_PASSWORD", "")
|
|
NAVIDROME_PLAYLIST = os.environ.get("RADIEO_NAVIDROME_PLAYLIST", "").strip()
|
|
# How often to reload the playlist contents, in seconds.
|
|
PLAYLIST_REFRESH = float(os.environ.get("RADIEO_PLAYLIST_REFRESH", "300"))
|
|
|
|
NAVIDROME_ENABLED = bool(
|
|
NAVIDROME_URL and NAVIDROME_USER and NAVIDROME_PASSWORD and NAVIDROME_PLAYLIST
|
|
)
|
|
|
|
# --- yt-dlp source ---
|
|
# Plain text file (one URL per line, '#' comments) mounted into the container.
|
|
# May contain direct track URLs or container URLs (playlist/album/label/artist),
|
|
# from which one track is picked at random. Left absent disables the source.
|
|
YTDLP_URLS_FILE = Path(os.environ.get("RADIEO_YTDLP_URLS_FILE", "/config/urls.txt"))
|
|
# How often to reload the URL list and re-expand container URLs, in seconds.
|
|
YTDLP_REFRESH = float(os.environ.get("RADIEO_YTDLP_REFRESH", "300"))
|
|
|
|
# --- Source mix (weights) ---
|
|
# Relative odds of drawing from each source. Isolated here on purpose so the
|
|
# mix can later move to a config file. A weight of 0 disables a source.
|
|
SOURCE_WEIGHTS = {
|
|
"navidrome": int(os.environ.get("RADIEO_WEIGHT_NAVIDROME", "3")),
|
|
"ytdlp": int(os.environ.get("RADIEO_WEIGHT_YTDLP", "1")),
|
|
}
|