◂ База знанийRYA.AEMulticam Word-Sync — синхрон дорожек после съёмки по транскриптамобновлено 26 сен 2026
После мультикам-съёмки клипы нескольких камер и рекордеров надо положить на один таймлайн в синхроне. Часам устройств верить нельзя: они сбиты на минуты и часы, а имена файлов рекордеров врут после рестарта. Единственное, что у всех дорожек общее — сама речь. Раз мы всегда транскрибируем всё с пословной точностью (.words.json, тайминги до миллисекунд), эти транскрипты и становятся якорем синхрона.
📌 Когда применять. Любой съёмочный день с 2+ источниками (камеры, петличка, рекордер), который надо собрать в мультикам-секвенцию: студия на 4 камеры + петля, интервью на 2 камеры без петли, подкаст с внешним рекордером. Требования: пословные транскрипты всех источников + живой creation_time в контейнерах. Не нужно: для одной камеры (линейная сборка) и когда есть общий таймкод (jam-sync).
🎙 .words.json→ 🧩 n-грамм голоса→ 🕸 граф часов→ ✅ проверки→ 🌉 мост дня → ingest→ 🎯 4.2 · 4.3→ 🖥 UXP Build→ ⚖️ Verify Sync

4.1 · 1Почему не creation_time и не имена файлов

На одном реальном дне (YTUVI, 4 камеры + DJI-петля) часы четырёх камер были сбиты на разные величины — от +5м49с до +22м00с чистой ошибки кварца (Sony пишет UTC честно, но сам кварц никто не выставлял). Время в именах DJI-файлов после рестарта рекордера врало на ~9.5 минут. На соседнем дне у камеры стоял 2027 год. Wall-clock-сборка по метаданным промахивается на минуты; аудио-кросскорреляция без опоры тонет в дублях и шуме. Речь же одна на всех — и у неё есть точные пословные тайминги.
кварц врёт по-разномуимена файлов врут после рестартаречь = общий якорь

4.1 · 2Модель: граф часов

Неизвестных мало, измерений много — решаем как систему.

· У каждой камеры один устойчивый (но сбитый) кварц: истина = creation_time + Δ_cam. Одно Δ на камеру на день — за смену дрейф кварца незаметен (<1с).
· Каждый кусок петли/рекордера k стартует в неизвестный момент T_k (имя файла — только начальное приближение).
· Речевое выравнивание клипа i по куску k даёт измерение r_ik — позицию начала клипа на таймлайне куска: creation_time_i + Δ_cam(i) ≈ T_k + r_ik.

Все Δ и T решаются итерированными медианами (робастно к выбросам), затем trimmed refit: рёбра с резидуалом >5с выкидываются, граф перерешивается. Рамка привязывается к большинству имён файлов — лжецы всплывают как поправки-меньшинство. Клипы без речи (макро, паузы петли) размещаются по часовой модели ct + Δ_cam — она уже откалибрована остальными клипами.

4.1 · 3Выравнивание пары и защита от дублей

N-грамм голосование (Hough-стиль). Слова нормализуются (lower, без пунктуации, ё→е), триграммы клипа ищутся в куске; каждое совпадение голосует оффсетом t_chunk − t_clip. Плотный кластер голосов = кандидат-совпадение; медиана кластера = измерение. ASR-ошибки не мешают: голосуют только совпавшие триграммы, а их тысячи.
Дубли — главный враг. В сценарном шоу один текст звучит несколько раз за день; «уверенный» матч цепляется за чужой дубль, и без защиты граф разъезжается на минуты. Защита: кластеры-кандидаты конкурируют по клип-локальным спанам слов. Перекрываются спаны = альтернативные гипотезы одних и тех же слов (эхо дубля) → выживает сильнейший. Спаны раздельны = клип реально пересекает два куска → оба ребра валидны. На один кусок клип даёт максимум одно ребро (линейный клип имеет один оффсет). Уцелевшие эхо добивает trimmed refit.
перекрытие спанов = эхораздельные спаны = два куска1 кусок = максимум 1 ребро

4.1 · 4Два режима: с петлёй и без

