Files
2026-07-04 01:56:10 +03:00

438 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ytdlp-navidrome — сервис поиска и загрузки музыки
## Назначение
Сервис-компаньон к Navidrome. Интерфейс — **Matrix-бот**. Позволяет:
1. Искать музыку по названию трека или имени исполнителя.
2. Отправлять превью (файл аудио) в чат для прослушивания.
3. По подтверждению пользователя скачивать трек в коллекцию Navidrome.
Весь трафик наружу (yt-dlp и Matrix) проходит через локальный ByeByeDPI-прокси (обход DPI).
Инфраструктура: k3s на Raspberry Pi. Музыка хранится на SSD:
- **Рабочая директория** (превью): `/mnt/ssd/k3s/services/ytdlp_navidrome/tmp`
- **Коллекция Navidrome** (финальный файл): `/mnt/ssd/k3s/services/navidrome/music/ytdlp`
---
## 1. Архитектура
```
┌────────────┐ ┌────────────────────────────────┐
│ Matrix │ Matrix CS API │ ytdlp-navidrome pod │
│ homeserver │◄──────────────────►│ │
│ │ │ ┌────────────┐ ┌───────────┐ │
└────────────┘ │ │ Go-сервис │ │ ByeByeDPI │ │
│ │ (Matrix │ │ (sidecar │ │
│ │ бот + │ │ SOCKS5 │ │
│ │ yt-dlp) │ │ прокси) │ │
│ └─────┬──────┘ └─────▲─────┘ │
│ │ │ :1080 │
│ │ --proxy │ │
│ └──────────────┘ │
└───────────────┬────────────────┘
┌──────────────────────────┼──────────────┐
│ /mnt/ssd (hostPath) │ │
│ ▼ │
│ ytdlp_navidrome/tmp/ navidrome/music/ytdlp/
│ (превью, очищается) (финальные файлы)
└─────────────────────────────────────────┘
```
### Стек
| Компонент | Технология | Обоснование |
|-----------|-----------|-------------|
| Язык | Go | Статический бинарник, минимум зависимостей для ARM. |
| Фреймворк | Echo GO | Простой, быстрый |
| Matrix SDK | [mautrix-go](https://github.com/mautrix/go) | Де-факто стандартный Go SDK для Matrix. |
| Поиск | yt-dlp `--flat-playlist --print` | Только метаданные, без скачивания. |
| Скачивание | yt-dlp + ffmpeg | Поддержка всех музыкальных источников. |
| Прокси | ByeByeDPI (sidecar) | Обход DPI без внешнего VPN. Локальный SOCKS5. |
| Хранение | hostPath | Общие директории с Navidrome и для превью. |
### Контейнеры в поде
| Контейнер | Образ | Порты | Назначение |
|-----------|-------|-------|------------|
| `ytdlp-bot` | Собственный (Go + yt-dlp + ffmpeg) | — | Основной сервис |
| `byedpi` | `ghcr.io/hufrea/byedpi:latest` (или собственный) | `1080` (SOCKS5) | Обход DPI, локальный прокси |
---
## 2. Функциональные требования
### 2.1 Диалог с пользователем (Matrix-бот)
Пользователь общается с ботом в личном или групповом чате Matrix.
```
Пользователь: /search Radiohead Creep
Бот: Найдено 5 результатов:
1. Radiohead - Creep (3:58)
2. Radiohead - Creep [Live] (4:21)
3. Radiohead - Creep [Acoustic] (3:45)
...
Ответьте номером для превью.
Пользователь: 1
Бот: [прикреплённый аудиофайл]
Добавить в коллекцию Navidrome? (да/нет)
Пользователь: да
Бот: ✅ Добавлено: Radiohead - Creep.opus
```
#### Команды бота
| Команда | Описание |
|---------|----------|
| `/search <запрос>` | Искать музыку. Возвращает список результатов с номерами. |
| `<номер>` | Отправить аудиопревью трека из результатов поиска. |
| `да` / `yes` / `добавить` | Сохранить трек в коллекцию Navidrome. |
| `нет` / `no` / `отмена` | Отменить. Удалить превью. |
| `/status` | Показать статус: место на диске, время работы, версия. |
| `/proxy` | Показать текущие настройки прокси и статус ByeByeDPI. |
| `/help` | Справка. |
#### Состояние диалога
Бот хранит состояние per-user (в памяти, не персистентное в v1):
| Состояние | Данные |
|-----------|--------|
| `idle` | — |
| `search_results` | Запрос, список результатов, timestamp |
| `preview_sent` | video_id, путь к превью-файлу, timestamp |
Таймаут состояния: 10 минут. После таймаута превью удаляется из tmp/.
### 2.2 Поиск (внутренний)
1. Бот формирует запрос к yt-dlp:
```
yt-dlp "ytsearch{limit}:{query}" \
--flat-playlist --print "%(id)s\t%(title)s\t%(duration_string)s\t%(channel)s" \
--proxy socks5://byedpi:1080
```
2. Парсит вывод, возвращает пронумерованный список.
3. Таймаут: `SEARCH_TIMEOUT` (по умолчанию `10s`).
### 2.3 Превью аудио (внутренний)
1. Указанный трек скачивается во временную директорию:
```
yt-dlp "https://www.youtube.com/watch?v={id}" \
-x --audio-format opus --audio-quality 5 \
--output "{tmp_dir}/{id}.%(ext)s" \
--proxy socks5://byedpi:1080
```
Качество `5` (среднее) — превью, сэкономить трафик и место.
2. Бот отправляет файл в Matrix-чат как `m.audio`.
3. Файл сохраняется в `tmp_dir` до решения пользователя (добавить / отменить).
### 2.4 Добавление в коллекцию (внутренний)
1. Окончательное скачивание (или конвертация превью) в коллекцию:
```
yt-dlp "https://www.youtube.com/watch?v={id}" \
-x --audio-format opus --audio-quality 0 \
--embed-thumbnail --add-metadata \
--output "{music_dir}/{artist} - {title}.%(ext)s" \
--proxy socks5://byedpi:1080
```
Качество `0` (лучшее) — финальный файл для Navidrome.
2. Файл переименовывается в формат `<artist> - <title>.<ext>`.
3. Превью из tmp/ удаляется.
4. Navidrome обнаружит файл при следующем сканировании (раз в 6ч).
### 2.5 Health check
**Эндпоинт:** `GET /healthz` (для Kubernetes liveness/readiness probes)
**Ответ:** `200 OK` `{"status": "ok"}` — если:
- Go-сервис запущен
- yt-dlp доступен
- ByeByeDPI sidecar отвечает (проверка через socks5://byedpi:1080)
---
## 3. Конфигурация
### 3.1 Переменные окружения
#### Matrix
| Переменная | Обязательная | По умолчанию | Описание |
|------------|:------------:|--------------|----------|
| `MATRIX_HOMESERVER` | да | — | URL homeserver (например `https://matrix.example.com`) |
| `MATRIX_USER_ID` | да | — | `@bot:example.com` — ID бота |
| `MATRIX_ACCESS_TOKEN` | да | — | Access token бота |
| `MATRIX_DEVICE_ID` | нет | `YTDLBOT` | Device ID |
| `MATRIX_ENCRYPTION` | нет | `false` | Использовать E2EE (требует libolm/curve25519-dalek) |
#### Поиск и скачивание
| Переменная | По умолчанию | Описание |
|------------|--------------|----------|
| `SEARCH_LIMIT` | `10` | Максимум результатов поиска |
| `SEARCH_TIMEOUT` | `10s` | Таймаут на yt-dlp search |
| `DOWNLOAD_TIMEOUT` | `120s` | Таймаут на скачивание |
| `AUDIO_FORMAT` | `opus` | Целевой формат аудио |
| `AUDIO_QUALITY_PREVIEW` | `5` | Качество превью (0=лучшее, 10=худшее) |
| `AUDIO_QUALITY_FINAL` | `0` | Качество финального файла |
| `PREVIEW_TTL` | `10m` | Время жизни превью-файла до автоудаления |
| `ALLOWED_USERS` | — | Список Matrix ID через запятую (пустой = все) |
#### Прокси (пробрасывается в yt-dlp через `--proxy`)
| Переменная | По умолчанию | Описание |
|------------|--------------|----------|
| `PROXY_ENABLED` | `true` | Включить прокси для yt-dlp. `false` = без прокси. |
| `PROXY_URL` | `socks5://localhost:1080` | URL прокси (SOCKS5 / HTTP). Если запущен sidecar — `socks5://byedpi:1080` |
#### ByeByeDPI (sidecar-контейнер)
Параметры ByeByeDPI передаются через `COMMAND` в Deployment (см. §4.2).
| Флаг | По умолчанию (рекомендация) | Описание |
|------|---------------------------|----------|
| `--ip` | `0.0.0.0` | Адрес для прослушивания |
| `--port` | `1080` | Порт SOCKS5 |
| `--disorder` | `1-1` | Fake packet для браузеров/доменов |
| `--auto` | *(включён)* | Автоматический обход DPI |
| `--ttl` | `3` | TTL для fake-пакетов |
Все флаги настраиваются в Deployment-манифесте. Если нужен другой обходчик DPI (naiveproxy, xray, sing-box),
поменяйте image и `PROXY_URL`.
#### Пути
| Переменная | По умолчанию | Описание |
|------------|--------------|----------|
| `MUSIC_DIR` | `/music` | mountPath к коллекции Navidrome |
| `MUSIC_SUBDIR` | `ytdlp` | Поддиректория для финальных файлов |
| `TMP_DIR` | `/tmp/ytdlp-previews` | Рабочая директория для превью |
| `YTDLP_PATH` | `yt-dlp` | Путь к бинарнику |
### 3.2 Kubernetes-манифесты
Каталог: `rasp/k3s/services/ytdlp_navidrome/`
```
├── README.md # ← этот документ
├── main.go # исходный код сервиса
├── Dockerfile # мультистейдж: Go build + yt-dlp + ffmpeg
├── service.yaml # Namespace + Deployment + PersistentVolumes
└── go.mod
```
#### Deployment — ключевые моменты
```yaml
spec:
containers:
- name: ytdlp-bot
image: ytdlp-navidrome:latest # Собственный образ
env:
- name: MATRIX_HOMESERVER
valueFrom:
secretKeyRef:
name: ytdlp-matrix-secret
key: homeserver
- name: MATRIX_USER_ID
valueFrom:
secretKeyRef:
name: ytdlp-matrix-secret
key: user-id
- name: MATRIX_ACCESS_TOKEN # секрет
valueFrom:
secretKeyRef:
name: ytdlp-matrix-secret
key: access-token
- name: PROXY_URL
value: "socks5://localhost:1080" # sidecar
- name: MUSIC_DIR
value: "/music"
volumeMounts:
- name: music
mountPath: /music/ytdlp # коллекция (Read-Write)
- name: tmp
mountPath: /tmp/ytdlp-previews
resources:
requests: { memory: "64Mi", cpu: "50m" }
limits: { memory: "128Mi", cpu: "500m" }
- name: byedpi # ◀ sidecar
image: ghcr.io/hufrea/byedpi:latest
command: ["ciadpi"]
args:
- "--ip=0.0.0.0"
- "--port=1080"
- "--auto"
- "--disorder=1-1"
- "--ttl=3"
ports:
- containerPort: 1080
name: sock5
resources:
requests: { memory: "16Mi", cpu: "10m" }
limits: { memory: "32Mi", cpu: "100m" }
# network=host не нужен — sidecar общается с основным
# контейнером через localhost (shared network namespace в поде)
volumes:
- name: music
hostPath:
path: /mnt/ssd/k3s/services/navidrome/music
type: DirectoryOrCreate
- name: tmp
emptyDir: { medium: Memory, sizeLimit: 512Mi }
# Превью хранятся в tmpfs — не нагружают SSD,
# ограничены 512 MiB для безопасности
```
**Service:** не нужен для бота (исходящие подключения к Matrix homeserver).
Если нужен health endpoint для мониторинга — ClusterIP на порт 8080.
**Secret:**
```yaml
apiVersion: v1
kind: Secret
metadata:
name: ytdlp-matrix-secret
namespace: ytdlp-navidrome
type: Opaque
stringData:
homeserver: "https://matrix.example.com"
user-id: "@bot:example.com"
access-token: "syt_..." # ← ваш токен бота
```
---
## 4. Нефункциональные требования
### 4.1 Формат аудио
| Параметр | Превью | Финальный файл |
|----------|--------|----------------|
| Кодек | Opus | Opus |
| Качество | `5` (среднее) | `0` (лучшее) |
| Обложка | нет | `--embed-thumbnail` |
| Теги | минимальные | `--add-metadata` (title, artist, album) |
| Пример размера | ~1–2 МБ на трек | ~3–5 МБ на трек |
Альтернатива: MP3 V0, если клиенты не поддерживают Opus.
### 4.2 Именование файлов
Директория на хосте: `/mnt/ssd/k3s/services/navidrome/music/ytdlp/`
Формат имени: `<artist> - <title>.<ext>`
Примеры:
- `Radiohead - Creep.opus`
- `Massive Attack - Teardrop.opus`
- `unknown - dQw4w9WgXcQ.opus` (метаданные неизвестны)
Запрещённые символы в имени файла (`/ \ : * ? " < > |`) заменяются на `_`.
### 4.3 Производительность
| Метрика | Целевое значение |
|---------|-----------------|
| Поиск (10 результатов) | ≤ 5 секунд |
| Превью (1 трек) | ≤ 30 секунд |
| Финальное скачивание (1 трек) | ≤ 60 секунд |
| Потребление RAM (простой) | ≤ 64 MiB |
| Потребление RAM ( ByeByeDPI ) | ≤ 16 MiB |
| Потребление CPU (простой) | ≤ 50m + 10m (bot + proxy) |
### 4.4 Доступ из Matrix
- Бот запускается как зарегистрированный пользователь homeserver.
- Бот слушает события `m.room.message` и `/search`, реагирует на `m.text`.
- Бот может быть ограничен `ALLOWED_USERS` — список Matrix ID через запятую (если пусто — отвечает всем).
- В v1 без E2EE. E2EE поддерживается mautrix-go (опциональное включение через `MATRIX_ENCRYPTION=true`).
### 4.5 Безопасность
| Вопрос | Решение |
|--------|---------|
| Токен бота в секрете | `ytdlp-matrix-secret` — не в env-файле кластера |
| Доступ к боту | Опционально `ALLOWED_USERS` |
| Прокси — только для yt-dlp | ByeByeDPI слушает на localhost, не проброшен наружу |
| Запись в коллекцию | runAsUser совпадает с Navidrome (совместимость прав) |
### 4.6 Ограничения (scope v1)
| Feature | Статус в v1 | Примечание |
|---------|:-----------:|-----------|
| Matrix-бот | ✅ входит | Основной интерфейс |
| E2EE | ⚠️ опционально | По флагу `MATRIX_ENCRYPTION` |
| REST API | ❌ не входит | Только Matrix. REST — при необходимости позже. |
| Web UI | ❌ не входит | — |
| Групповые чаты | ⚠️ basic | Бот отвечает на mention в группах |
| Очередь загрузок | ❌ не входит | Синхронная загрузка |
| SoundCloud/Bandcamp | ⚠️ best-effort | yt-dlp поддерживает, поиск может не работать |
| Изменение прокси на лету | ❌ не входит | Требуется перезапуск пода |
---
## 5. Логирование
Формат: текстовый, в stdout.
```
2026-07-02T14:30:00Z INFO search user=@alice:example.com query="Radiohead Creep" results=10 dur=2.1s
2026-07-02T14:30:05Z INFO preview user=@alice:example.com video_id=XF2YMYBLnSw size=1.2MB dur=18s
2026-07-02T14:30:20Z INFO download user=@alice:example.com file="Radiohead - Creep.opus" size=3.7MB dur=12s
2026-07-02T14:30:20Z INFO cleanup preview=XF2YMYBLnSw removed=true
2026-07-02T14:31:00Z WARN preview_ttl_expired video_id=dQw4w9WgXcQ
2026-07-02T14:32:00Z INFO byedpi_check status=ok latency=3ms
```
Уровень логирования: настраивается через `LOG_LEVEL`.
---
## 6. Тестирование
| Уровень | Что проверять |
|---------|--------------|
| Unit | Парсинг вывода yt-dlp, формирование имён файлов, санитизация, FSM состояний |
| Integration | Запуск yt-dlp в subprocess с `/dev/null` прокси, проверка формата вывода |
| Matrix | Взаимодействие с тестовым homeserver (Dendrite/Conduit) |
| Manual | Полный сценарий: /search → номер → превью → да → файл в navidrome |
---
## 7. Стадии реализации
### Stage 1 — MVP
- [ ] Matrix-бот: подключение через mautrix-go
- [ ] Команда `/search` — парсинг вывода yt-dlp, отправка результатов
- [ ] Превью: скачивание трека в tmp/, отправка аудио в чат
- [ ] Добавление: финальное скачивание в коллекцию, очистка tmp
- [ ] ByeByeDPI sidecar в Deployment
- [ ] `--proxy` везде где yt-dlp обращается наружу
- [ ] Dockerfile (Go + yt-dlp + ffmpeg)
- [ ] service.yaml (Deployment + Secret)
- [ ] Smoke-тест: /search → preview → да → файл появился в файловой системе
### Stage 2 — Улучшения
- [ ] Авторизация через ALLOWED_USERS
- [ ] E2EE (опционально)
- [ ] Автоочистка tmp/ по расписанию (cronJob или timer в Go)
- [ ] `/status` и `/proxy` команды
- [ ] Обработка ошибок: не найдено, таймаут превью, нет места
- [ ] Батч-загрузка (серия треков по команде)
### Stage 3 — Интеграция
- [ ] Принудительное сканирование Navidrome после загрузки (Navidrome API)
- [ ] Метрики Prometheus
- [ ] Адаптация прокси под разные обходчики (naiveproxy, xray, sing-box)
- [ ] REST-эндпоинты для альтернативного доступа (опционально)