Skip to content

Repository files navigation

Записная книжка (Zapis)

Локальное десктопное приложение для работы с речью и текстом:

  • распознавание речи (ASR) — аудио/видео в текст с таймкодами на уровне слов;
  • LLM-постобработка — YouTube-описания, таймкоды, посты для Telegram, статьи, свободные вопросы к транскрипту (стриминг ответов в реальном времени);
  • озвучка текста (TTS) — превращение .txt в аудиокнигу (mp3/m4b) одним из пяти движков.

Всё работает локально — модели скачиваются в кеш при первом использовании. Облако нужно только для облачных TTS (Яндекс/Сбер) и LLM. Расшифровки и озвучки сохраняются между сеансами.

Возможности

Распознавание речи (ASR)backend/asr/

  • GigaAM v3 CTC + KenLM (T-one) — высокое качество для русского языка (по умолчанию).
  • faster-whisper — мультиязычная модель (en, ru, es, de, fr, …), грузится лениво при первом запуске.
  • Таймкоды на уровне слов с точностью до ~40 мс.
  • Диаризация — разделение записи по говорящим (необязательно, выключено по умолчанию). Работает с обоими движками: привязка идёт по таймкодам слов. Подписи попадают в UI, в TXT/SRT/VTT и в промпты LLM.

LLM-постобработкаbackend/llm/

  • 4 встроенных пресета (YouTube-описание, YouTube-таймкоды, Telegram-пост, Статья) + свободные вопросы к транскрипту.
  • SSE-стриминг ответов по словам.
  • Несколько профилей с fallback-цепочкой: порядок профилей = порядок попыток, порядок models[] внутри профиля = приоритет моделей.
  • Провайдеры: openai (любой OpenAI-совместимый endpoint — Azure, OpenRouter, Qwen, DeepSeek, Ollama, LM Studio) и anthropic.
  • Редактируемые промпты с разделением на system и user_template.

Озвучка текста в аудиокнигу (TTS)backend/tts/

Пять движков через общий интерфейс (initialize / get_status / list_speakers / synth):

  • Silero (по умолчанию) — офлайн на CPU, модель v4_ru, 5 голосов (aidar, baya, kseniya, xenia, eugene); понимает +-разметку ударений.
  • Piper — офлайн, выше качество, голоса rhasspy/piper-voices (напр. ru_RU-ruslan-medium), регулятор темпа (length_scale).
  • edge-tts (рекомендуемый «из коробки») — нейронные голоса Microsoft (ru-RU-DmitryNeural / ru-RU-SvetlanaNeural, 24 кГц), без ключа и регистрации.
  • Яндекс SpeechKit — облако, нужны api_key + folder_id.
  • Сбер SaluteSpeech — облако, нужны client_id / client_secret (обмениваются на access-token в рантайме).

Конвейер озвучки: загрузка .txt → разбиение на главы → нормализация чисел/дат/аббревиатур (rule-based через num2words или LLM с дисковым кэшем и защитой от «отсебятины») → расстановка ударений (ruaccent) → синтез → паузы между предложениями/абзацами/главами → сборка в mp3 / m4b (с главами) / m4a / wav. Прогресс стримится по SSE. Каждая озвучка сохраняется в историю.

Сбой облачного движка бросает CloudTtsError и прерывает книгу — pipeline не подменяет тишину молча. edge-tts ходит на неофициальный endpoint и иногда транзиентно отдаёт «No audio» → ретрай с backoff.

Экспорт и хранение

  • Расшифровки → TXT / SRT / VTT.
  • Озвучки → mp3 / m4b / m4a / wav.
  • Расшифровки и озвучки хранятся локально и доступны между сеансами (backend/transcripts.py, backend/tts_runs.py).

Архитектура

