johncrash64/radio-dj
GitHub: johncrash64/radio-dj
一个封装为单一 Go 二进制文件的全天候 AI DJ 网络电台,通过 LLM 驱动的语音播报和 Icecast 流媒体广播,实现自动化、可互动的个人电台运营。
Stars: 14 | Forks: 6
# radio-dj
**一个封装在单一 Go 二进制文件中的 24/7 AI-DJ 网络电台。**
指定你的音乐文件夹,配上你自己的 LLM API key,它就会广播一条
连续的 Icecast MP3 流,让 DJ 在直播过程中——甚至在歌曲播放中途——进行解说。
[](LICENSE)
[](https://go.dev)
[]()
[](https://www.linkedin.com/in/johncrash64/)
English · [Español](README.es.md)
`radio-dj` 是一个用于运行完整电台的单一 Go 二进制文件(约 9 MB)。它
会从你的音乐库中挑选曲目,AI DJ 会在歌曲之间进行语音播报——包括曲目介绍、
当前时间、**真实天气**、通过网络搜索获取的奇闻趣事,以及直播点播
专属问候——并广播标准的 Icecast MP3 流,任何播放器都可以
收听。
无需 Docker。绝不臃肿。仅仅是一个 **8.7 MB 的二进制文件**,占用 **约 20–30 MB 的 RAM**(包含 Icecast + ffmpeg 时约 50–60 MB)——在 macOS 上测得,轻量到甚至可以跑在树莓派上。
## ✨ 功能特性 - **24/7 电台** — 一条永不掉线的连续 Icecast MP3 流 (192 kbps)。 - **AI DJ** 在歌曲间进行语音播报 —— 介绍、时间、真实天气、奇闻趣事 (网络搜索),以及直播点播专属问候。语音支持任何语言。 - **歌曲进行中的实时闪避 (ducking)** —— DJ 可以在音乐播放*之上*说话 (`sidechaincompress`),硬件混音器风格。 - **实时点播** —— 听众可以通过 Web UI 或 API 点播歌曲, DJ 会在节目中读出点播者的名字。 - **新粗野主义 UI** —— 动画卡带、正在播放 + 下一首,以及三个 信息面板(历史记录、DJ 日志、点播列表)。 - **可编辑技能** —— 以文本文件形式重新编程 DJ 的播报环节。无需 重新编译。 - **BYOK LLM** —— 支持 GLM, OpenAI, OpenRouter, Ollama, Groq,或任何 兼容 OpenAI 的提供商。 - **常驻运行** —— 可作为 macOS launchd、Linux systemd 或 OpenRC 服务安装,重启后依然运行。 - **可安装的 PWA** —— 将电台 UI 添加到你的程序坞/主屏幕(通过 Chrome 安装按钮或 Safari → 添加到程序坞)。App shell 支持离线工作。 ## 📸 截图 | DJ 直播中 (桌面端) | 移动端 | 设置向导 | |:---:|:---:|:---:| |
|
|
|
## 🚀 快速开始
### 一行命令(推荐)
```
curl -fsSL https://github.com/johncrash64/radio-dj/raw/master/install.sh | bash
```
自动检测你的操作系统(macOS 或 Linux),安装 `icecast` + `ffmpeg` + `edge-tts`,
并下载预编译的二进制文件(如果没有则从源码构建)。
### 从源码构建
**macOS (Homebrew):**
```
brew install icecast ffmpeg
pipx install edge-tts
git clone https://github.com/johncrash64/radio-dj.git
cd radio-dj && go build -o radio-dj .
```
**Linux (apt):**
```
sudo apt-get install icecast2 ffmpeg
pipx install edge-tts # or: pip3 install --user edge-tts
git clone https://github.com/johncrash64/radio-dj.git
cd radio-dj && go build -o radio-dj .
```
### 配置并启动
运行 `radio-dj serve` —— 首次运行时会打开一个**引导向导**,它会
将所有配置写入 `~/.radio-dj/config.json`。你也可以设置环境变量(见下文):
```
export ZAI_API_KEY=your_key # GLM-5.2 (any OpenAI-compatible works)
export RDJ_LIBRARY=~/Music/library # your music folder
export RDJ_LOCATION="La Paz" # for time + weather
export RDJ_VOICE_CMD="edge-tts --voice es-CO-SalomeNeural --text {text} --write-media {out}"
```
### 常驻服务(支持重启后自动恢复)
```
radio-dj install # macOS → launchd agent · Linux → systemd user unit or OpenRC (needs sudo/doas)
radio-dj uninstall # stop and remove the service
```
然后打开 **http://localhost:7710** (Web UI) 和 **http://localhost:7702/stream.mp3** (音频流)。
### 作为应用安装 (PWA)
电台 UI 是一个 Progressive Web App —— 安装它以获得原生应用般的体验:
- **Chrome / Edge** —— 打开 UI,点击地址栏中的**安装图标**。
- **macOS Safari** —— **文件 → 添加到程序坞**。
- **iOS Safari** —— 分享 → **添加到主屏幕**。
安装后,它会在独立窗口中打开,没有浏览器框架,并且 App shell
支持离线加载(但直播流本身仍需网络连接)。
## 🎙️ DJ
DJ 由 **LLM Director(导演)** 驱动,它会通过一次
结构化调用规划好每一 *tanda*(批次)—— 它负责挑选歌单并决定在歌曲之间说什么:
- **曲目介绍** —— “接下来是,*专辑*里 *艺术家* 的*歌曲*……”
- **电台 ID** —— 电台名称、位置、听众数量。
- **时间与天气** —— 基于你的位置获取真实情况。
- **奇闻趣事** —— 由 LLM 进行网络搜索,或使用免费的 Wikipedia API。
- **点播专属问候** —— “这首歌是送给 *María* 的 —— *她点播了……*”
每一句语音播报的流程:**LLM**(结合曲目上下文)撰写文本 → **你的 TTS**
进行语音合成 → 在曲目播放前或中途播出。歌曲进行中的播报使用
`sidechaincompress`,在 DJ 说话时压低背景音乐的音量。
## 🧩 技能(可编辑)
DJ 的播报环节以文本文件形式存放在 `~/.radio-dj/skills/` 下。你可以编辑
内置环节或添加你自己的内容 —— radio-dj 会在启动时加载它们。无需重新编译。
## 📡 API
简单的 HTTP 接口 —— 兼容任何客户端(移动应用、Home Assistant、PanelHUD):
| 方法 | 路径 | 描述 |
|---|---|---|
| `GET` | `/now-playing` | 当前 + 下一首曲目、点播列表、状态 |
| `POST` | `/request` | `{"from":"María","text":"Bohemian Rhapsody"}` — 点播一首歌 |
| `GET` | `/stream.mp3` | 音频流 |
| `GET` | `/listen.pls` · `/listen.m3u` | 用于 Sonos/VLC/汽车的播放列表 |
| `GET` | `/health` | 存活探测 |
音频流是一个 **标准的 Icecast / HTTP-MP3 URL** —— 将其粘贴到 VLC、mpv、
任何浏览器、TuneIn、Radio Garden、Sonos,或 iOS/Android 电台应用中即可。
完整参考:**[docs/API.md](docs/API.md)** · **[docs/openapi.yaml](docs/openapi.yaml)**。
## ⚙️ 配置
radio-dj 从**三个层级读取配置,最低层级优先**:
```
env vars (RDJ_*) > ~/.radio-dj/config.json > defaults
```
**你只需选择其中一种。** 引导向导会为你写入 `config.json`。
高级用户 / CI 可以通过环境变量覆盖任何配置。
### `config.json` 键(由向导写入,或手动编辑)
```
{
"library": "/home/me/Music/library",
"source": "folder",
"glm_api_key": "your-key",
"glm_model": "glm-5.2",
"llm_provider": "glm",
"voice_provider":"edge-tts",
"voice": "es-CO-SalomeNeural",
"location": "La Paz",
"lat": -16.5,
"lon": -68.15,
"language": "es",
"dj_talk": "regular",
"chunk": 8,
"bitrate": 192,
"station_name": "radio-dj"
}
```
### 完整参考(环境变量 → config.json 键 → 默认值)
| 环境变量 | `config.json` 键 | 默认值 | 说明 |
|---|---|---|---|
| `ZAI_API_KEY` / `RDJ_GLM_API_KEY` | `glm_api_key` | — | 你的 LLM key (BYOK) |
| `RDJ_LIBRARY` | `library` | `~/Music/library` | 音乐文件夹 |
| `RDJ_SOURCE` | `source` | `folder` | `folder` 或 `navidrome` |
| `RDJ_NAVIDROME_URL` | `navidrome_url` | `http://localhost:4533` | Navidrome 服务器 |
| `RDJ_NAVIDROME_USER` | `navidrome_user` | — | Navidrome 用户名 |
| `RDJ_NAVIDROME_PASS` | `navidrome_pass` | — | Navidrome 密码 |
| `RDJ_GLM_MODEL` | `glm_model` | `glm-5.2` | 模型名称 |
| `RDJ_LLM_PROVIDER` | `llm_provider` | `glm` | 预设:`glm`/`openai`/`openrouter`/… |
| `RDJ_VOICE_CMD` | `voice_cmd` | — | 原始 TTS 命令(`{text}` / `{out}` 占位符) |
| `RDJ_VOICE_PROVIDER` | `voice_provider` | — | `edge-tts` / `piper` / `say` |
| `RDJ_VOICE` | `voice` | — | 该提供商对应的语音 ID |
| `RDJ_LOCATION` | `location` | `La Paz` | 城市名称(用于获取时间 + 天气) |
| `RDJ_LAT` / `RDJ_LON` | `lat` / `lon` | La Paz 坐标 | 获取天气所需的精确坐标 |
| `RDJ_LANGUAGE` | `language` | `es` | `es` 或 `en`(DJ 提示词 + UI 语言) |
| `RDJ_DJ_TALK` | `dj_talk` | `regular` | `low`/`regular`/`high`/`verbose` — 话痨程度 |
| `RDJ_DJ_EVERY` | `dj_every` | `3` | DJ 两次播报之间的歌曲间隔(旧版下限) |
| `RDJ_CHUNK` | `chunk` | `8` | 每批次歌曲数(预取窗口) |
| `RDJ_BITRATE` | `bitrate` | `192` | 流媒体比特率 (kbps) |
| `RDJ_STATION_NAME` | `station_name` | `radio-dj` | 电台名称 |
| `RDJ_BED` | `bed` | — | 用于闪避的器乐背景音(DJ 在音乐之上说话) |
| `RDJ_ICECAST_HOST` | — | `localhost` | icecast 主机地址 (仅限环境变量) |
| `RDJ_ICECAST_PORT` | — | `7702` | icecast 端口 (仅限环境变量) |
| `RDJ_ICECAST_SOURCE_PW` | — | — | icecast 源密码 (仅限环境变量) |
| `RDJ_STATUS_PORT` | — | `7710` | Web UI + API 端口 (仅限环境变量) |
## 🛠️ 架构
```
radio-dj (Go, ~20–30 MB RAM)
├─ supervisor spawns + watches icecast (child, auto-restart)
├─ producer builds next batch (LLM Director + TTS) via prefetch
├─ master ffmpeg (persistent) PCM → MP3 → icecast, never reconnects
└─ decoders ffmpeg (per segment) track/voice → PCM into the master
```
一条永不中断的 icecast 连接 = 听众端零卡顿掉线。
```
┌──────────┐ PCM ┌──────────────┐ MP3 ┌──────────┐
│ producer │─────────▶│ master ffmpeg │──────▶│ icecast │──▶ listeners
└──────────┘ └──────────────┘ └──────────┘
│ ▲
│ TTS voice │ PCM (fd4)
▼ │
┌──────────┐ ┌───────────────┐
│ LLM + TTS │ │ sidechaincomp │ ← ducks music for voice
└──────────┘ └───────────────┘
```
## 🪶 许可证
**MIT** —— 随你折腾。详见 [LICENSE](LICENSE)。
由 **[johncrash64](https://www.linkedin.com/in/johncrash64/)** 制作 ·
[报告 Bug](https://github.com/johncrash64/radio-dj/issues) ·
[功能请求](https://github.com/johncrash64/radio-dj/issues)
标签:AI电台, EVTX分析, FFmpeg, Go语言, Icecast, LLM集成, 日志审计, 流媒体, 程序破解, 音频处理