# 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. Файл переименовывается в формат ` - .<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-эндпоинты для альтернативного доступа (опционально)