A
Lav-referenced — есть петля/рекордер
Куски петли = опора (их имена дают начальные T_k, решение их поправляет). Камеры выравниваются против кусков. Так собрана студия YTUVI: 4 камеры × 45 клипов + 9 DJI-кусков → V1–V4 + A1–A4 + петля на A5.
B
Ref-cam — петли нет (2+ камеры пишут звук сами)
Одна камера назначается опорной (--ref-cam): её клипы становятся якорным пулом, рамка = её часы, T фиксированы. Остальные камеры пришиваются рёбрами. Важно: в якорный пул идут все транскрипты опорной камеры (даже клипы, не попавшие в выборку) — больше рёбер, точнее Δ. Так собрано интервью YTUVI-706: ZVE1 пришит к FX3A (кварц ZVE1 спешил на +3м15с).

4.1 · 5Система проверок — 5 уровней

Синхрон, который нельзя проверить, не существует. Каждый уровень ловит свой класс ошибок; все пять зелёные → можно строить таймлайн.

1
Selftest на синтетике
wordsync.py --selftest — сгенерированный «день» с врущим именем куска и настоящим эхом дубля. Проверяет и решатель, и защиту от эха (echo_rejections ≥ 1). Гоняется после любого изменения кода.
2
Резидуалы речь ↔ часовая модель
Для каждого клипа: разница речевого размещения и ct + Δ_cam. Норма < 1с (тип. 0.2–0.6с). Большой резидуал = клип пришит к чужому дублю или у камеры нестабильные часы — смотреть руками.
3
Непрерывность сплитов рекордера — контроль истиной
Рекордер режет длинную запись на куски встык (сэмпл-точно). Решение обязано выставить эти пары в ноль: gap ≈ 0. На YTUVI-704 сплит-пары сошлись в +0.006с / −0.054с / +0.014с — независимое подтверждение всей системы. Зазор в секунды = решение неверно.
4
Gauge-инвариантность
Смена рамки (поправка имён, таймзона) не должна двигать клипы друг относительно друга: wall_offset до/после — идентичны до мс. Проверяется diff'ом двух прогонов.
5
Контракт билдера + QC-витрина
validate_ingest.py — все требования UXP-билдера, включая ТИХИЕ отказы (fallback в linear, выпадение петли, «unknown_cam», коллизии имён — билдер резолвит клипы по имени глобально, полосы одной метки петлички внахлёст больше кадра). qc_html.py — тёмная витрина: таймлайн дня, часы камер, поправки имён, рёбра/эхо/резидуалы каждого клипа, первые слова матча.
⚠
Ни один из пяти не сверяет со ЗВУКОМ

Все пять уровней проверяют внутреннюю связность: решение против самого себя, ингест против контракта, сборку против плана. Вопрос «а звучит ли готовый таймлайн синхронно» не задаёт никто — и дефект, сидящий внутри кольца, проходит все пять.

            ЧТО СПРАШИВАЛИ                        ОТВЕТ
validate_ingest.py  «ингест по контракту?»        VALIDATION PASSED
prproj_verify.py    «таймлайн по плану?»          сошлось 21 из 21  ✅
pair_check          «камеры друг другу?»          37 из 37, 14.2 мс

            ЧЕГО НЕ СПРАШИВАЛ НИКТО
                    «а звучит синхронно?»         −28.7 с

YTUVIE01, 25.09.2026: всё зелёное, петличка стоит на 28,7 с мимо. Обязательный шестой уровень — замер по самим медиафайлам, снаружи кольца: 4.4 Sync Verification.

4.1 · 6Инструменты

Репо ~/YTAI/scripts/999_extra/wordsync_multicam/ — README там = полный контракт билдера и грабли. Подробный ранбук для пошагового исполнения (в т.ч. слабыми моделями): scripts/05_editing/0500_uxp/docs/wordsync/WORDSYNC_RUNBOOK.md.
# 1. решить день (петля)
python3 wordsync.py --day /Volumes/DISK/DAY --cam-map "CAM1=CAM1_Name,..."
# 1'. решить день (без петли)
python3 wordsync.py --day /Volumes/DISK/DAY --ref-cam CAM1-FX3A --only-file selected.txt
# 1''. две системы часов на ±1 ч (спаренные DJI + Sony против TX00/Pocket) —
#      сдвиг группы в решении, пишется в _reframed; повтор с той же меткой отказывает
python3 reframe_result.py --project "$P" --sec -3600 --chunk-re '^TX0[1-9]_' \
    --cams CAM-A_FX3,CAM-B_ZVE1 --tag dubai_clock --why "…" --dry-run