Zapis/
├── main.py                      # Desktop entry point (pywebview)
├── backend/
│   ├── main.py                  # FastAPI: маршруты ASR / LLM / TTS / расшифровки / экспорт
│   ├── config.py                # settings.json: чтение, валидация, кеш
│   ├── schema.py                # Pydantic-модели (app / asr / llm / prompts / tts)
│   ├── formats.py               # SRT / VTT / TXT
│   ├── transcripts.py           # сохранённые расшифровки (persistence)
│   ├── tts_runs.py              # история озвучек (persistence)
│   ├── asr/
│   │   ├── base.py              # Transcriber Protocol
│   │   ├── factory.py           # фабрика и переключение движков
│   │   ├── gigaam_engine.py     # GigaAM v3 CTC + KenLM
│   │   ├── whisper_engine.py    # faster-whisper (ленивая загрузка)
│   │   └── diarize.py           # диаризация: кто говорит (sherpa-onnx)
│   ├── llm/
│   │   ├── client.py            # AsyncOpenAI / AsyncAnthropic + fallback + SSE
│   │   └── prompts.py           # дефолты пресетов + сборка messages
│   └── tts/
│       ├── factory.py           # выбор движка по имени
│       ├── engine.py            # Silero (по умолчанию, офлайн)
│       ├── engine_piper.py      # Piper (офлайн, качество)
│       ├── engine_cloud_base.py # общий базис облачных движков
│       ├── engine_yandex.py     # Яндекс SpeechKit
│       ├── engine_sber.py       # Сбер SaluteSpeech
│       ├── engine_edge.py       # Microsoft edge-tts (без ключа)
│       ├── pipeline.py          # сборка аудиокниги (главы, паузы, экспорт)
│       ├── reader.py            # .txt → текст с разметкой глав
│       ├── chapters.py          # разбиение по главам
│       ├── chunker.py           # нарезка на фрагменты для синтеза
│       ├── normalize.py         # rule-based нормализация (num2words)
│       ├── normalize_cache.py   # кэш LLM-нормализации на диске
│       ├── stress.py            # ударения (ruaccent) перед синтезом
│       ├── assemble.py          # склейка PCM
│       ├── export.py            # mp3 / m4b / m4a / wav
│       ├── spool.py             # буферизация выходных файлов
│       └── errors.py            # CloudTtsError
├── frontend/
│   ├── index.html               # двухколоночный UI с табами
│   └── static/
│       ├── style.css            # тёмная/светлая тема через CSS-переменные
│       ├── app.js               # основной поток UI
│       ├── stream.js            # SSE через fetch + ReadableStream
│       ├── settings.js          # модалка настроек
│       ├── tts.js / tts.css     # вкладка «Озвучка»
├── settings.json                # пользовательские настройки
├── requirements.txt
├── build.ps1                    # сборка exe (Windows)
├── build.sh                     # сборка (Linux / macOS)
└── README.md

Источник истины для GigaAM

backend/asr/gigaam_engine.py — обёртка над GigaAM v3 CTC и алгоритм Longform-склейки. Логика синхронизирована с отдельным сервисом gigaam_tone (transcribe.py), чьё ASR-ядро является source-of-truth.

Процедура синхронизации при изменениях в gigaam_tone:

  1. Обновите gigaam_tone/transcribe.py (новая модель / правка алгоритма).
  2. Перенесите изменения в классы GigaAMCTC, LongformCTC, CTCDecoderWithLM, merge_ctc_log_probs_by_blank_sep, chunk_audio и т.п. в backend/asr/gigaam_engine.py.
  3. Прогоните smoke-тест транскрипции (раздел «Верификация»).

Установка

cd C:\Projects\Zapis
pip install -r requirements.txt

Дополнительно нужен ffmpeg в PATH — он используется для декодирования произвольных аудио/видео в 16 кГц mono.

GigaAM v3 — установка с GitHub

PyPI-версия пакета gigaam поддерживает только до v2. Для v3 пакет ставится прямо из репозитория Salute Developers — эта строка уже включена в requirements.txt (пин на коммит):

pip install --force-reinstall git+https://github.com/salute-developers/GigaAM.git

Если после pip install -r requirements.txt приложение пишет Model 'v3_ctc' not found — значит осталась старая PyPI-версия, выполните команду выше вручную с --force-reinstall.

Распознавание на видеокарте (GPU)

Движки считают на разных стеках, поэтому и устройство у них задаётся отдельно — asr.gigaam.device и asr.whisper.device. Не заданное значение наследуется из общего asr.device.

Движок Считает на GPU в готовом дистрибутиве
Whisper CTranslate2 возможен — нужен cuDNN 9 на машине
GigaAM torch нет: torch в сборке без CUDA, движок сам откатится на CPU
Диаризация sherpa-onnx нет: колесо с PyPI собрано без CUDA, всегда CPU

Задаётся в Настройки → ASR → Вычислительное устройство (общее плюс отдельные значения для каждого движка) или прямо в settings.json — только NVIDIA, CTranslate2 умеет исключительно CUDA:

