Files
stoat-mbot/.env.example
T
Leonid PershinandClaude Opus 5 0b17b880d6 Fix video codec, aspect and placement
Three problems visible at once on a running instance: the picture
stuttered and drifted behind the sound, it was letterboxed oddly, and it
appeared as a separate tile instead of coming from the bot.

- YouTube handed us AV1 (format 398). Software-decoding AV1 at 720p does
  not sustain real time on a small server, which explains both the
  stutter and the drift; H.264 is now requested first, VP9 second.
- The frame was padded into a fixed box, so a clip whose proportions
  differed got black bars baked in and then more from the client. Size is
  now a bounding box and the frame keeps the clip's own proportions.
- Video was published as a screen share, which every client renders as
  its own tile. VIDEO_SOURCE=camera (the new default) puts it inside the
  bot's tile; "screen" keeps the old behaviour.

VIDEO_SYNC_OFFSET_MS is there for the residual drift, since audio and
video travel as two separately published tracks. Measured loudnorm first
to rule it out as the cause of the desync: it adds 0 ms.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 02:29:29 +03:00

127 lines
7.5 KiB
Bash

# ---------------------------------------------------------------- Stoat ----
# Публичный адрес API вашего инстанса (тот же, что в клиенте).
# Бот ходит по нему как обычный клиент; домен резолвится в хост через
# extra_hosts в compose.yml, поэтому TLS валидный и NAT loopback не нужен.
STOAT_API_URL=https://chat.example.com/api
# Домен вашего Stoat — тот же, что в STOAT_API_URL, без схемы.
# Используется в compose.yml: контейнер резолвит его в сам хост, где внешний
# Caddy держит валидный TLS. Сам compose.yml править не нужно.
STOAT_DOMAIN=chat.example.com
# Токен бота: Settings → My Bots → создать бота → скопировать токен.
STOAT_BOT_TOKEN=
# Префикс команд в чате.
COMMAND_PREFIX=!
# Удалять сообщение с командой после её распознавания (нужно право ManageMessages).
DELETE_COMMAND_MESSAGES=true
# Имя LiveKit-ноды из Revolt.toml, секция [hosts.livekit].
# В стандартном self-hosted это "worldwide".
VOICE_NODE=worldwide
# ------------------------------------------------------------- Веб-панель ---
PORT=3005
HOST=0.0.0.0
# Адрес, по которому панель открывается в браузере (без слэша в конце).
PUBLIC_URL=https://music.example.com
# Секрет для подписи сессионных cookie. Сгенерируйте: openssl rand -hex 32
JWT_SECRET=
# Срок жизни сессии панели, часов.
SESSION_TTL_HOURS=168
# ---------------------------------------------------------------- Аудио ----
# Путь к yt-dlp. В docker-образе он уже установлен.
YTDLP_PATH=yt-dlp
# Необязательно: cookies.txt аккаунта YouTube — снимает возрастные ограничения,
# «Sign in to confirm you're not a bot» и открывает приватные/платные видео.
# Файл должен быть доступен на ЗАПИСЬ: yt-dlp обновляет в нём ротируемые куки.
# Подробности — в README, раздел «Учётка YouTube (cookies)».
# YTDLP_COOKIES=/data/cookies.txt
# Необязательно: прокси для всех запросов yt-dlp — YouTube и SoundCloud,
# поиск, метаданные и сам аудиопоток. Пригодится, когда из сети сервера
# YouTube недоступен (Psiphon, свой SOCKS и т.п.).
# Схемы: http://, https://, socks5:// и socks5h:// (socks5h резолвит DNS на
# стороне прокси — обычно нужен именно он). Можно с логином и паролем:
# socks5h://user:pass@host:1080
# YTDLP_PROXY=socks5h://127.0.0.1:1080
# Где применять прокси: all — везде (по умолчанию), search — только поиск и
# метаданные, а сам аудиопоток качать напрямую. Второй вариант выручает, когда
# выходной IP прокси ловит от YouTube «Sign in to confirm you're not a bot»,
# а без прокси скачивание работает.
# YTDLP_PROXY_SCOPE=search
#
# Внимание: 127.0.0.1 внутри контейнера — это сам контейнер. Если прокси поднят
# на хосте, используйте socks5h://host.docker.internal:1080 и добавьте в
# compose.yml к extra_hosts строку "host.docker.internal:host-gateway".
# JS-рантайм для yt-dlp: YouTube требует исполнять свой JS, иначе извлечение
# деградирует и начинаются бот-проверки. В образе уже есть Node, поэтому по
# умолчанию используется он. Пустое значение отключает флаг.
YTDLP_JS_RUNTIME=node
# Необязательно: дополнительные --extractor-args, через ";".
# Помогает, когда YouTube не отдаёт форматы серверному IP:
# YTDLP_EXTRACTOR_ARGS=youtube:player_client=default,web_safari
# Необязательно: каталог с локальной медиатекой (смонтируйте том).
# LOCAL_MEDIA_DIR=/media/music
# Показывать клип в голосовом канале как демонстрацию экрана (только YouTube).
# Требует прав Video у бота и включённого видео в конфигурации инстанса.
# 360p по умолчанию из осторожности: кодирование видео заметно грузит CPU
# сервера. Можно поднять до 720 (VIDEO_WIDTH=1280, VIDEO_HEIGHT=720), если
# машина позволяет.
# Это лишь разрешение: сам показ включается тумблером в панели или командой
# !video, и по умолчанию выключен. При false тумблер в панели не показывается.
VIDEO_ENABLED=false
# Рамка, в которую вписывается кадр: пропорции клипа сохраняются, чёрные поля
# не добавляются. 640x360 выбрано из осторожности — видео грузит CPU.
VIDEO_WIDTH=640
VIDEO_HEIGHT=360
VIDEO_FPS=24
# camera — картинка внутри плитки бота; screen — отдельной плиткой,
# как демонстрация экрана.
VIDEO_SOURCE=camera
# Подстройка синхронизации, мс. Плюс задерживает видео, минус — торопит.
# Звук и картинка публикуются двумя дорожками, поэтому идеального совпадения
# «из коробки» не гарантируется.
VIDEO_SYNC_OFFSET_MS=0
DEFAULT_VOLUME=60
MAX_QUEUE_SIZE=500
# Сколько треков максимум добавит одна ссылка на плейлист или микс.
MAX_PLAYLIST_TRACKS=100
SEARCH_RESULT_LIMIT=10
# Через сколько секунд после ухода ПОСЛЕДНЕГО человека бот покидает голосовой
# канал (0 — не выходить никогда). Пока в канале кто-то есть, бот остаётся,
# даже если очередь давно закончилась.
EMPTY_TIMEOUT_SECONDS=120
# --------------------------------------------------------------- Доступ ----
# false — управлять может любой участник сервера.
# true — только владелец, ManageServer или роль DJ_ROLE_NAME.
REQUIRE_DJ_ROLE=false
DJ_ROLE_NAME=DJ
# true — позвать бота можно только в тот голосовой канал, где вы сами находитесь
# (и панель, и команды). false — разрешить выбирать канал вручную.
REQUIRE_LISTENER=true
# ----------------------------------------------------------------- Прочее ---
LOG_LEVEL=info
NODE_ENV=production