Skip to content

Transcriber

Services/Transcriber - Python CAN worker для live-транскрибации конференций. Он подключается к /can, принимает transcriber.start, входит в конференцию как сервисный receiver, подписывается на аудио renderer streams и отправляет обновления расшифровки в чат конференции.

Сервис ориентирован на Apple Silicon и mlx_whisper, потому что MLX дает практичный realtime path для локальной транскрибации.

Lifecycle

sequenceDiagram
    participant Core
    participant CAN as Transcriber CAN worker
    participant WS as CommandLoop
    participant RTP as RTP audio
    participant MLX as mlx_whisper
    participant Chat

    CAN->>Core: /can hello(role=transcriber)
    Core-->>CAN: job transcriber.start(auth.access_token, conference_tag)
    CAN->>WS: connect_request(access_token)
    CAN->>WS: connect_to_conference_request(tag)
    WS-->>CAN: device_connect microphone renderer
    CAN->>RTP: receiver_ssrc adjust packet
    RTP-->>CAN: plain RTP Opus 48 kHz
    CAN->>MLX: 3-5 sec PCM window
    MLX-->>CAN: text
    CAN->>Chat: delivery_messages transcript payload

Production job

{
  "job_type": "transcriber.start",
  "target_role": "transcriber",
  "required_capability": "speech_to_text",
  "payload": {
    "server_url": "https://join.videograce.ru",
    "conference_tag": "teamsink",
    "source": {
      "type": "conference_audio"
    },
    "model": "mlx-community/whisper-large-v3-turbo",
    "language": "ru",
    "window_sec": 4,
    "max_windows": 0
  }
}

Если payload.auth отсутствует, core пытается выпустить bearer JWT из service_accounts. По умолчанию используется service account с именем transcriber; альтернативное имя можно передать как payload.service_account.

После инъекции auth worker получает:

{
  "auth": {
    "type": "bearer",
    "access_token": "eyJ..."
  }
}

RTP assumptions

Текущий low-level media path повторяет путь recorder receiver:

  • каждый renderer socket принимает один аудиопоток;
  • поток приходит как plain RTP Opus 48 kHz;
  • шифрование и mux на этом участке не используются;
  • receiver_ssrc открывает RTP path через adjust packet;
  • author_ssrc связывает текст с конкретным remote microphone device;
  • окно транскрибации обычно 3-5 секунд.

Chat payload

Транскрибер отправляет структурированное сообщение:

{
  "type": "transcript",
  "version": 1,
  "job_id": "transcriber-...",
  "conference_tag": "teamsink",
  "partial": false,
  "speaker": {
    "client_id": 228800001,
    "device_id": 1071,
    "ssrc": 1530,
    "name": "Anton"
  },
  "text": "..."
}

GUID сообщения формируется стабильно:

transcript_{job_id}_{speaker_key}

Это позволяет клиенту обновлять один live transcript block, а не засыпать чат отдельными сообщениями каждые несколько секунд.

Локальная проверка

cd Services/Transcriber
python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt

CAN worker:

VG_CAN_URL=wss://join.videograce.ru/can \
VG_CAN_SERVICE_TOKEN=... \
python3 transcriber.py

Protocol-only self-test без MLX:

python3 transcriber.py --self-test \
  --conference-tag teamsink \
  --text "Тестовая расшифровка"

Инварианты

  • Transcriber не публикует свои microphone/camera devices.
  • Transcriber подключается к конференции как сервисный receiver и слушает remote microphone devices.
  • Пароль сервисного пользователя не должен попадать в CAN job; production path - bearer JWT от service_accounts.
  • CAN переносит только control plane, media идет напрямую по RTP.