Голосовые системы

py_faster_whisper: как звук проходит через систему

Статус: as-built по checkout, 16 сентября 2026 года.
Git revision: 21c85b0c7bc7a96497ce78ec2ead44b113f18db6 (main).
Основание: исходники, тесты, effective local configuration, Docker/DRS и CI этого репозитория.

Запущенные контейнеры, image digests, Swarm/Insight, production NATS-трафик и end-to-end работа внешних моделей в этом проходе не проверялись.

1. Что это за система

py_faster_whisper принимает речь и организует её обработку внешними моделями. Сам репозиторий не содержит модель Whisper: он принимает аудио, подготавливает его, вызывает LinTO ASR и сервисы диаризации, хранит состояние задания и возвращает текст с говорящими.

Работа разделена на три контура:

  1. Live bridge собирает короткие фразы из NATS-потока и возвращает текстовые события.
  2. Automatic file принимает целый файл и сам доводит его до готового результата.
  3. Staged file позволяет отдельно запускать подготовку, распознавание, диаризацию и объединение.

Все три контура запускаются из python -m src.stt_whisper_to_nats. Дальше они расходятся по разным моделям состояния и способам выдачи результата.

VERIFIED / SOURCE: src/stt_whisper_to_nats.py:1615-1688, src/webui/server.py, src/webui/staged_jobs.py.

2. Как читать доказательства

Метка Что она означает
VERIFIED / SOURCE Связь подтверждена кодом, тестом или effective configuration этого checkout.
VERIFIED / RUNTIME (local) Поведение наблюдалось при локальном запуске. Это не доказательство production runtime.
INFERRED Вывод следует из нескольких источников, но не подтверждён прямым test/trace.
UNKNOWN Доказательств недостаточно или нужный слой не проверялся.

Исходники, pipeline, образ, desired stack, запущенный runtime и конкретный пользовательский сценарий — разные уровни доказательств. Успех на одном уровне не подтверждает следующий.

3. Где проходит граница репозитория

3. Где проходит граница репозитория
3. Где проходит граница репозитория · Открыть в полном размере
Исходный Mermaid
flowchart LR
    Live[Live producer] -->|PCM16 через NATS| Repo[py_faster_whisper]
    User[Человек / HTTP-клиент] -->|медиафайл| Repo
    Repo -->|multipart HTTP| ASR[LinTO ASR]
    Repo -->|аудио + metadata| Diar[Diarization backend]
    Repo -->|state + artifacts| FS[(Local filesystem)]
    Repo -->|progress + result| NATS[(NATS)]
    Repo -->|HTTP snapshot| User

Как читать схему:

  • Live producer принадлежит соседней системе и передаёт короткие PCM16-пакеты.
  • Репозиторий управляет порядком действий: готовит аудио, вызывает модели и собирает результат.
  • LinTO и diarization backend выполняют тяжёлую модельную работу за HTTP-границей.
  • Filesystem хранит файловые задания; NATS переносит live-аудио и события.
  • Человек или HTTP-клиент видит состояние задания и готовый текст через WebUI/API.

WebRTC ingress, NATS infrastructure, LinTO, внешние H100 routes, registry и Swarm находятся за этой границей. Поэтому /health = ok у WebUI подтверждает процесс, но не готовность моделей.

VERIFIED / SOURCE: src/stt_whisper_to_nats.py:83-145, src/webui/server.py:1075-1194, stack/stack.drs:12-70.

4. Один entrypoint, три контура работы

4. Один entrypoint, три контура работы
4. Один entrypoint, три контура работы · Открыть в полном размере
Исходный Mermaid
flowchart TB
    Main[_main_async] --> Live[SttWhisperToNatsService]
    Main --> WebUI[run_webui / create_app]
    WebUI --> Mode{WEBUI_PAGE_MODE}
    Mode -->|diarize / transcribe| Auto[FileJobWorkerService]
    Mode -->|staged / summary / bench / solution / case| Staged[StagedFileJobService]
    Live --> Text[короткие фразы в NATS]
    Auto --> Result[готовый file job]
    Staged --> Steps[повторяемые шаги и artifacts]

