
Когда мы проектировали пайплайн автоматического заполнения EMR по итогам видеоконсультаций, исходная гипотеза была простой: Jitsi Meet — open source, документация есть, значит, подключить бота и получить транскрипт — задача на пару дней. На практике именно этот слой занял непропорционально много времени относительно своей “очевидности”.
В этой статье разберу, как устроена транскрипция в Jitsi Meet под капотом, почему это не “просто включить кнопку”, с какими конфигурационными нюансами пришлось столкнуться и как в итоге был выстроен пайплайн от видеозвонка до структурированного текста.
Контекст задачи
Система решала прикладную задачу: автоматически превращать doctor-patient видеоконсультации в структурированные данные для EMR. Полный пайплайн выглядел так:

Ключевой момент архитектурного решения: обработка шла post-consultation, а не в реальном времени. Это было намеренное упрощение: live-подсказки врачу во время звонка требуют совсем другого уровня latency, orchestration и отказоустойчивости. Для задачи автозаполнения карты после консультации асинхронная модель подходила лучше.
Архитектура транскрипции в Jitsi Meet
Прежде чем лезть в конфигурацию, важно понять, как Jitsi Meet вообще устроен с точки зрения транскрипции. Это не монолит с кнопкой “включить STT”, а набор сервисов с чёткими зонами ответственности.
Ключевые компоненты:
-
Jitsi Meet — фронтенд и сигнальный слой;
-
Jitsi Videobridge (JVB) — медиасервер, который роутит аудио- и видеопотоки между участниками;
-
Jicofo — конференс-менеджер, управляющий сессиями;
-
Prosody — XMPP-сервер и сигнальный транспорт;
-
Jigasi — SIP/XMPP gateway, который подключается к конференции как участник и обеспечивает транскрипцию;
-
STT backend — внешний сервис распознавания речи, в нашем случае Vosk.
Транскрипция в Jitsi Meet реализована через Jigasi, который выступает в роли обычного участника конференции. С точки зрения Jitsi Meet это просто ещё один peer. Jigasi принимает аудиопотоки от участников, проксирует их в STT-сервис и возвращает транскрипт обратно.
Именно поэтому задача “подключить бота” — это не задача на уровне Jitsi Meet API, а задача на уровне инфраструктуры.
Почему это нетривиально: SIP-слой
Первое, с чем приходится разбираться, — Jigasi исторически вырос из SIP gateway. Он умеет подключать к Jitsi Meet обычные телефонные звонки по протоколу SIP. Транскрипция — более поздняя фича, реализованная поверх той же архитектуры.
Это влечёт несколько следствий.
Во-первых, конфигурационный файл Jigasi — /etc/jitsi/jigasi/sip-communicator.properties — содержит SIP-параметры, даже если используется только транскрипция без реальной телефонии. Это вносит путаницу: при настройке транскрипции нужно понимать, какие секции относятся к SIP, а какие — к transcription pipeline.
Во-вторых, Jigasi подключается к конференции через XMPP (Prosody), используя заранее настроенный аккаунт. В старых конфигурациях для запуска транскрипции нужно было вручную приглашать пользователя jitsi_meet_transcribe в комнату через интерфейс Jitsi Meet. Для автоматизированного сценария это был явный UX-антипаттерн.
В более свежих конфигурациях transcriber подключается автоматически при включении транскрипции через UI или API. Но путь к этому — корректный конфиг на всех уровнях стека.
Конфигурация: что и где менять
Настройка транскрипции затрагивает три независимых конфигурационных слоя. Это важный момент: изменения в одном месте без изменений в другом просто не работают.
1. Jigasi: /etc/jitsi/jigasi/sip-communicator.properties
Основной конфигурационный файл Jigasi. Для включения транскрипции нужно раскомментировать и настроить следующие параметры:
# Включаем транскрипцию
org.jitsi.jigasi.ENABLE_TRANSCRIPTION=true
# Директория для сохранения транскриптов
org.jitsi.jigasi.transcription.DIRECTORY=/var/lib/jigasi/transcripts
# BASE_URL используется при ADVERTISE_URL=true — для генерации ссылок на транскрипты
org.jitsi.jigasi.transcription.BASE_URL=http://localhost/
org.jitsi.jigasi.transcription.jetty.port=-1
org.jitsi.jigasi.transcription.ADVERTISE_URL=false
# Форматы сохранения (на диск)
org.jitsi.jigasi.transcription.SAVE_JSON=false
org.jitsi.jigasi.transcription.SAVE_TXT=true
# Форматы отправки (в real-time участникам конференции через XMPP)
org.jitsi.jigasi.transcription.SEND_JSON=true
org.jitsi.jigasi.transcription.SEND_TXT=false
# STT backend: используем Vosk через WebSocket
org.jitsi.jigasi.transcription.customService=org.jitsi.jigasi.transcription.VoskTranscriptionService
org.jitsi.jigasi.transcription.vosk.websocket_url=ws://localhost:2700
Несколько практических нюансов:
-
SAVE_TXT=trueиSEND_JSON=true— это разные каналы. SAVE — сохранение итогового транскрипта на диск после завершения сессии. SEND — отправка промежуточных результатов участникам конференции в реальном времени через XMPP-сообщения. Для post-consultation pipeline нужен был именно SAVE. -
ADVERTISE_URL=falseозначает, что Jigasi не будет публиковать ссылку на транскрипт в чате конференции. Приtrueи корректно настроенномBASE_URLучастники получают ссылку на файл транскрипта в конце сессии. -
При необходимости поддержки нескольких языков можно задать
websocket_urlв виде JSON-маппинга:{"en": "ws://localhost:2700", "fr": "ws://localhost:2710"}— и поднять отдельные Vosk-инстансы для каждого языка.
2. STT backend: Vosk
Vosk — офлайн-совместимый STT-движок с неплохим качеством для английского языка. Для этого сценария он поднимался в Docker:
docker run -d -p 2700:2700 alphacep/kaldi-en:latest
Vosk-сервер принимает аудио через WebSocket, что и ожидает VoskTranscriptionService в Jigasi. Здесь важно убедиться, что порт 2700 доступен с хоста, где запущен Jigasi.
Альтернативные STT backend’ы, такие как Deepgram, Google STT или Whisper, тоже можно использовать через кастомные реализации TranscriptionService, но для этого уже нужен либо форк Jigasi, либо отдельный адаптерный слой.
3. Jitsi Meet frontend: конфиг хоста
Файл располагается по пути /etc/jitsi/meet/<hostname>-config.js. Без изменений в нём опция транскрипции не появится в UI, даже если Jigasi корректно настроен:
transcription: {
enabled: true,
useAppLanguage: true,
preferredLanguage: 'en-US',
disableStartForAll: false,
autoCaptionOnRecord: false,
},
Параметр useAppLanguage: true означает, что язык транскрипции определяется из настроек браузера участника. При false используется preferredLanguage. Для медицинского сценария с фиксированным языком консультации использовался вариант с явным preferredLanguage.
4. Перезапуск сервисов
После изменения конфигов нужно перезапустить все четыре сервиса:
sudo systemctl restart prosody.service
sudo systemctl restart jicofo.service
sudo systemctl restart jitsi-videobridge2.service
sudo systemctl restart jigasi.service
Prosody перезапускается первым, так как остальные компоненты зависят от XMPP-соединения при старте.
Проблемы, с которыми пришлось столкнуться
Проблема 1. Jigasi не подключается к конференции
Симптом: транскрипция запущена в UI, но в конференции не появляется второй участник — transcriber bot.
На практике самая частая причина — XMPP-аутентификация. Jigasi подключается к Prosody под отдельным аккаунтом. Если аккаунт не создан или пароль не совпадает с конфигом, бот просто не подключается.
Что проверять в первую очередь: логи Jigasi в /var/log/jitsi/jigasi.log. Именно там обычно видны XMPP-ошибки аутентификации.
Проблема 2. Транскрипт пустой или не сохраняется
Симптом: транскрипция запускается, промежуточные субтитры в UI появляются, но файл в DIRECTORY не создаётся.
Типичная причина — права на директорию. Jigasi запускается под пользователем jigasi, и директория /var/lib/jigasi/transcripts должна принадлежать ему:
sudo chown -R jigasi:jigasi /var/lib/jigasi/transcripts
Проблема 3. Низкое качество транскрипта
Vosk — рабочий офлайн-движок, но качество сильно зависит от модели. Образ alphacep/kaldi-en:latest содержит относительно лёгкую модель. Для медицинского контекста это чувствительно: терминология, названия препаратов, диагнозы и дозировки плохо распознаются общими моделями.
Рабочие варианты улучшения здесь были очевидны:
-
использовать более тяжёлые модели Vosk, например
alphacep/kaldi-en-streaming:latest; -
перейти на Deepgram с медицинским языковым профилем;
-
добавить постобработку транскрипта через LLM с доменной нормализацией.
В этом проекте именно LLM-слой частично компенсировал ошибки STT: модель восстанавливала корректные названия препаратов и структуры назначений из контекста.
Проблема 4. Синхронизация завершения сессии
Jigasi сохраняет итоговый транскрипт при завершении сессии. Но завершение сессии с точки зрения Jigasi — это момент, когда все участники покинули комнату. Для медицинского сценария этого было недостаточно: нужно было надёжно детектировать этот момент и запускать downstream-обработку.
Рабочий вариант — polling директории транскриптов в сочетании с webhook-интеграцией через Jitsi Meet Webhooks, если используется Jitsi as a Service, либо через кастомный Prosody-модуль для on-session-end событий.
Что происходит после получения транскрипта
Сырой .txt-транскрипт из Jigasi выглядит примерно так:
[00:01:23] Doctor: Расскажите, как давно появились симптомы?
[00:01:31] Patient: Примерно неделю назад. Сначала была температура, потом кашель.
[00:02:15] Doctor: Принимали что-нибудь? Ибупрофен, парацетамол?
[00:02:22] Patient: Только парацетамол, 500 миллиграмм три раза в день.
[00:03:10] Doctor: Хорошо. Давайте продолжим парацетамол и добавим амоксициллин...
Это обычный неструктурированный диалог. Для EMR из него нужно извлечь:
-
текущие жалобы;
-
анамнез симптомов;
-
назначения: препарат, дозу и режим;
-
рекомендации;
-
follow-up действия.
Именно здесь подключался LLM-слой с системным промптом, ориентированным на извлечение медицинских сущностей. Результат маппился на FHIR-совместимые структуры и передавался в React-форму на фронтенде для валидации врачом перед финальной записью в EMR.
Полностью автоматическая запись без ревью намеренно не использовалась: в медицинском контексте human-in-the-loop на последнем шаге обязателен.
Что стоит знать до начала
Несколько выводов, которые сильно сэкономили бы время, если бы были понятны заранее.
-
Jigasi — это SIP gateway с транскрипцией, а не наоборот. Это понимание сильно помогает при разборе конфигурации и логов. Многие параметры в
sip-communicator.propertiesотносятся к телефонии и не нужны для чистого транскрипционного сценария. -
Три конфига — три независимые точки отказа. Jigasi, Vosk и Jitsi Meet frontend должны быть согласованы. Частичная конфигурация часто не даёт явной ошибки — она просто молча не работает.
-
Логи — единственный надёжный источник правды. В первую очередь нужны
/var/log/jitsi/jigasi.log,/var/log/jitsi/jicofo.logи логи Prosody. -
Vosk подходит для MVP, но не для production-сценария с медицинской терминологией. Для реального клинического использования нужен либо специализированный STT, либо LLM-постобработка для нормализации результата.
-
Post-consultation модель проще, чем real-time. Если задача — заполнение карты, а не live-ассистент во время звонка, асинхронный пайплайн снимает значительную часть требований к latency и упрощает обработку ошибок.
Итог
Транскрипция в Jitsi Meet — это не фича с переключателем, а отдельный инфраструктурный контур: Jigasi, SIP/XMPP-конфигурация, внешний STT backend и согласованные настройки на нескольких уровнях. Для медицинского продукта это ещё и точка, где качество распознавания напрямую влияет на качество данных в EMR.
Автор: i_alakey