Мост дня: решение → ingest всех сцен. make_ingest.py собирает одну сцену, а у съёмочного дня их десяток и больше; ingest из одной сцены панель вдобавок уводит в линейный сборщик. Мост зависит от того, чем разобран день (3.1):
A
Полевой день (разбор 0120) YTEVO03
cd ~/YTAI/scripts/999_extra/wordsync_multicam
python3 retarget_result.py --project "$P"            # пути решения → файлы ВНУТРИ проекта
                                                     #   (видео 01_Source_Proxy/{сцена}/{камера}/, звук 01_Source/{сцена}/Sound/)
python3 scene_stems.py --project "$P"                # списки клипов по сценам + прогноз коллизий имён петличек
python3 build_day_ingest.py --project "$P" --dry-run
python3 build_day_ingest.py --project "$P"           # все сцены: первая начисто, остальные --add

Порядок камер — из паспорта дня (00_Setup/06_Day/*_day_passport.json), ничего руками не набирается. Драйвер зовёт make_ingest.py --tx-label device --tx-scene-suffix: дорожка петлички = физический передатчик DJI-N, а полоса, попавшая в несколько сцен, получает суффикс сцены (…_orig__S08.wav). Сцены другого дня — 00_Setup/01_Ingest/extra_scenes.json, драйвер доливает их после паспорта.

B
Студийный день (разбор 0121) YTUVIE01
cd ~/YTAI/scripts/01_prepare/0121_studio_day
python3 ingest_src.py --block 1                      # сухой прогон
python3 ingest_src.py --block 1 --apply              # камеры + петличка срезами уроков
# … finesync (4.2) …
python3 ingest_src.py --block 1 --screens --apply    # дорожка экрана — строго ПОСЛЕ finesync, ДО 0113

Сцена = урок ({NN}_{слаг}), симлинков нет (--no-symlink): файлы уже лежат деревом курса. Петличка — срезы уроков lavcut.py (wall_offset 0). Запись экрана кладётся с нуля урока отдельным проходом: в графе finesync она держится одной слабой связью и получила бы отрицательный wall_offset.

C
Одна сцена руками
python3 make_ingest.py --project "$P" --scene 05_Interview --add \
    --result .../wordsync_result.json --cam-order CAM1,CAM2      # мультикам-сцена
python3 make_ingest.py --project "$P" --scene 04_Stones --add \
    --linear --files-from stones.txt --folder STONES             # линейная (selects/b-roll)
Замеры для сборки из пресета. make_ingest.py пишет на клип audio_streams и probe.fps/width/height/rotation, на сцену — scene_fps (50p, только если вся сцена 50p, иначе 25p). Только такой ingest панель строит из .sqpreset с точным числом дорожек, без посева и огрызков. Старый ingest дозамерить без пересборки: make_ingest.py --probe-audio --probe-fps --ingest <ingest.json> (бэкап *.bak_probe_*.json сам).
# проверки + витрина
python3 validate_ingest.py "$P/00_Setup/01_Ingest/{CODE}_ingest.json"
python3 qc_html.py --result .../wordsync_result.json --tr .../Transcription --out qc.html
# дальше по порядку — каждый шаг меняет то, что читает следующий
4.2 finesync_run.py --apply → [экран] → 4.3 0113_frame_align.py
→ ingest_markers.py --project "$P" --apply    # маркеры говорящих — последними (время через block_spans плана)
→ Premiere: Build Ingest → Verify Sync

Маркеры: название — мысль реплики, цвет — кто говорит (marker_titles.py --titles <json> --apply). После повторного ingest_markers.py заголовки накладываются заново.

4.1 · 7В Premiere: Build Ingest и точная раскладка дорожек

Ingest готов и провалидирован → дальше всё делает UXP-панель «YTAI Assembly». Дорожки раскладывает не Premiere и не ты — их на 100% задаёт ingest. Ниже — как загрузить, собрать и убедиться, что легло ровно.

1
Загрузить панель
Premiere ≥ 25.6 → открыть {PROJECT}.prproj. Панель грузится через Adobe UXP Developer Tool: Add Plugin → выбрать ~/YTAI/scripts/05_editing/0500_uxp/manifest.json → Load. В Premiere появится панель «YTAI Assembly» (id com.ytai.assembly.panel). Если панель уже грузилась раньше — в UDT просто Load/Reload.
2
Вкладка Ingest — автодетект
Панель сама ищет {PROJECT}/00_Setup/01_Ingest/{CODE}_ingest.json (код проекта берётся из имени папки по ^(YT[A-Z]{2,4}\d+)_). Кнопка Build Ingest должна стать активной.

Капкан 1: статус завис на «Detecting files…», Build неактивна → ingest не распарсился. Чаще всего это отсутствие top-level files:{} или битый JSON. Прогони validate_ingest.py — он назовёт точную причину.
Капкан 2: Build прошёл, но секвенция получилась «встык, без петли» → билдер ТИХО ушёл в linear-режим (нет layout_mode:"wallclock", хоть у одного клипа нет числового wall_offset или в ingest всего одна сцена). Мосты дня это исключают, но если ingest правили руками — validate_ingest поймает.
3
Build Ingest — что построится
По секвенции на каждую сцену, имя = {CODE}_{scene} (напр. YTEVO03_04_…); готовые секвенции панель убирает в бин 00_Source_Timelines. Какие сцены строить — галочки «Sequences to build» (new · all · none). У измеренного ingest секвенция рождается из .sqpreset сразу с частотой scene_fps и точным числом дорожек; без замеров — прежний путь от посевного клипа, с однокадровыми огрызками (лог называет, каким путём собрана каждая сцена). Клипы камеры лягут на свою V-дорожку, паузы между блоками видео билдер вырезает и ставит на стыках Comment-маркеры с величиной пропуска.

Rebuild (кнопка сама переименовывается, когда секвенции уже есть) сначала сам удаляет секвенции и бины отмеченных сцен, потом строит заново — удалять руками и переименовывать в *_old не нужно. Остальные сцены не трогаются.
4
Чем задаётся порядок дорожек (единственный источник правды)
Порядок V1..VN = массив cam_layers[scene] в ingest, слева направо: первый элемент → V1, второй → V2… Задаётся флагом --cam-order у make_ingest.py. Имена в массиве обязаны байт-в-байт совпадать с именами папок-родителей клипов (симлинк-папки камер) — иначе клипы камеры молча выпадут (warning unknown_cam в логе панели).

Звук камер — блоком по числу звуковых потоков. Камеры идут в порядке V-дорожек, и каждой отводится столько A-дорожек подряд, сколько потоков в её файлах (audio_streams из ingest): у FX3/ZV-E1 один поток — одна дорожка, у DJI Pocket 4 Pro два — две. Поэтому номер A-дорожки совпадает с номером V только когда у всех камер по одному потоку.
Петлички — над блоком камер, по дорожке на метку (--tx-label device → физический передатчик), нарезанными полосами строго под видео (там, где видео нет, петли нет — это норма). Источник петли в секвенции — timeline-рендер {TX}_{scene}_timeline.wav от 0113_frame_align.py: один на пару сцена × передатчик, лежит рядом с исходной полосой (полевой день — 01_Source/{сцена}/Sound/, студийный — Sound/ урока), формат 1:1 с источником — Float32 mono, паузы = тишина. Ingest (tx_strips) и секвенции ссылаются только на рендеры; исходные полосы нужны лишь пересборке (пути в tx_strips_source).

Пример YTUVIE01: V1 FX3 · V2 ZVE1 · V3 запись экрана · V4 ролики → A1–A4 их звук · A5, A6 петлички TX01/TX02. Точную раскладку до Premiere показывает node tools/plan_cli.js --ingest … (cam_audio, tx_aIdx_base, a_tracks_needed).
5
Проверить, что легло ровно — числом, потом глазами
· Verify Sync (вкладка Ingest, на открытой секвенции сцены): панель сохраняет проект и меряет звук каждой пары дорожек в кадрах. Гейт — худшая пара камера ↔ петличка ≤ ½ кадра; вердикт SYNC / DESYNC / INSTRUMENT (контроль петличка ↔ петличка не ноль — прибор сломан) / NO_DATA. Подробно — 4.4 Sync Verification.
· Debug Dump → verify_build_sync.py — легло ли по плану тик-в-тик (4.3).
· Глазами: число V-дорожек = числу камер, V1 = основная камера (Solo V1), петлички полосами над блоком звука камер.
· Клип красный «Media Offline» → панель Doctor → Find & Relink local (симлинки живы, это кэш путей Premiere).
6
Порядок дорожек или синхрон не те — чинить числами, не руками
Всё, что сдвинуто на таймлайне руками, умрёт при следующем Rebuild — в том числе сдвиг кнопкой Sync Audio: она двигает элементы таймлайна мимо ingest. Правильный цикл:
· порядок дорожек — перегенерировать ingest с нужным --cam-order (для дня — поправить паспорт и перегнать build_day_ingest.py; wall_offset'ы не меняются) → Rebuild отмеченных сцен;
· синхрон камер — поправка в wall_offset (finesync или Spread → Synchronize → Collect, 4.2) → 0113_frame_align.py заново → Rebuild → Verify Sync;
· петличка — клип петли руками не двигать: перегнать 0113 (новый рендер в координатах таймлайна) или, если звук уехал при верной раскладке, пересобрать рендер по замеру офлайн (4.4).
Порядок навсегда синхрон → цвет: Rebuild стирает клиповые эффекты.

4.1 · 8Боевые случаи

YTEVO03 (полевой день, 14 сцен, Sony FX3/ZV-E1 + DJI Pocket + петлички DJI). Две системы часов ровно на ±1 ч: спаренные передатчики TX01…TX04 берут время у приёмника → телефона (UTC+4), неспаренный TX00 и Pocket — МСК; Sony по речи ушли к спаренным. Внутри групп всё сходилось, поэтому валидатор и finesync молчали; признак — correction_sec полосы ≈ ±3600. Лечение — reframe_result.py, затем retarget → scene_stems → build_day_ingest. Там же: слот TX00 — не устройство (под ним писали разные передатчики, билдер обрезал полосу на 112 мин) → --tx-label device; 14 полос из 44 попали в несколько сцен → --tx-scene-suffix; короткие дубли Pocket по 5–60 с — полоса < 30 с берётся, если она ≥ 3 с и хотя бы наполовину лежит под клипами.
YTUVIE01 (студия, FX3 + ZV-E1 + запись экрана, две петлички). Сцена = урок, петличка — срезами уроков, запись экрана кладётся с нуля урока отдельным проходом --screens. Все сверки с планом дали PASS, а петличка стояла на 28,7 с мимо — дефект сидел внутри кольца «планировщик → план → билдер» (4.4). Отсюда Verify Sync как обязательный шаг после Build.
Грабли метода. Мастер-конкат сессии склеен встык — session-время кусочно к wall, решать по кускам, не по мастеру; короткий чисто-сценарный клип может пришиться к чужому дублю — ловится резидуалом. Кадровую и миллисекундную точность word-sync не даёт: её добивают 4.2 Fine Sync и 4.3 Frame-Align.
🎯 Дальше по конвейеру. Word-sync даёт речевую точность 0.6–0.9 с — для раскладки дня хватает, для монтажа по петличке нет (губы разъезжаются). Третий слой — 4.2 Audio Fine Sync: корреляция самих звуковых волн доводит рассинхрон до единиц миллисекунд, автоматически, ~1.5 мин на день. Затем 4.3 Frame-Align переносит миллисекунды в сами WAV петличек. Шаги 4.5 и 4.6 RUNBOOK, между валидатором и «Build Ingest».