Блоки схемы:

  • _main_async читает конфигурацию и решает, запускать live bridge, WebUI или оба процесса.
  • SttWhisperToNatsService владеет буферами живого аудио и закрывает фразы по VAD/тайм-ауту.
  • FileJobWorkerService выполняет полный файловый процесс без ручного управления шагами.
  • StagedFileJobService работает с уже сохранёнными промежуточными результатами.
  • WEBUI_PAGE_MODE выбирает нужный контур на HTTP-границе.

Эти контуры используют общие адаптеры LinTO, диаризации и transport, но по-разному хранят состояние. Поэтому их полезно разбирать отдельно.

VERIFIED / SOURCE: src/stt_whisper_to_nats.py:83-145,586-745,1615-1688, src/webui/server.py:1075-1194.

5. Контур 1: Live bridge

Роль

Live bridge превращает непрерывный поток PCM16 в отдельные текстовые фразы. Он нужен, когда результат должен появляться во время разговора, а не после загрузки целого файла.

Роль
Роль · Открыть в полном размере
Исходный Mermaid
sequenceDiagram
    participant P as Live producer
    participant N as NATS
    participant B as Live bridge
    participant V as Silero VAD
    participant L as LinTO
    participant C as Consumer

    P->>N: stream.{token}: PCM16 frame
    N->>B: binary wire packet
    B->>V: накопленный buffer 16 kHz
    V-->>B: speech timestamps
    B->>N: pending + phrase_id
    B->>L: temporary WAV
    L-->>B: text
    B->>N: result / error
    N-->>C: phrase event

Что происходит по блокам:

  • Producer отправляет metadata и PCM16 samples в inference.whisper.stream.*.
  • Bridge ведёт отдельный PhraseStreamState для каждого output subject.
  • Silero VAD находит участки речи и помогает определить момент закрытия фразы.
  • LinTO получает временный WAV и возвращает текст.
  • NATS consumer получает pending, затем result или error с тем же phrase_id.

Ограничение

Буфер живёт в памяти процесса. После restart незавершённая фраза не восстанавливается. Временный WAV удаляется в finally после обращения к LinTO.

VERIFIED / SOURCE: src/stt_whisper_to_nats.py:83-438, src/utils/wire_packet.py, src/utils/silero_onnx_vad.py.

6. Контур 2: Automatic file

Роль

Automatic file нужен для простого сценария: человек загружает файл и ждёт готовый текст. Runner сам проходит подготовку, ASR, диаризацию, объединение и cleanup.

Роль
Роль · Открыть в полном размере
Исходный Mermaid
sequenceDiagram
    participant U as Browser / API
    participant W as WebUI
    participant R as File runner
    participant S as FileJobStore
    participant L as LinTO
    participant D as Diarization

    U->>W: POST /api/file-transcribe
    W->>R: start_job(job_id, session_id)
    R->>S: source + starting state
    par Распознавание
        R->>L: chunks /transcribe
        L-->>R: words + timestamps
    and Разделение говорящих
        R->>D: audio artifact /diarize
        D-->>R: speaker segments
    end
    R->>R: words + segments → turns
    R->>S: transcript + combined result
    U->>W: GET /api/file-job
    W-->>U: state + result

Что происходит по блокам:

  • WebUI проверяет вход, создаёт job_id и передаёт работу runner.
  • Runner нормализует звук в mono PCM WAV 16 kHz и режет длинную запись на перекрывающиеся chunks.
  • LinTO возвращает слова с метками времени; без них нельзя надёжно назначить говорящих.
  • Diarization backend возвращает интервалы говорящих на той же временной шкале.
  • Runner сопоставляет слова и интервалы, строит turns и сохраняет результат в FileJobStore.

Ветка ошибки

Если пользователь запросил диаризацию, а backend недоступен, WebUI может продолжить транскрипцию без говорящих. Причина сохраняется в diarization_fallback_reason; отсутствие speakers не выдаётся за полный результат.

VERIFIED / SOURCE: src/webui/server.py:603-793, src/stt_whisper_to_nats.py:668-1542, src/webui/file_mode.py:839-1253.

7. Контур 3: Staged file

Роль

Staged file разделяет дорогую обработку на повторяемые шаги. Это полезно для сравнения моделей и настроек на одном аудио: файл не нужно загружать и нормализовать заново.

