◂ База знанийRYA.AEPre-Edit Word Precision — пословный транскрипт максимальной точности (forced alignment)обновлено 26 сен 2026
Монтаж интервью режется по словам: границы клипов, вставок, мультикам-переключений берутся из пословных таймкодов транскрипта. Whisper выдаёт слова через DTW по кросс-аттеншену — приблизительно, часто ставит слово раньше реального звука (особенно после пауз — в начале фраз, ровно там, где режем). Forced alignment (wav2vec2 CTC) сажает каждое слово на реальный акустический онсет; сетка модели 20 мс (= ½ кадра при 29.97p). На нашем материале (YTCH05) границы сдвинулись от whisper на медиану ~174 мс (до ~1 с в начале фраз) и стали точнее ложиться на энергетический онсет (проверено). Это преэдит-слой: между транскрибацией и сборкой.
📌 Когда применять. Любой проект, который режется по транскрипту: пре-эдит по «как надо», мультикам-word-sync (4.1), авто-подборки, VO Было→Стало. Вход: готовый {stem}.words.json (mlx/wordrole, текст+диаризация) + аудио клипа. Не нужно, если режете на глаз/по маркерам. Требует изолированный venv .venv_whisperx с torch·torchaudio·transformers (пакета whisperx инструмент не использует) и локальную модель wav2vec2-xlsr-russian в --model-dir — в рантайме ничего не качается (см. грабли).
📄 транскрипт words.json→ 🎧 16 kHz mono→ 🧠 wav2vec2 CTC emission→ 🎯 forced_align по словам→ 🗣 speaker по диаризации→ ⏱ .aligned.words.json (мс)

3.10 · 1Зачем: почему whisper-таймкоды «плывут»

Whisper (в т.ч. mlx-whisper large-v3) не слышит границы слов напрямую — он восстанавливает их из кросс-аттеншена декодера методом DTW. Это даёт приблизительный тайминг: сетка ~20 мс, но реальная ошибка границы слова ±50–100 мс, а на паузах/дыхании/числах слово «прилипает» к соседнему. Для чтения транскрипта норм, для покадрового реза — нет: рез по такому таймкоду садится в середину предыдущего слова или в паузу.

Forced alignment решает обратную задачу: текст уже известен (из whisper), надо лишь выровнять его по акустике. Акустическая CTC-модель (wav2vec2) выдаёт для каждого 20-мс кадра распределение по фонемам/буквам; алгоритм forced-align прокладывает единственный путь через эти кадры, совпадающий с текстом → каждая буква и слово садятся на реальный звук. Именно так работает WhisperX (whisper для текста + wav2vec2 для таймингов). Точность границы падает до ±10–20 мс — суб-кадрово.
whisper DTW — раннее размещениеwav2vec2 CTC — сетка 20 мсграницы на акустическом онсете
Проверено на YTCH05 (клип 0511, 133 слова). Forced-align сдвинул границы слов от whisper на медиану 174 мс (mean 398, p90 998 мс) — сдвиг систематически положительный: whisper ставит слова раньше звука, сильнее всего в начале фраз после пауз (напр. первое слово «вот»: whisper 0.00 с → онсет 0.66 с). Контроль по энергии аудио: доля попадания старта на энергетический рост у forced-align 1.37× против 1.20× у whisper — ложится точнее, особенно на границах фраз = точках реза. Не серебряная пуля: остаточная точность ~1 кадр (20 мс сетка + CTC-лаг), на отдельных словах внутри фразы выигрыш мал. Максимальная польза — на паузах/началах реплик.

3.10 · 2Точность, нужная Premiere

Premiere покадровый: таймкод осмыслен до длительности кадра, всё мельче округляется к ближайшему кадру. Значит целевая точность = длительность кадра. Стандарт хранения — миллисекунда (как в SRT ЧЧ:ММ:СС,ммм): она мельче любого fps.
fps1 кадрвывод
23.976 / 2441.7 / 41.67 мсms-точность с большим запасом
25 (PAL)40 мс—
29.97 (наш)33.37 мсforced-align ±20 мс = суб-кадр
50 / 59.94 / 6020 / 16.68 / 16.67 мстут whisper ±50–100 мс уже даёт промах на 1–3 кадра — forced-align обязателен
Итог: храним мс (3 знака, 0.001 с). Whisper на нашем материале мажет медиану ~170 мс (≈5 кадров), особенно в начале фраз; forced alignment садит слово на акустический онсет с сеткой 20 мс (≈½ кадра). Больше 3 знаков смысла нет — это ложная точность (акустика не даёт).

3.10 · 3Как работает forced alignment (wav2vec2 CTC)

Три шага под капотом torchaudio.functional.forced_align (тот же приём, что у WhisperX, но без пакета whisperx):
# 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 (jonatasgrosman/… на HuggingFace), лежит локально в ~/YTAI/environment/models/wav2vec2-xlsr-russian и грузится через transformers (Wav2Vec2Processor + Wav2Vec2ForCTC). Язык задаётся моделью, а не флагом. Мы переиспользуем готовый текст и диаризацию из words.json — гоняется только выравнивание, не повторная транскрибация.
Токенам, которые wav2vec2 не берёт (цифры «12», латиница, пунктуация), forced-align не даёт границу — им ставим fallback на whisper-таймкод, чтобы не терять слово.

