make_ingest.py и валидатора, до «Build Ingest» — на каждом проекте, где диалог будут резать по петличке. Поправки живут в ingest.json (wall_offset): на уже собранный таймлайн они не действуют — секвенции надо пересобрать. Не нужно: если петлички нет вовсе или точность в полкадра не требуется.Источник — всё, что звучит и стоит на таймлайне: видеоклип (накамерный микрофон) и полоса петлички (WAV рекордера). Ключевой факт, доказанный на реальном материале: один клип смещён одинаково относительно всех петличек, одна полоса — относительно всех клипов. Ошибка положения принадлежит источнику.
Значит каждое измерение пары даёт уравнение Δ(A,B) = e_B − e_A, и N источников сцены дают до N(N−1)/2 уравнений на N неизвестных — переопределённую систему, которая решается целиком методом наименьших квадратов. Это принципиальное отличие от попарного синхрона (Premiere Synchronize, PluralEyes, Syncaila): они решают по одному уравнению за раз и выбрасывают избыточность — ту самую, что защищает от ошибок.
Для каждой пары источников, пересекающихся во времени сцены, берётся несколько окон (по умолчанию 4 × 20 с) внутри пересечения:
# полоса речи → огибающая → нормированная взаимная корреляция ffmpeg −ss (точный seek) → mono 16 кГц → highpass 200 / lowpass 3800 лог-RMS-огибающая, шаг 4 мс, минус медленный тренд (уровни/АРУ разные) NCC(окно A, окно B ± диапазон поиска) → пик + параболическая интерполяция → Δ с точностью единицы мс при кадре 40 мс
Меряются все типы пар: камера↔петличка, петличка↔петличка и камера↔камера. Пары «петличка↔петличка» — не украшение: полосы двух рекордеров перекрываются по 30 минут и сшивают сцену в один жёсткий граф (см. грабли ниже). Ни один попарный синхронизатор таких рёбер не строит в принципе.
Защита на уровне окна: истинный сдвиг один на всю пару, поэтому разлёт окон > 50 мс = ложный захват на похожей фразе — измерение отбрасывается целиком (unstable), а не усредняется. Пик на краю диапазона поиска (5% зоны) = настоящий пик снаружи — окно выбрасывается. Пары с перекрытием < 25 с не меряются вовсе: короткое перекрытие — зона ложных захватов.
Система Δ = e_B − e_A решается итеративно (IRLS): вес измерения падает как 1/|невязка| за порогом 20 мс — ложный захват не портит решение тихо, а всплывает как выброс и фактически исключается. Калибровка (gauge): общий сдвиг сцены не наблюдаем, поэтому в каждой связной компоненте среднее поправок видео-клипов приравнивается нулю — видео остаётся, где его поставил речевой синхрон, двигается звук.
Что это даёт сверх попарного синхрона:
sync_source: clock уехал на 1.34 с = 33 кадра и вылечился без единого клика.Рекордер режет петличку по 30 минут. Если мерить только «камера↔петличка», сцена распадается на острова по границам файлов, связанные единственным клипом на стыке. Один ложный захват на нём сдвигает половину сцены — при идеальной невязке, потому что противоречить нечему. Поймано живьём на YTCH10: 1.4 с при невязке 1.5 мс.
Лечение: пары петличка↔петличка (сшивают острова) + поиск мостов (Тарьян) — связей, обрыв которых разваливает граф. Мост в отчёте = «домерить --repeat 3 или добавить перекрывающийся источник».
Степени свободы = сколько измерений проверяют решение, а не задают его. Когда граф — дерево (измерений ровно столько, сколько неизвестных), невязка обязана быть нулевой — это свойство арифметики, не доказательство синхрона. Инструмент говорит это вслух и возвращает код 2; лечится --repeat 2 (повторные независимые замеры тех же пар другими окнами — они же страхуют мосты).
Немой B-roll, снятый в стороне, корреляцией не привязать — он остаётся на речевой точности, и это видно в отчёте («сцена распадается на N кусков»). Внутри куска — миллисекунды, между кусками — прежние 0.6–0.9 с. Знать, где стык, и глянуть глазами.
Находка 16.08.2026 №1 (YTCH10): Premiere (UXP setSourceInOut) квантует source-in/out аудио-клипа к видеокадровой сетке — floor, даже для аудио-only WAV. Билдер задавал in = 324.512, Premiere молча делал 324.480 — дробь 14–32 мс срезалась, и петличка везде опаздывала на 0–40 мс.
Находка 16.08.2026 №2 (YTCH13, тик-дамп, 28/28 замеров): позицию аудио-клипа Premiere субкадрово НЕ держит — insert округляет позицию к ближайшему кадру (дробь <20 мс — вниз, >20 мс — вверх). Первая версия фикса («in-точка на сетку, дробь — в позицию слайса», ceil-политика quantizeTxPlacements) строилась на обратном предположении и потому не работала: компенсацию съедало округление позиции, остаток −31.6…+19.9 мс. Отдельно поймано: overwrite видео может лечь на +1 кадр от запрошенного (float-шум в секундах), а compound-транзакция исполняет свои overwrite в ОБРАТНОМ порядке — хвост раннего клипа съедает голову позднего.
Действующая архитектура (17.08.2026): у clip-API нет субкадрового рычага — значит, всё на сетку тиками, а миллисекунды живут в медиа. Полное описание этапа доставки чисел в Premiere — 3.4 Frame-Align & Build Verify.
1. Билдер кладёт всё сам на точные тики кадра (sceneLayout.gridAlignPlan + TickTime.createWithTicks): позиции видео и слайсов, in/out — целые кадры, Premiere нечего округлять и floor'ить. Блок целиком (видео + петля) якорится к ближайшему кадру — ступенек на границах V/A нет по построению. Видео кладётся последовательными транзакциями по возрастанию времени (защита от реверса compound).
2. Субкадровая точность переезжает в WAV (0113_frame_align.py, шаг 4.6 ранбука): на каждую (сцену × петличку) рендерится один WAV в координатах таймлайна — контент каждого слайса лежит в файле ровно на своём сэмпле (пер-слайсная компенсация: поправки fine-sync у каждого видеоклипа свои, одним сдвигом файла не лечится). Билдер кладёт identity-слайсы (offset == source-in) — синхрон ≤ 1 сэмпла.
3. Гейт числом (verify_build_sync.py, ГЕЙТ 6.5): после сборки дамп секвенций сверяется с планом по тикам — счётчики, позиции, in, длительности; допуск 2 мс. Ловит и то, что уши не слышат: клип на +1 кадр, съеденную голову, «вставленные», но пропавшие слайсы.
Политика «видео — хозяин» сохранена: видео стоит на кадрах, петличке при рендере режут/тянут головы блоков на сэмплы. Правило на будущее: любой механизм точного синхрона (finesync, Synchronize, Syncaila) заканчивается пересборкой через grid-раскладку + 0113 + verify — иначе пересборка вернёт до ±20 мс рассинхрона на слайс.
Вкладка Ingest: в сводке ingest видна строка Fine sync: applied <дата> либо Fine sync: NOT APPLIED. Кнопка Fine Sync рядом с Build Ingest: панель передаёт путь к ingest и запускает расчёт (Терминал открывается сам — внутри UXP нельзя, нужен ffmpeg и сотни гигабайт медиа). Когда поправки легли, панель сама перечитывает ingest и пишет «Fine sync applied — REBUILD the sequences».
cd ~/YTAI/scripts/999_extra/audio_finesync python3 finesync_run.py "<папка проекта>" # посмотреть, ничего не меняет (~1.5 мин/день) python3 finesync_run.py "<папка проекта>" --apply # применить + валидатор + ПЕРЕМЕР + вердикт
Отчёты → 00_Setup/01_Ingest/finesync/. Откат: рядом с ingest остаётся *_backup_finesync_*.json. Двойной клик — finesync.command (спросит папку в Finder). Ручной режим по шагам: finesync.py → finesolve.py.
Три инварианта, на которых всё держится:
wall_offset ingest; билдер пересобирает таймлайн (нарезку петличек, компрессию пауз) из чисел сам. «Возвращать клипы на место» руками не нужно ни в одном сценарии.Для выборочной сверки штатным механизмом Premiere (или когда хочется контролировать глазами). Работает посценно — Synchronize живёт внутри одной секвенции. Ряд Premiere sync на вкладке Ingest:
Открыть секвенцию сцены → кнопка строит одноразовую {code}_{scene}_SYNC: каждый клип на своей V/A-паре (Synchronize серый, пока выделенные клипы лежат на одной дорожке), каждая петличка целой полосой на своей A-дорожке (резать нечего → floor'ить нечего), всё на сырых wall-позициях + преролл 30 с, чтобы было куда двигать влево. Манифест раскладки → 01_Ingest/finesync/sync_session_{scene}.json.
Выделить всё → Clip → Synchronize → Audio (Track Channel петлички).
Панель читает, куда Premiere подвинул каждый элемент; дельта = поправка. Перечитывает ingest с диска, сверяет сцену с манифестом (если ingest менялся после Spread — стоп, «re-Spread»: складывать поправки поверх поправок = удвоить их), вливает дельты в wall_offset (бэкап *_backup_syncspread_*) и просит Rebuild сцены. _SYNC-секвенция одноразовая — удалить.
Ограничение: >40 клипов в сцене — Spread отказывается (нужно 40+ дорожек), совет использовать Fine Sync. Оба пути кладут результат в одно место — ingest — и дают одинаковые миллиметры.
| что видно | что значит | что делать |
|---|---|---|
| ✅ все пары в допуске | попадание точное (допуск — полкадра) | строить таймлайн |
| ⚠️ МОСТЫ | связь без страховки: ошибка в ней сдвинет кусок сцены | --repeat 3 по этой сцене |
| ⚠️ распадается на N кусков | нет общего звука между кусками | внутри — мс, между — речевая точность; глянуть стык |
| ⚠️ проверить нечем (dof=0) | граф — дерево, нулевая невязка = арифметика | --repeat 2 (в раннере включён по умолчанию) |
| пара вне допуска | обычно короткое перекрытие или шум (машина, ходьба) | перемерить точечно или принять |
| значение | |
|---|---|
| материал | 12 сцен · 151 клип · 40 полос петличек · 314 измерений |
| время счёта | 1 мин 33 с на весь съёмочный день |
| до поправок | разброс до 1.7 с (42 кадра) внутри сцены |
| невязка решения | 0.1–2.5 мс по сценам, худший максимум 9.9 мс |
| контрольный перемер другими окнами | 159 / 159 пар в пределах полкадра, худшая 9.3 мс |
| после фикса floor-грабли, симуляция сборки | 212 слайсов: 0 раньше видео · 0 наложений · floor-потеря 0.000 мс (симуляция не знала об округлении позиции — на живом YTCH13 остаток был −31.6…+19.9 мс, см. секцию «Квантование Premiere»; закрыто grid-раскладкой + 0113) |
Код панели прошёл адверсарное ревью (18 агентов, 15 находок, 9 подтверждено и закрыто: мосты в графе, отрицательные позиции головных слайсов, стейл-память при Collect, лимит прогрева дорожек и др.). Тесты: wallclock-сьют 107/107 (после перехода на grid-раскладку 17.08.2026; мок Premiere моделирует реальное квантование floor/nearest).
| что | где |
|---|---|
| измеритель + решатель + раннер | ~/YTAI/scripts/999_extra/audio_finesync/ (README там же) |
| grid-раскладка (всё на тики кадра) | 0500_uxp/src/ingest/layout/sceneLayout.js · gridAlignPlan |
| рендер петли в координатах таймлайна | 999_extra/wordsync_multicam/0113_frame_align.py (+ 0500_uxp/tools/plan_cli.js) |
| гейт после сборки (дамп vs план, тики) | 999_extra/wordsync_multicam/verify_build_sync.py |
| Spread / Collect (чистая логика + тесты) | 0500_uxp/src/ingest/syncSpread.js · tests/ingest/wallclock/ |
| постановка, история, сравнение путей | 0500_uxp/HANDOFF_audio_sync.md |
| шаг в конвейере | docs/wordsync/WORDSYNC_RUNBOOK.md — Шаг 4.5 с гейтом |
| речевой слой (предыдущий) | /kb/multicam-wordsync/ · 999_extra/wordsync_multicam/ |