anc
Разбор по голосам: «Спикер 1», «Спикер 2»… на компьютере, без сети
- crates/voices: отпечаток голоса реплики (WeSpeaker ResNet34 через
sherpa-onnx, статические сборки под Windows и macOS) и разбиение снизу
вверх со средней связью. Из отпечатков вычитается средний по записи:
без этого у подкаста любые две реплики были похожи на 0,5–0,8.
- После распознавания реплики «Собеседников» и файла раскладываются по
голосам; микрофон — «Я». Отпечатки и реплики — в voices.json рядом с
записью: «Говорящих: N» пересобирает разбиение без новой расшифровки.
- Ход работы: этап «Разделяю по голосам» с процентом; по ходу
распознавания у реплик разбираемых дорожек ещё нет имени.
- Исправлено: файл без смены говорящего ложился одним абзацем 00:00:00
(подкаст 1 ч 52 мин — 170 КБ одной строкой). Абзац теперь — реплики
одного говорящего без пауз больше 1,5 с и не длиннее ~600 знаков.
У реплики появился конец (из ответа Whisper или до следующей).
- Модель (26 МБ) не в git: scripts/fetch-models.sh с проверкой SHA-256,
в сборку — ресурсом. Windows — со статической средой C (+crt-static,
явная libucrt): установщику не нужен VC++ Redistributable; crate-type
ядра — только rlib (cdylib нужен лишь мобильным платформам).
- Проверено на подкасте Kuji 230 (1 ч 52 мин, трое в студии): трое,
48 % / 36 % / 15 % речи, отпечатки 1184 реплик за ~3 мин.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
d0d9d7d
30 сентября 2026 г.
transcript.rs
//! Реплики расшифровки и то, как они ложатся в заметку.
//!
//! Говорящий сначала — дорожка звука: «Я» (микрофон), «Собеседники» (звук
//! компьютера) или файл целиком. Потом дорожки собеседников и файла
//! раскладываются по голосам (`speakers.rs`): «Спикер 1», «Спикер 2»… Людей
//! программа не узнаёт — только отличает один голос в записи от другого
//! (решение владельца 01.10.2026, см. README, «Согласие на запись»).
use Cow;
use ;
/// Заголовок раздела с расшифровкой в заметке.
pub const HEADING: &str = "Расшифровка";
/// Реплики одного говорящего склеиваются в абзац, только если между ними
/// пауза не больше этой…
const JOIN_GAP_MS: u64 = 1_500;
/// …и абзац ещё не длиннее этого. Иначе час одного голоса (лекция, файл
/// без разбора по голосам) ложился одним абзацем со временем 00:00:00.
const PARAGRAPH_CHARS: usize = 600;
/// Блок «Расшифровка» для заметки: абзац — реплика или несколько подряд
/// идущих реплик одного говорящего без пауз, со временем начала.
/// Реплика абзацем: «**00:03:21 Я:** текст».
/// Реплика по ходу расшифровки: у дорожек, которые потом разберутся по
/// голосам, имени ещё нет — только время и текст.