3.10 · 4Пайплайн по этапам

1
Взять готовый транскрипт

Из конвейера транскрибации — {stem}.words.json: segments[] с текстом + speaker (диаризация pyannote). Это источник ТЕКСТА и РОЛЕЙ; тайминги перепишем.

2
Аудио → 16 kHz моно

ffmpeg -ac 1 -ar 16000 во временный WAV → numpy float32. wav2vec2 работает на 16 кГц.

3
Загрузить align-модель

Wav2Vec2Processor/Wav2Vec2ForCTC.from_pretrained(--model-dir) → wav2vec2-CTC для русского. Словарь символов модели (vocab) определяет, какие буквы выравниваются; blank = pad_token_id.

4
Forced alignment

По сегментам: emission считается по аудио-окну сегмента, текст разбивается на буквы из словаря модели, forced_align кладёт их на кадры; буквы собираются обратно в слова → {w, s, e, score}. score = средняя уверенность букв слова (0–1). Сегмент, где выравнивание упало или нет ни одной буквы из словаря, целиком уходит на whisper-таймкоды.

5
Вернуть роли

Каждому выровненному слову — speaker по перекрытию его середины с исходным диаризационным турном. Слова без выравнивания (числа/пунктуация) — fallback на whisper-таймкод.

6
Записать выход + отчёт точности

{stem}.aligned.words.json (мс) + .aligned.words.srt + .aligned.words.csv. В JSON — блок alignment: медианный/средний/p90 сдвиг границ vs whisper (доказательство, насколько подвинулось).

3.10 · 5Запуск инструмента

~/YTAI/scripts/999_extra/forced_align/forced_align_words.py — принимает медиа + готовый транскрипт, пишет высокоточную версию рядом с медиа (или в --out-dir).
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 \
    --model-dir ~/YTAI/environment/models/wav2vec2-xlsr-russian \
    --out-dir DIR --device cpu
Ключи: --media и --transcript обязательны; --model-dir (по умолчанию — путь выше), --out-dir, --base (имя выхода, по умолчанию stem медиа), --device (cpu). Ключа --language нет — язык задаёт модель; с ним команда падает на разборе аргументов. Выход перезаписывается по {base}. В консоль — отчёт точности: median / mean / p90 сдвига границ относительно whisper.
⚠️ Модель кладётся заранее: папка --model-dir должна содержать веса и словарь (config.json, vocab.json, веса) — сам инструмент ничего не скачивает, пустая папка = падение на загрузке. venv .venv_whisperx — отдельный от .venv_transcribe (там работает конвейер транскрибации): forced-align не должен трогать его зависимости.

3.10 · 6Формат выхода и импорт в Premiere

{
 "words": [ {"w":"мне", "s":5.560, "e":5.740, "score":0.98, "speaker":"Speaker 1"}, … ],
 "segments": [ … ], "diarization": { … },
 "alignment": { "aligned_words": …, "engine":"torchaudio.forced_align + wav2vec2-xlsr-ru",
                "median_shift_vs_whisper_ms": …, "mean_shift_vs_whisper_ms": …, "p90_shift_vs_whisper_ms": … },
 "align_engine": "wav2vec2 CTC forced alignment (torchaudio)"
}
s/e — секунды, 3 знака (мс). Импорт в Premiere: .aligned.words.srt → как транскрипт/caption-трек (Premiere ест SRT с мс), либо words.json читает наш UXP/скрипт сборки и ставит маркеры/резы прямо по w.s/w.e. Пословный .csv (word,start,end,score,speaker) — для проверки и таблиц.

3.10 · 7Грабли

① Изоляция venv. torch/torchaudio/transformers ставим ТОЛЬКО в .venv_whisperx. pip install в .venv_transcribe апнет numpy/tokenizers и сломает mlx/pyannote работающего конвейера.
② IPv6/DPI на pip. pip install (PyPI/files.pythonhosted.org) виснет по той же причине, что rclone/yt-dlp — режется IPv6/DPI. При этом модели с HuggingFace качаются по IPv4 (curl --ipv4 = HTTP 200). Установку пакетов проводит Winston через VPN/IPv4 (см. Метод); модель один раз скачивается в --model-dir заранее.
③ Русский = не латиница. Модель xlsr-russian обучена на кириллице напрямую — романизация не нужна. Для torchaudio-MMS пути потребовался бы uroman.
④ Числа/пунктуация/латиница wav2vec2 не выравнивает — этим словам fallback на whisper-таймкод (иначе слово выпадет). В score у них 0.
⑤ Сетка 20 мс. wav2vec2 — кадр 20 мс, интерполяция даёт мс, но физический предел точности ~20 мс (≈½ кадра @30p). Больше не выжать — и не нужно.
⑥ Мы НЕ транскрибируем заново. Текст и диаризация берутся из готового words.json; forced-align только двигает границы. Ошибку РАСПОЗНАВАНИЯ он не чинит — только тайминг.
⑦ Сегменты короче 50 мс пропускаются — их слов в выходе нет; сверяйте число слов с исходником.
Этап 3 · Pre-Editing — 3.10 Pre-Edit Word Precision · yt.rya.ae/kb