Skip to content

Субтитры (распознавание речи)

Sapsan умеет распознавать речь в записанном потоке и писать её в отдельную субтитровую дорожку. Распознавание идёт на встроенном движке Whisper (whisper.cpp) поверх DVR-архива: фоновый воркер читает записанное аудио, распознаёт его и дописывает субтитровые кадры обратно в архив. Дальше субтитровая дорожка отдаётся в HLS и DASH как WebVTT.

Поскольку воркер читает из архива, а не тапает живой поток, сломать поток он не может: если распознавание не успевает, субтитры просто отстают и потом догоняют. Поэтому для стрима обязателен включённый DVR.

Предварительные требования

  1. Сборка с фичей whisper. Распознавание тянет нативную сборку whisper.cpp + ffmpeg, поэтому по умолчанию выключено:

    bash make release FEATURES=whisper # или: cargo build --release --features whisper

    На Apple Silicon добавьте Metal для ускорения на GPU (заметно быстрее): --features whisper,metal.

  2. Включённый DVR на стриме (воркер читает и пишет архив).

  3. Файл модели. Модель задаётся коротким именем (tiny, base, small, medium, large-v3); Sapsan резолвит имя в GGML-файл ggml-<имя>.bin в каталоге моделей:

    • WHISPER_MODELS_DIR, если задан, иначе ~/.cache/whisper-models.

    Скачайте модель один раз, например medium:

    bash mkdir -p ~/.cache/whisper-models curl -L -o ~/.cache/whisper-models/ggml-medium.bin \ https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium.bin

Конфигурация

Добавьте блок speech2text к стриму с включённым DVR:

streams:
  news:
    inputs:
    - rtsp:
        url: rtsp://admin:password@10.0.0.5/stream0
    dvr:
      root: /storage
    speech2text:
      model: medium        # tiny | base | small | medium (по умолчанию) | large-v3
      language: ru         # ISO-639-1; уберите для автоопределения
      audio_track: a1       # исходная аудиодорожка (по умолчанию первая)
      max_window_secs: 30   # предел окна распознавания, с (по умолчанию 30, макс. 120)

Поля:

Поле Смысл По умолчанию
model Модель коротким именем; резолвится в ggml-<имя>.bin medium
language Язык распознавания (ISO-639-1) авто
audio_track Идентификатор исходной аудиодорожки первая аудио
max_window_secs Верхний предел окна анализа 30

Валидация: стрим с speech2text, но без dvr, отклоняется; max_window_secs — в диапазоне 1..=120; language — код из 2–3 букв.

Изменения применяются на reload (SIGHUP / central): воркер перезапускается только при фактическом изменении блока speech2text.

Проверка офлайн (до включения на стриме)

CLI subtitle (собран с фичей whisper) гоняет ровно тот же продакшн-распознаватель на локальном файле, кладёт результат рядом и, если у файла уже есть субтитровая дорожка, считает метрику близости. Это самый быстрый способ подобрать модель и язык и оценить качество:

subtitle recognize movie.mkv --lang ru --model medium
# обработать только отрезок (например, 2 минуты с 25:00) для быстрой проверки:
subtitle recognize movie.mkv --lang ru --start-sec 1500 --limit-sec 120

Файлы рядом с исходным:

  • movie.subtitles.json — структурные кадры субтитров с полным провенансом (аудио-окно, модель, хеш, prompt, диагностика на реплику), достаточно, чтобы воспроизвести прогон;
  • movie.vtt — проекция в WebVTT;
  • movie.diff.txt — эталон против распознанного построчно (если у файла есть эталонная субтитровая дорожка).

Если у файла есть эталонные субтитры, CLI печатает WER (word error rate) и CER (character error rate) — глобально и по окнам. Важно: против независимо сделанной субтитровой дорожки WER меряет расхождение переводов, а не ошибку ASR — читайте его как относительный сигнал между прогонами и смотрите дифф глазами.

На вход годится любой .mkv/.mp4.

Проверка на стриме

После включения speech2text на DVR-стриме:

  1. В медиаинфо стрима появляется субтитровая дорожка. Проверьте HLS-мастер — в нём теперь есть рендиция EXT-X-MEDIA:TYPE=SUBTITLES, а варианты ссылаются на неё атрибутом SUBTITLES="subs":

    bash curl -s "http://server/streaming/<stream>/index.m3u8" | grep -i subtitle

    В DASH в манифесте появляется AdaptationSet с contentType="text".

  2. Откройте архив в плеере с поддержкой субтитров (или HLS/DASH-URL) и включите субтитровую дорожку. Субтитры появляются с отставанием, пока воркер догоняет живой край, затем идут следом.

  3. Следите за счётчиками распознавания в метриках (обработано секунд, размер щели / отставание, ошибки).

Воркер stateless — прогресс выводится из края субтитровой дорожки в архиве, — поэтому после рестарта он продолжает с той же щели, добивает субтитрами уже накопленный архив и никогда не дублирует кадр.

Как это работает

Единица работы воркера — «щель» между покрытием аудио и покрытием субтитров в архиве. Он читает аудио щели, декодирует его в моно PCM 16 кГц, гоняет Whisper по скользящим окнам (с гейтом тишины против галлюцинаций и ре-якорем по полным сегментам) и дописывает структурные кадры субтитров. Отдаваемый плеерам WebVTT — проекция этих кадров; сами кадры дополнительно хранят провенанс и диагностику Whisper для отладки.

Дальше