{stem}.words.json (mlx/wordrole, текст+диаризация) + аудио клипа. Не нужно, если режете на глаз/по маркерам. Требует изолированный venv .venv_whisperx с torch·torchaudio·transformers (см. грабли — установку через VPN делает Winston, чтобы не трогать venv работающего конвейера).mlx-whisper large-v3) не слышит границы слов напрямую — он восстанавливает их из кросс-аттеншена декодера методом DTW. Это даёт приблизительный тайминг: сетка ~20 мс, но реальная ошибка границы слова ±50–100 мс, а на паузах/дыхании/числах слово «прилипает» к соседнему. Для чтения транскрипта норм, для покадрового реза — нет: рез по такому таймкоду садится в середину предыдущего слова или в паузу.
wav2vec2) выдаёт для каждого 20-мс кадра распределение по фонемам/буквам; алгоритм forced-align прокладывает единственный путь через эти кадры, совпадающий с текстом → каждая буква и слово садятся на реальный звук. Именно так работает WhisperX (whisper для текста + wav2vec2 для таймингов). Точность границы падает до ±10–20 мс — суб-кадрово.
ЧЧ:ММ:СС,ммм): она мельче любого fps.
| fps | 1 кадр | вывод |
|---|---|---|
| 23.976 / 24 | 41.7 / 41.67 мс | ms-точность с большим запасом |
| 25 (PAL) | 40 мс | — |
| 29.97 (наш) | 33.37 мс | forced-align ±20 мс = суб-кадр |
| 50 / 59.94 / 60 | 20 / 16.68 / 16.67 мс | тут whisper ±50–100 мс уже даёт промах на 1–3 кадра — forced-align обязателен |
whisperx.align / torchaudio.functional.forced_align:
# 1. EMISSION — акустическая модель → лог-вероятности по кадрам emission = wav2vec2(audio_16k) # [T кадров × V символов], кадр = 20 мс # 2. TRELLIS + BACKTRACK — единственный путь через кадры, дающий ИМЕННО наш текст tokens = tokenize("мне было двенадцать лет") # текст из whisper path = forced_align(emission, tokens) # CTC: буква↔кадр # 3. FRAME→SEC — индекс кадра × (длит.аудио / число кадров) word_spans = group_tokens_to_words(path) # {word, s, e, score}Ключевое отличие от whisper: тайминг берётся из акустики напрямую (где реально звучит буква), а не из внимания декодера. Модель для русского —
wav2vec2-large-xlsr-53-russian (WhisperX подтягивает её сам по language_code="ru"). Мы переиспользуем готовый текст и диаризацию из words.json — гоняется только выравнивание, не повторная транскрибация.
Из конвейера транскрибации — {stem}.words.json: segments[] с текстом + speaker (диаризация pyannote). Это источник ТЕКСТА и РОЛЕЙ; тайминги перепишем.
whisperx.load_audio(media) — извлекает и ресемплит (ffmpeg внутри). wav2vec2 работает на 16 кГц.
whisperx.load_align_model(language_code="ru", device="cpu") → wav2vec2-CTC для русского (скачивается с HuggingFace один раз, ~1–2 ГБ; качается по IPv4 — см. грабли).
whisperx.align(segments, model, meta, audio, device) → каждый сегмент выровнен по акустике, на выходе words[] с {word, start, end, score}. score = уверенность выравнивания (0–1).
Каждому выровненному слову — speaker по перекрытию его середины с исходным диаризационным турном. Слова без выравнивания (числа/пунктуация) — fallback на whisper-таймкод.
{stem}.aligned.words.json (мс) + .aligned.words.srt + .aligned.words.csv. В JSON — блок alignment: медианный/средний/p90 сдвиг границ vs whisper (доказательство, насколько подвинулось).
~/YTAI/scripts/999_extra/forced_align/forced_align_words.py — принимает медиа + готовый транскрипт, пишет высокоточную версию рядом.
PY=~/YTAI/environment/.venv_whisperx/bin/python $PY ~/YTAI/scripts/999_extra/forced_align/forced_align_words.py \ --media clip.mp4 \ --transcript clip.words.json \ --language ru --out-dir DIR --device cpuИдемпотентно по
{stem}. В консоль — отчёт точности: медианный сдвиг Nмс, mean Mмс, p90 Pмс относительно whisper. Модель кэшируется после первого прогона (следующие клипы — без докачки).
.venv_whisperx — отдельный от .venv_transcribe (там работает конвейер транскрибации): forced-align не должен трогать его зависимости.{
"words": [ {"w":"мне", "s":5.560, "e":5.740, "score":0.98, "speaker":"Speaker 1"}, … ],
"segments": [ … ], "diarization": { … },
"alignment": { "median_shift_vs_whisper_ms": …, "model":"wav2vec2-CTC" }
}
s/e — секунды, 3 знака (мс). Импорт в Premiere: .aligned.words.srt → как транскрипт/caption-трек (Premiere ест SRT с мс), либо words.json читает наш UXP/скрипт сборки и ставит маркеры/резы прямо по w.s/w.e. Пословный .csv (word,start,end,score,speaker) — для проверки и таблиц.
.venv_whisperx. pip install в .venv_transcribe апнет numpy/tokenizers и сломает mlx/pyannote работающего конвейера.pip install (PyPI/files.pythonhosted.org) виснет по той же причине, что rclone/yt-dlp — режется IPv6/DPI. При этом модели с HuggingFace качаются по IPv4 (curl --ipv4 = HTTP 200). Установку пакетов проводит Winston через VPN/IPv4 (см. Метод), модели тянутся сами.ru обучена на кириллице напрямую (xlsr-russian) — романизация не нужна. Для torchaudio-MMS пути потребовался бы uroman.score у них 0.words.json; forced-align только двигает границы. Ошибку РАСПОЗНАВАНИЯ он не чинит — только тайминг.