"asr": { "device": "cpu", "whisper": { "device": "cuda" } }

Дополнительно нужны драйвер NVIDIA с поддержкой CUDA 12 и cuDNN 9. Сам cuDNN в exe не входит — колесо ctranslate2 везёт только диспетчер на 266 КБ, а движковые библиотеки (nvidia-cudnn-cu12 + nvidia-cublas-cu12) добавили бы к дистрибутиву больше гигабайта. Поставьте cuDNN 9 в систему либо положите его DLL рядом с Zapis.exe.

При "device": "auto" наличие видеокарты у Whisper определяется через ctranslate2.get_cuda_device_count(), а не через torch: torch в дистрибутиве собран без CUDA, и опрос через него всегда давал бы CPU.

Смена устройства из UI применяется сразу: движки пересоздаются, и следующая транскрибация уже пойдёт на новом устройстве (загруженная модель при этом теряется — но только когда устройство действительно поменялось, сохранение других настроек её не трогает). А вот правка settings.json руками, мимо приложения, по-прежнему требует перезапуска: файл перечитывается, но фабрика движков об этом не узнаёт.

Куда приложение ходит по сети

Веса моделей не входят в дистрибутив и качаются при первом использовании с нескольких независимых хостов — доступность одного ничего не говорит о другом:

Что Откуда Куда кладётся
GigaAM v3 cdn.chatwm.opensmodel.sberdevices.ru ~/.cache/gigaam
KenLM (T-one), Whisper, ruaccent, голоса Piper huggingface.co + *.xethub.hf.co ~/.cache/huggingface
Silero (TTS) github.com (torch.hub) ~/.cache/torch/hub
Модели диаризации (~34 МБ) github.com (релизы sherpa-onnx) cache/diarization рядом с приложением

⚠️ Крупные файлы с HuggingFace идут не с huggingface.co: huggingface_hub 1.x отдаёт их через Xet (cas-server.xethub.hf.co, transfer.xethub.hf.co). Если файрвол пропускает huggingface.co, но режет xethub.hf.co, скачивание молча зависает — метаданные приходят, а файл остаётся нулевого размера. Обходится переменной окружения:

$env:HF_HUB_DISABLE_XET = "1"    # качать напрямую по HTTPS с huggingface.co

Если модель не скачивается, приложение назовёт в ошибке конкретный хост и причину (блокировка, таймаут, перехват TLS). Полный traceback — в zapis.log рядом с исполняемым файлом, он пишется всегда на уровне DEBUG.

На машинах с корпоративным прокси, подменяющим сертификаты, проверка идёт через системное хранилище Windows/macOS (пакет truststore) — отдельно прописывать корневой сертификат не нужно.

Настройка

settings.json (поля можно править из UI: Настройки → ASR / LLM-профили / Промпты / Озвучка / Вид):

{
  "app":  { "title": "Записная книжка", "port": 8001, "theme": "dark" },
  "asr":  {
    "engine": "gigaam",
    "language": "ru",
    "gigaam":  { "version": "v3" },
    "whisper": { "model": "small", "cpu_threads": 0 },
    "diarization": {
      "enabled": false,
      "num_speakers": 0,
      "threshold": 0.5,
      "embedding_model": "wespeaker_en_voxceleb_resnet34_LM.onnx",
      "num_threads": 0,
      "window_shift_ratio": 0.3
    }
  },
  "llm": {
    "temperature": 0.3,
    "max_tokens": 4096,
    "profiles": [
      {
        "name": "openai",
        "api_provider": "openai",
        "base_url": "https://api.openai.com/v1",
        "api_key": "sk-…",
        "models": ["gpt-4o", "gpt-4o-mini"]
      }
    ]
  },
  "prompts": {
    "youtube_description": { "system": "", "user_template": "" },
    "youtube_timecodes":   { "system": "", "user_template": "" },
    "telegram_post":       { "system": "", "user_template": "" },
    "article":             { "system": "", "user_template": "" },
    "custom_system": "",
    "tts_normalize":       { "system": "", "user_template": "" }
  },
  "tts": {
    "engine": "silero",
    "language": "ru",
    "silero": { "speaker": "baya", "sample_rate": 48000 },
    "piper":  { "speaker": "ru_RU-ruslan-medium", "length_scale": 1.0 },
    "edge":   { "voice": "ru-RU-DmitryNeural" },
    "yandex": { "api_key": "", "folder_id": "", "voice": "alena" },
    "sber":   { "client_id": "", "client_secret": "", "voice": "Nazar" },
    "pauses": { "sentence": 300, "paragraph": 700, "chapter": 1500 },
    "normalize": { "use_llm": false },
    "accent":    { "enabled": true, "model_size": "tiny" },
    "export":    { "format": "mp3", "split_chapters": true, "bitrate": 128000 }
  }
}

