- Python 81.1%
- TypeScript 11.9%
- CSS 6.9%
- HTML 0.1%
| deploy | ||
| webui | ||
| .gitignore | ||
| extensions_custom.conf | ||
| full_probe.py | ||
| jukebox.agi | ||
| jukebox_api.py | ||
| komarujukebox.py | ||
| models.json | ||
| README.md | ||
| scout_models.py | ||
| station.agi | ||
| station.py | ||
| track_agent.py | ||
| voicepool.py | ||
Jukebox — музыка по запросу: поток, телефон, Discord
Опиши, что хочешь послушать — система соберёт бесконечную радиостанцию под твой вайб и отдаст её сразу в трёх видах: живой HTTP MP3-поток (браузер, Minecraft- радио, любой плеер), голосовой канал Discord и телефонную трубку через FreePBX.
Как это работает
- Промт → трек. LLM-агент (
track_agent.py) сам ищет на YouTube через тулзы (search_youtube,list_channel_uploads,get_video_details) и выбирает конкретное видео — понимает контекст «посвежее / из третьего акта / не тот кавер», а не гадает по устаревшей памяти. Модель для выбора настраивается (цепочка по аптайму либо конкретная). Скоринг по ключевым словам — fallback. - Авто-DJ. Станция бесконечно подбирает треки под вайб, не повторяясь
(модель видит и сыгранное, и то что уже в очереди), качает через
yt-dlp, нормализует громкость (dynaudnorm), склеивает в непрерывный MP3 и вещает всем слушателям сразу. Следующий трек выбирается заранее, пока играет текущий — на стыке нет паузы. - Конечные запросы. «Поставь одну песню X» — агент ставит её и завершает
станцию (
finish/end_station), а не крутит по кругу. Открытый вайб («что-нибудь бодрое») играет бесконечно. - Пауза при пустоте. Нет слушателей → обычная станция замирает (0 CPU/
трафик), но живёт; пришёл слушатель — продолжает. Вечный эфир
liveкрутит 24/7 и всегда готов принять звонок.
Компоненты
| Файл | Что делает |
|---|---|
station.py |
Radio Station API + авто-DJ + MP3-поток + web UI + вечный эфир + пул телефонных номеров |
track_agent.py |
LLM-агент выбора трека (tool-calling), цепочка/выбор модели, nudge-ретрай |
komarujukebox.py |
Discord-бот: /listen, /radio_*, старые /phone, /queue |
voicepool.py |
Пул ботов-воркеров для параллельного голосового стрима |
jukebox_api.py |
Старый API очереди «заказал → сыграло в трубке подряд» |
jukebox.agi |
AGI: проигрывание очереди заказов в трубке |
station.agi |
AGI: слушать live/latest/номер-станции поток в трубке (MP3Player) |
extensions_custom.conf |
Пример dialplan FreePBX |
Станция и Discord-бот могут жить на разных машинах (бот ходит на станцию по HTTP), FreePBX — третья машина. Все три общаются по сети.
Вывод / точки входа
- Веб:
http://<station-host>/— форма «вбей вайб, выбери модель → поток + плеер». - Список станций:
http://<station-host>/list— все активные станции с их потоками и телефонными номерами (автообновление) — чтобы не потерять url, если закрыл вкладку. - Поток:
http://<station-host>/stream/<id>.mp3, вечный…/stream/live.mp3. - Discord:
/listen <vibe> [model]— поднимает станцию, врубает в твоём голосовом через воркер-бота, отдаёт ссылку и телефонный номер./radio_skip|status|stop|volume|pool. - Телефон (FreePBX): вечный эфир 24/7, последняя созданная станция, а также
пул временных персональных номеров — каждой станции при создании выдаётся свой
номер из диапазона, звонок на него играет именно её (номера в
extensions_custom.conf).
HTTP API станции
| Метод | Путь | Назначение |
|---|---|---|
| POST | /session |
создать станцию: {prompt, requester?, model?} → {id, stream_url, phone} |
| GET | /stream/<id>.mp3 |
живой MP3-поток станции |
| GET | /session/<id> |
статус (что играет, очередь, слушатели) |
| POST | /session/<id>/skip |
следующий трек |
| POST | /session/<id>/enqueue |
заказать конкретный трек следующим |
| POST | /session/<id>/stop |
остановить станцию |
| GET | /latest |
последняя активная станция (для телефона) |
| GET | /by_phone/<num> |
станция по временному номеру (для телефона) |
| GET | /models |
список пресетов моделей |
| GET | /sessions |
список станций (JSON) |
| GET | /list |
список станций (HTML-страница) |
| GET | /health |
health |
Конфигурация (env)
Станция (station.py):
| Переменная | Смысл |
|---|---|
JUKEBOX_TOKEN |
токен управляющих запросов (поток и заходы с доверенных сетей — без токена) |
STATION_TRUSTED_NETS |
подсети без токена (через запятую, CIDR) — веб-морда, LAN, mesh |
STATION_PORT |
порт (по умолчанию 80; для <1024 нужна CAP_NET_BIND_SERVICE) |
STATION_PUBLIC_HOST |
хост в ссылках на поток |
LOGFARE_API_KEY, LOGFARE_URL |
доступ к LLM |
JUKEBOX_AGENT_MODELS |
цепочка моделей агента по аптайму |
JUKEBOX_AGENT_MAX_TOKENS |
лимит токенов ответа (запас под reasoning-модели) |
YT_DLP_PATH |
путь к yt-dlp |
STATION_PHONE_START/END |
диапазон временных телефонных номеров |
STATION_LIVE_PROMPT |
вайб вечного эфира |
Discord-бот (komarujukebox.py): DISCORD_BOT_TOKEN, STATION_API,
STATION_TOKEN, STATION_PHONE, опционально JUKEBOX_WORKER_TOKENS /
JUKEBOX_WORKER_CLIENT_IDS для нескольких голосовых станций одновременно.
Разворачивание
# станция
python3 -m venv venv && ./venv/bin/pip install aiohttp yt-dlp
# задать env (см. выше), затем запустить station.py как сервис
# Discord-бот
pip install -U discord.py pynacl
# задать env, запустить komarujukebox.py как сервис
# FreePBX: положить station.agi в agi-bin, подключить extensions_custom.conf
Токены воркеров (для нескольких станций одновременно) задаются через
JUKEBOX_WORKER_TOKENS или отдельные env-файлы. Без них пул стартует пустым —
/listen тогда отдаёт только поток и телефон.
Секреты
Токены/ключи держатся в env (или env-файлах с chmod 600), читаются через
переменные окружения и не коммитятся — см. .gitignore. В коде нет
захардкоженных ключей.
Замечания по безопасности
/streamнамеренно без токена (плееры не шлют заголовки) — защита неугадываемым id сессии; рассчитано на закрытую сеть без внешнего доступа.- Управляющие эндпоинты требуют
X-Jukebox-Token, кроме запросов из доверенных подсетей (STATION_TRUSTED_NETS). - Публичного доступа к потоку нет by design; для выставления наружу добавь обратный прокси с TLS и аутентификацией.