Роль
Роль · Открыть в полном размере
Исходный Mermaid
flowchart LR
    Upload[Upload] --> Prepare[Prepare]
    Prepare --> T[Transcribe]
    Prepare --> D[Diarize]
    T --> TT[(transcription.json)]
    D --> DD[(diarization.json)]
    TT --> C[Combine]
    DD --> C
    C --> R[(combined.json)]

Что происходит по блокам:

  • Prepare сохраняет исходник и создаёт рабочее аудио.
  • Transcribe получает слова с timestamps и записывает transcription.json.
  • Diarize получает speaker segments и записывает diarization.json.
  • Combine читает оба сохранённых результата и не вызывает модели повторно.
  • combined.json становится итогом, который WebUI возвращает через job snapshot.

Combine остаётся заблокирован, пока не готовы обе ветки. Один активный step защищён локальной блокировкой; optional HTTP lock добавляет lease между процессами и страницами.

VERIFIED / SOURCE: src/webui/staged_jobs.py:146-540, src/webui/job_lock.py, tests/test_diarization.py.

8. Как живут данные файлового задания

8. Как живут данные файлового задания
8. Как живут данные файлового задания · Открыть в полном размере
Исходный Mermaid
flowchart LR
    Input[input.ext] --> Normalize[normalized.wav]
    Normalize --> Chunks[chunks/*.wav]
    Chunks --> Words[transcription.json]
    Input --> Audio[diarization_input.m4a]
    Audio --> Segments[diarization.json]
    Words --> Merge[Attribution + turns]
    Segments --> Merge
    Merge --> Combined[combined.json]
    State[state.json] -. tracks .-> Normalize
    State -. tracks .-> Words
    State -. tracks .-> Segments
    State -. tracks .-> Combined

Разбор данных:

  • input.ext — исходный upload; он определяет идентичность задания.
  • normalized.wav и chunks/ — рабочие данные для ASR.
  • diarization_input.m4a или WAV — аудио, подготовленное для backend диаризации.
  • transcription.json и diarization.json — две независимые ветки результата.
  • combined.json соединяет текст и говорящих; state.json хранит progress и признаки готовности.

Корневой каталог по умолчанию: локальный источник<job_id>/. Automatic job планирует удаление примерно через 30 секунд после terminal completion. Сохранность staged artifacts зависит от runtime mount и cleanup policy; production retention остаётся UNKNOWN.

VERIFIED / SOURCE: src/webui/file_mode.py:631-837, src/stt_whisper_to_nats.py:1491-1510.

9. Где искать реализацию

src/
├── stt_whisper_to_nats.py      # entrypoint, live bridge, automatic file runner
├── webui/
│   ├── server.py               # HTTP routes и выбор runner
│   ├── file_mode.py            # store, audio, chunks, LinTO client
│   ├── staged_jobs.py          # Prepare/Transcribe/Diarize/Combine
│   ├── job_lock.py             # optional shared lock
│   ├── diagnostics.py          # dependency checks
│   └── static/                 # browser UI
├── diarization.py              # backend client, attribution, turns
├── model_transport.py          # direct/CONNECT transport
├── *_diarization_server.py     # pyannote, sherpa, Sortformer
└── utils/                      # wire packet, VAD, audio helpers

tests/                          # contracts и orchestration branches
docker/                         # runtime images и local composition
stack/                          # desired legacy CPU deployment

Эта карта отвечает на вопрос «куда идти». server.py владеет HTTP-границей, runners — порядком действий, file_mode.py — рабочими файлами и LinTO client, diarization.py — назначением говорящих, model_transport.py — способом HTTP-доставки.

10. Два выборочных call tree

Call tree показывает реализацию ветки. Временной порядок участников уже показан sequence diagrams выше.

Live path

main
└── _main_async
    └── SttWhisperToNatsService.run
        └── _handle_message                 # NATS callback
            └── _process_message
                └── _check_stream_buffer
                    ├── _get_timestamps     # Silero VAD
                    └── _transcribe_and_publish
                        ├── transcribe_http_text
                        └── _publish_json   # NATS result

File path

run_webui
└── create_app
    └── POST /api/file-transcribe
        └── _file_http_handler
            └── FileJobWorkerService.start_job
                └── _process_job
                    ├── write_normalized_audio_wav
                    ├── transcribe_http_json
                    ├── diarize_http_json
                    ├── attribute_words / build_speaker_turns
                    └── FileJobStore writes + NATS events

Обе ветки сходятся на HTTP-адаптерах моделей, но расходятся по состоянию: live хранит буфер в памяти, file пишет каталог job.

11. Контракты, которые нельзя менять локально

Граница Контракт Что проверить при изменении
Browser → WebUI POST /api/file-transcribe, /file-prepare, /file-{step}-step form fields, page mode, reset, snapshot
WebUI polling GET /api/file-job terminal/error states и rendering
Producer → live bridge inference.whisper.stream.*, binary wire packet producer и parser
Bridge → consumer inference.whisper.text.*, pending/error/result JSON correlation и terminal event
Runner → observers *.file.event.*, *.file.diar* job/session fanout
Runner → LinTO multipart file; file mode ждёт word timestamps parser, chunk merge, attribution
Runner → diarizer audio + job metadata → segments progress и domain mapping

HTTP связывает caller и response во времени. NATS разделяет producer и consumer границей сообщения. Файлы связывают повторные шаги через сохранённое состояние. Похожая ошибка в UI требует разных проверок для каждой границы.

VERIFIED / SOURCE: src/webui/server.py:1153-1178, src/webui/file_mode.py:285-366,1150-1253, src/model_transport.py.

12. Три backend диаризации

Backend Что получает Что отдаёт Особенность runtime
pyannote multipart audio + hints segments, progress HF access, lazy load, CPU route в desired stack
sherpa-onnx multipart audio speaker segments CPU pipeline, ffmpeg normalization, model cache
Sortformer multipart audio stitched segments NeMo/Triton, окна для длинного аудио

Внешний HTTP shape похож, но backend нельзя считать взаимозаменяемым только по наличию /diarize. Desired stack/stack.drs направляет bridge на локальный pyannote; sherpa и Sortformer объявлены как отдельные сервисы без автоматического переключения.

VERIFIED / SOURCE: src/*_diarization_server.py, src/diarization.py, stack/stack.drs:53-70.

13. Что происходит при сбое

Сбой Наблюдаемый результат Где искать
NATS недоступен live bridge не стартует; file HTTP может работать без live events entrypoint, NATS connection
LinTO не отвечает transcription получает error; готового текста нет file_mode.py, live publish path
Diarization недоступна возможен transcript-only результат с явной причиной server.py, runner state
Proxy route настроен, но не работает запрос не падает скрыто на direct transport model_transport.py
Job lock недоступен остаётся только local one-active-task guard job_lock.py, runners
Процесс перезапущен live buffer теряется; file recovery зависит от сохранённых artifacts live state, FileJobStore

Здесь нет общего fallback-механизма. Каждый контур сохраняет собственную семантику ошибки, поэтому диагностика начинается с границы, на которой пропал результат.

14. Как исходники доходят до работающей системы

14. Как исходники доходят до работающей системы
14. Как исходники доходят до работающей системы · Открыть в полном размере
Исходный Mermaid
flowchart LR
    S[Source] --> P[Pipeline]
    P --> I[Image]
    I --> D[Desired DRS]
    D --> R[Running tasks]
    R --> B[Named behavior]

Состояние ступеней:

  • Source — entrypoints, contracts и tests проверены в 21c85b0.
  • Pipeline — build/push описан; job с тестами закомментирован.
  • Image — digest конкретной сборки не проверялся.
  • Desired DRS — proxy, bridge и три sidecar описаны в stack/stack.drs.
  • Running tasks — Swarm health/update state не проверялись.
  • Named behavior — production live/file smoke не выполнялся.

Desired stack подключается к внешней сети web-rtc-komaroff_default. Deploy wrapper объединяет otlp.drs + stack.drs и вызывает Dry Stack с --prune; это описание намерения deployment, а не доказательство текущего runtime.

VERIFIED / SOURCE: .gitlab-ci.yml, docker/build-images.sh, stack/stack.drs, stack/deploy.sh. Остальные слои помечены выше.

15. Что защищают тесты

16 сентября 2026 года выполнено:

.venv/bin/python -m unittest discover -s tests -v

Результат: Ran 115 tests in 0.943s — OK. Предупреждения aiohttp.NotAppKeyWarning не привели к падениям.

Проверены transport, WebUI routes/modes, file state, staged steps, locks, diarization parsing/attribution, diagnostics, pyannote helpers и Sortformer stitching. Этот прогон не подтверждает реальные модели, production NATS routing, image/deployment state, cold/warm readiness и путь browser → model → result.

VERIFIED / RUNTIME (local): локальный unittest run. CI сам по себе эти тесты сейчас не запускает — job в .gitlab-ci.yml закомментирован.

16. Риски и область влияния

Причина Последствие Где проявится
README.md называет stack/stack.drs пустым, хотя в нём 5 сервисов разработчик неверно определит deployment ownership onboarding, delivery
.version=0.0.20, src/.version=0.0.19 разные части могут сообщать разные версии image labels, metadata
CI tests job выключен build/deploy не обязан выполнить 115 тестов regression confidence
Job state по умолчанию в /tmp сохранность зависит от mount restart, staged jobs
Один active job/step на процесс масштабирование требует общей coordination model concurrent files
Оркестрационные модули занимают 1000–1688 строк изменение контракта затрагивает несколько задач review, regression surface
/health проверяет только процесс зелёный route не означает готовую модель readiness, diagnostics

Таблица не требует немедленного рефакторинга. Она показывает, где локальная правка нуждается в более широкой проверке.

17. Если нужно изменить систему

Задача Начать здесь Проверить до завершения
WebUI layout или текст src/webui/static/, server.py page modes и browser rendering
Upload/job API src/webui/server.py snapshot, reset, automatic/staged tests
Chunks или timestamps src/webui/file_mode.py overlap merge и LinTO JSON
Speaker attribution src/diarization.py midpoint/overlap и turns tests
Staged workflow src/webui/staged_jobs.py artifacts, invalidation, lock, cancel
Live phrase behavior SttWhisperToNatsService wire packet, VAD, events, consumer
Proxy/model route model_transport.py, bench.py fail-closed tests и diagnostics
Deployment stack/*.drs, .gitlab-ci.yml image → desired → running → smoke

Сначала назовите наблюдаемый эффект и изменяемый контракт. Затем ищите файл. Такой порядок не даёт ограничиться одной функцией там, где меняются producer, consumer или runtime route.

18. Что остаётся неизвестным

  1. Какие image digests реально запущены?
  2. Совпадают ли runtime env, subjects и routes с DRS?
  3. Какие backend диаризации доступны и прогреты сейчас?
  4. Какой storage mount используется для file jobs и какова retention policy?
  5. Есть ли production job lock service?
  6. Каковы latency/error profile для live phrase и больших файлов?
  7. Какая version file считается canonical?

Эти вопросы нельзя закрыть чтением исходников. Для ответа нужны CI/image/Insight/NATS/browser evidence.

19. Короткий маршрут нового разработчика

  1. Начните с разделов 1–4 и выберите один из трёх контуров.
  2. Пройдите его sequence diagram от входа до наблюдаемого результата.
  3. Найдите соответствующий call tree и implementation anchors.
  4. Выпишите контракт, который изменится: HTTP, NATS, artifact или env.
  5. Проверьте tests обеих сторон границы.
  6. Для delivery отдельно докажите image, desired DRS, running state и named behavior.

20. Claim ledger

Утверждение Статус Evidence
Один entrypoint запускает live bridge и/или WebUI VERIFIED / SOURCE src/stt_whisper_to_nats.py:1615-1688
Live path использует NATS → VAD → LinTO → NATS VERIFIED / SOURCE src/stt_whisper_to_nats.py:83-438
File path создаёт normalized WAV, chunks и timed transcript VERIFIED / SOURCE src/webui/file_mode.py:839-1253
ASR и diarization могут выполняться параллельно VERIFIED / SOURCE src/stt_whisper_to_nats.py:914-947
Staged Combine не вызывает модели повторно VERIFIED / SOURCE + tests src/webui/staged_jobs.py, tests/test_diarization.py
Proxy path не делает silent direct fallback VERIFIED / SOURCE + tests src/model_transport.py, tests/test_model_transport.py
Desired stack использует local pyannote CPU VERIFIED / SOURCE stack/stack.drs:53-64
Production соответствует desired stack UNKNOWN runtime evidence не собиралось
End-to-end обработка реального файла сейчас работает UNKNOWN named smoke не выполнялся

Исходник

Скачать Markdown · К материалам исследования