Пустые поля в prompts.* означают «использовать встроенный шаблон». Секреты облачных TTS и LLM хранятся plaintext — приложение локальное и однопользовательское.

asr.whisper.cpu_threads — число потоков CPU для faster-whisper. 0 (по умолчанию) означает «по числу ядер»; собственный дефолт CTranslate2 — 4 потока независимо от железа, из-за чего на многоядерных машинах транскрибация шла втрое дольше, чем могла бы.

Диаризация: кто говорит

Необязательная функция, по умолчанию выключена (asr.diarization.enabled). Включается галочкой «Определять говорящих» на панели транскрипции или в Настройки → ASR.

Стек. Ровно тот же, что у pyannote/speaker-diarization-3.1: сегментация pyannote-segmentation-3.0 + эмбеддинги WeSpeaker ResNet34-LM. Отличие в источнике: берутся ONNX-зеркала из релизов sherpa-onnx, а не оригиналы с HuggingFace. Оригиналы лежат за гейтом — нужен аккаунт, принятие условий и токен, то есть на машине пользователя они бы просто не скачались. Лицензии позволяют: модель MIT, sherpa-onnx — Apache-2.0.

Считает на onnxruntime внутри sherpa-onnx: torch не участвует, всегда CPU. Модели (~34 МБ) качаются один раз в cache/diarization рядом с приложением — кнопкой «Скачать модели» в настройках или автоматически при первом запуске.

Как склеивается с расшифровкой. Движки ASR про диаризацию ничего не знают: они, как и раньше, отдают слова с таймкодами. Разметка накладывается сверху (backend/formats.py: apply_speakers) — каждое слово получает говорящего по максимальному перекрытию с репликой, сегменты режутся не только по паузам, но и по смене говорящего. Слова, попавшие в паузу между репликами, наследуют ближайшего по времени соседа. Поэтому диаризация одинаково работает и с GigaAM, и с Whisper.

Настройки:

Поле Смысл
num_speakers 0 — определить самому по порогу. Если число участников известно, лучше указать явно.
threshold Порог кластеризации при num_speakers: 0. Меньше — больше говорящих.
embedding_model Имя файла модели эмбеддингов из релизов sherpa-onnx (список — в backend/asr/diarize.py).
num_threads 0 — по числу ядер (собственный дефолт sherpa-onnx — один поток).
window_shift_ratio Шаг окна сегментации: главный регулятор «скорость против точности».

Замеры (4 vCPU Xeon 2.3 ГГц, синтетический диалог из двух голосов, 29 с):

Шаг окна Время Скорость Результат
0.1 (как в pyannote) 20 с 1.4× реального времени 2 говорящих, без ошибок
0.3 (по умолчанию) 7 с 4.3× 2 говорящих, без ошибок
0.5 5 с 6.1× кластеризация развалилась в одного говорящего

Отсюда умолчание 0.3: втрое быстрее при той же разметке. Порог 0.5 тоже проверен развёрткой — при 0.6 и выше два голоса схлопываются в один.

⚠️ Замеры сделаны на синтетической записи (голоса edge-tts). На четырёх синтетических голосах диаризатор объединял однополые пары: голоса одного TTS-движка акустически близки, у живых людей эмбеддинги расходятся сильнее. Перед тем как полагаться на функцию, прогоните её на своей реальной записи — и если участников известное число, задайте его явно.

Если диаризация не сложилась (нет сети, битая модель, пакет не установлен), расшифровка не теряется: текст возвращается без подписей, а причина показывается предупреждением над транскриптом. Терять минуты работы ASR из-за необязательной надстройки нельзя.

Пакет sherpa-onnx необязателен: без него функция просто не показывается в UI, всё остальное работает как раньше.

LLM: профили и fallback

  • Порядок профилей в массиве = порядок попыток. Если первый профиль возвращает ошибку до первого чанка ответа, движок переходит к следующему.
  • Внутри профиля порядок models[] — тоже приоритет (для одного URL пробуются разные модели по очереди).

Запуск

python main.py

Откроется окно pywebview. Модель GigaAM подгружается лениво при первой транскрибации (при первом запуске может занять несколько минут — скачивается с HuggingFace). Whisper и TTS-модели (Silero, ruaccent, голоса Piper) тоже грузятся при первом использовании соответствующей функции.

Использование

Расшифровка:

  1. Выберите движок (GigaAM для русского, Whisper — для прочих языков).
  2. Перетащите файл в зону загрузки.
  3. Нажмите «Транскрибировать», дождитесь результата (вкладка «Транскрипт»).
  4. Экспортируйте в TXT/SRT/VTT.

ИИ-обработка: перейдите на вкладку «ИИ-обработка», нажмите пресет или задайте свой вопрос — ответ стримится по словам.

Озвучка:

  1. Перейдите на вкладку «Озвучка», загрузите .txt (книгу/статью/расшифровку).
  2. Выберите движок и голос (для пробы без настройки — edge-tts).
  3. При необходимости включите LLM-нормализацию чисел/аббревиатур и расстановку ударений.
  4. Нажмите «Озвучить» — прогресс стримится по главам; результат сохранится в audiobooks/ и в истории озвучек.

Сборка

Платформа Команда Результат
Windows .\build.ps1 dist\Zapis.exe
Linux bash build.sh dist/Zapis
macOS (Apple Silicon) bash build.sh dist/Zapis.app

Скрипт ставит зависимости в локальный venv, ставит pyctcdecode без конфликтующих deps и собирает PyInstaller-бинарь. Дефолтный settings.json кладётся рядом только при первом билде — пользовательский не затирается.

Модели (GigaAM, KenLM, Whisper, Silero, ruaccent, голоса Piper) НЕ пакуются в бинарь — они скачиваются в кеш при первом использовании. Это держит размер дистрибутива в разумных пределах.

KenLM имеет готовые wheels для Linux/macOS. На Windows это C++-расширение: компилируется через MSVC (CI ставит microsoft/setup-msbuild) либо, без MSVC, движок откатывается на greedy-декодер.

В собранном приложении автодоустановка kenlm не выполняется: pip в дистрибутиве нет, а sys.executable там — сам Zapis.exe, так что попытка установки запустила бы вторую копию приложения. Если модуль не импортировался (не собран или на машине нет Visual C++ Runtime), декодирование идёт без языковой модели, с записью в zapis.log.

CI/CD

.github/workflows/build.yml собирает все платформы при пуше в main и при пуше тега v*:

  • Windows / Linux / macOS Apple Silicon — основные job'ы, определяют зелёный статус.
  • macOS Intel (macos-13)continue-on-error: собирается «по возможности», не блокирует статус и релиз (Intel Mac снят с продаж в 2020, сборка медленная и нестабильная).
  • При пуше тега v* артефакты автоматически аттачатся к GitHub Release (через softprops/action-gh-release). macOS-бандл .app запаковывается в zip (релиз принимает только файлы).

Верификация

  1. GigaAM v3 — взять русскоязычное .mp3 (1–3 мин), запустить транскрипцию, убедиться что текст корректный, экспортировать SRT, открыть в плеере.
  2. faster-whisper — переключить движок, выбрать язык en, загрузить английский .mp4. Первый запуск качает модель (small ≈ 500 MB).
  3. LLM-стриминг — настроить рабочий профиль, нажать «YouTube таймкоды»: текст должен появляться по чанкам.
  4. Fallback LLM — поставить первым профиль с заведомо нерабочим ключом, вторым — рабочий. Запрос должен пройти со второго.
  5. Custom-чат — задать «Сделай 5 ключевых тезисов» — ответ стримится.
  6. TTS (edge-tts) — на вкладке «Озвучка» выбрать edge-tts, загрузить .txt, «Озвучить» → проверить mp3/m4b в audiobooks/.
  7. TTS-нормализация — включить LLM-нормализацию на тексте с числами/датами, перегенерировать — числа должны произноситься словами.
  8. Диаризация — взять запись с двумя-тремя собеседниками, включить «Определять говорящих» (первый запуск качает ~34 МБ), проверить, что подписи в транскрипте меняются по репликам и попадают в TXT/SRT.

Публикации

Этот репозиторий использован в статье на Инфостарт

Инфостарт


About

No description, website, or topics provided.

Resources

Stars

12 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages