ingest.json должны доехать до таймлайна Premiere без потерь. Проверено на живых сборках (YTCH10, YTCH13, 16–17.08.2026): у clip-API Premiere нет субкадрового рычага — всё, что не на кадровой сетке, он молча округляет, и миллисекундная точность Fine Sync умирает на последнем шаге. Этот этап решает задачу системно: всё на сетку точными тиками, миллисекунды — в сами WAV, а после сборки — проверка числом. Итог: синхрон петлички с видео ≤ 1 сэмпла (0.02 мс), и ни одна поломка сборки не проходит молча.Предпосылка: пройдены 4.1 и 4.2 — в ingest лежат выверенные wall_offset. Дальше:
P="/Volumes/DISK/YTXXNN_Project"
V=~/YTAI/environment/.venv_transcribe/bin/python # 0113 нужен soundfile — в системном python3 его нет
cd ~/YTAI/scripts/999_extra/wordsync_multicam
# 1. Рендер петличек в координаты таймлайна + патч ingest (идемпотентно)
$V 0113_frame_align.py --project "$P" --dry-run # посмотреть
$V 0113_frame_align.py --project "$P" # → "VERIFY OK: all placements on exact frame ticks"
# 2. Маркеры говорящих — ПОСЛЕ 0113 (время маркера переводится через block_spans плана)
python3 ingest_markers.py --project "$P" --apply
# 3. Предполётная: план тем же кодом, что соберёт панель
node ~/YTAI/scripts/05_editing/0500_uxp/tools/plan_cli.js \
--ingest "$P/00_Setup/01_Ingest/{CODE}_ingest.json" --out /tmp/plan.json
# по каждой сцене: preset_ready: true · нет warnings tx_td_* / tx_unaligned_media
# 4. Premiere: перезагрузить панель → вкладка Ingest → Build Ingest → Verify Sync (4.4)
# 5. Панель → Debug Dump (пишет 99_Pipeline/logs/{slug}_dump_*/)
# 6. Гейт числом — обязательный
python3 verify_build_sync.py --dump "$P/99_Pipeline/logs/<свежий дамп>"
# → PASS = таймлайн совпал с планом тик-в-тик. FAIL = не отдавать дальше.
После ЛЮБОЙ правки ingest (finesync, Collect, дорожка экрана, пере-нарезка сцен) — заново 0113 → маркеры → Rebuild → verify. Гварды устаревания (ниже) не дадут забыть. ⚠️ python3 0113_frame_align.py системным интерпретатором падает на import soundfile — только через .venv_transcribe.
Всё ниже — из тик-дампов реальных сборок (254 016 000 000 тиков/с; 54 точки данных YTCH13 + YTCH10), а не из документации Adobe (там квантование не описано вообще):
| операция | что делает Premiere со временем | следствие |
|---|---|---|
setSourceInOut (трим источника) | FLOOR к видеокадровой сетке — даже для аудио-only WAV | субкадровая in-точка невозможна |
createInsertProjectItemAction | ОКРУГЛЯЕТ позицию к БЛИЖАЙШЕМУ кадру (28/28 замеров: дробь <20 мс → вниз, >20 мс → вверх @25p) | субкадровая позиция невозможна; компенсация «дробь в позицию» не работает |
createOverwriteItemAction | on-grid тики проходят точно; float-секунды опасны (double 109.44 лёг на +1 кадр) | позиции слать только целыми тиками кадра |
| compound-транзакция | исполняет свои действия В ОБРАТНОМ порядке | батч overwrite'ов: хвост раннего клипа съедает голову позднего → видео класть по одному, по возрастанию времени |
| фабрики действий (25.6) | работают только внутри lockedAccess — иначе «Requires locked access» | создавать action внутри колбэка транзакции, никогда до |
| «вставлено ok» | ≠ «элемент на таймлайне» (90_CTA: «Placed 3 TX, 0 skipped» — на дорожке 1) | после сборки — обязательный пересчёт фактических элементов |
Вывод, на котором стоит весь этап: субкадрового рычага у clip-API нет. Поправки fine-sync субкадровые и у каждого видеоклипа свои → остаток на каждом слайсе РАЗНЫЙ (−31.6…+19.9 мс на YTCH13 до фикса), одним сдвигом файла не лечится. Единственный сэмпл-точный носитель — сама медиа.
sceneLayout.gridAlignPlan: позиции видео и слайсов, in/out-точки — целые кадры, отправляются через TickTime.createWithTicks (float-секунды типа 109.44 сидят на пол-ULP мимо сетки и ловят ±1 кадр). Блок «видео + петля» якорится к кадру ЦЕЛИКОМ — ступенек на границах V/A нет по построению. Первый блок сцены якорится в 0 — мёртвый воздух до первого видео схлопывается (петличка часто стартует раньше камеры). Видео кладётся последовательными транзакциями по возрастанию времени.
Секвенция — из пресета. В UXP нет API добавления дорожек, а удаление элементов в живом Premiere 25.6/26 молча не работает — поэтому прежний путь «посевной клип + заглушки прогрева» оставлял однокадровые огрызки на V1/A1 и на дорожках петличек. У измеренного ingest (scene_fps + audio_streams у каждого клипа камеры, пишет make_ingest.py) панель записывает .sqpreset и рождает из него секвенцию сразу с частотой сетки и точным числом дорожек: V по камерам, A — блок звука камер (по дорожке на звуковой поток, у DJI Pocket два) плюс петлички над ним. Сетка = scene_fps: 50p, только если вся сцена 50p. Неизмеренный ingest строится прежним путём от посева — лог называет путь каждой сцены, plan_cli.js показывает его заранее (preset_ready / preset_missing).
0113_frame_align.py рендерит на каждую (сцену × петличку) один WAV, где время файла = финальное время таймлайна: контент каждого слайса лежит ровно на том сэмпле, где слайс встанет (пер-слайсная компенсация из content_src_sec плана). Билдер кладёт identity-слайсы (offset == source-in, оба на тиках) — Premiere нечего округлять. Синхрон ≤ 1 сэмпла. Пути исходных полос сохраняются в tx_strips_source, пересборка читает их оттуда: полевой день — полосы в 01_Source/{сцена}/Sound/, студийный — срезы уроков {CODE}_{урок}_TXnn_timeline.wav в Sound/ урока; целые записи рекордера живут в 99_Pipeline/DJI_Audio/. Бэкап ingest пишется рядом, повторный запуск идемпотентен. Хэндлы работают: потянуть край слайса = открыть соседний контент таймлайна.
Три уровня. READBACK в билдере: после сборки панель пересчитывает фактические элементы по дорожкам против плана — «items VANISHED» / «stray items» видно прямо в логе сборки. Гварды устаревания в плане: рендер несёт отпечаток раскладки (block spans + wall_offset источников) — любое изменение после 0113 даёт tx_td_render_stale / tx_td_sources_changed. verify_build_sync.py: дамп секвенций против плана тик-в-тик — по каждой дорожке секвенции, не только плановой; элементы сопоставляются по имени файла и ближайшему старту (огрызок в начале не сдвигает все пары); звук камеры — по потоку на свою дорожку; позиции, in-точки, длительности; допуск 2 мс; exit 1 = не отдавать проект дальше.
Единый источник правды: tools/plan_cli.js гоняет ТОТ ЖЕ чистый код раскладки, что и панель — 0113 и verify считают ровно то, что построит Build. Мок в тестах моделирует реальное квантование (floor/nearest/реверс/lock) — регрессии этого класса ловятся до Premiere.
*_timeline.wav — это просто WAV, где время файла = время таймлайна. Если звук разъехался, пересобирать Build не нужно: файл пересобирается offline по замеру в стейджинг и подменяется на месте, оригиналы уходят в бэкап. Так чинили YTUVIE01 25.09 (relav2.py, сегмент = дубль): 28 730 → 31 мс, 322 → 26 мс, 248 → 22 мс. Пересборка Build, наоборот, стирает клиповые эффекты — поэтому порядок навсегда синхрон → цвет.
формат рендера pcm_f32le, моно ← брать С ИСТОЧНИКА, не выбирать
pcm_s24le НЕЛЬЗЯ ← срежет всё выше 0 dBFS:
во float это законный сигнал,
у двух файлов YTUVIE01 пик ровно в нуле
Запускать интерпретатором .venv_transcribe (нужен soundfile). Сначала --dry-run: таблица «сцена × петличка × слайсы», длины рендеров и coverage gaps (файл рекордера кончился раньше блока видео — там будет тишина; десятки мс на стыках 30-мин файлов — норма). Затем боевой запуск: рендер {TX}_{сцена}_timeline.wav ложится в папку первой исходной полосы этой метки (полевой день — 01_Source/{сцена}/Sound/, студийный — Sound/ урока), ingest патчится. В 01_Source прокси-комплекта и на Drive целых записей рекордера нет — они в 99_Pipeline/DJI_Audio/. Обязателен финал «VERIFY OK: all placements on exact frame ticks» — иначе не продолжать. Следом ingest_markers.py --apply и предполётная plan_cli.js.
Перезагрузить панель (если код обновлялся) → вкладка Ingest → отметить сцены → Build (Rebuild сам удалит прежние секвенции отмеченных сцен). В логе смотреть: путь сборки (sequence from preset, а не fallback seed+pre-warm), warnings плана (не должно быть tx_td_*), «Placed N video + M TX», и READBACK-строки — любой «VANISHED»/«stray» валит сборку, в Premiere дальше делать нечего. Затем Verify Sync — звук в кадрах (4.4).
Панель → Debug Dump → verify_build_sync.py --dump …. PASS = этап закрыт, можно резать. FAIL печатает точный элемент и величину в мс — это и есть тикет на разбор, гадать не нужно.
| сигнал | что значит | что делать |
|---|---|---|
tx_unaligned_media | петличка кладётся из оригинальных WAV — 0113 не прогнан; синхрон деградирует до ≤½ кадра на слайс | прогнать 0113 → Rebuild |
tx_td_render_stale | раскладка видео изменилась ПОСЛЕ рендера (finesync, offset'ы, пере-нарезка) — звук в рендере устарел, хотя позиции выглядят идеально | 0113 заново → Rebuild |
tx_td_sources_changed | wall_offset источника петлички в tx_strips_source отличается от запечённого в рендер | 0113 заново → Rebuild |
tx_td_render_unverifiable | рендер без отпечатка (старый 0113) — проверить свежесть невозможно | 0113 заново → Rebuild |
| READBACK: «items VANISHED» | транзакция отчиталась ok, но элементов на дорожке нет (класс 90_CTA) | лог сборки → повторный Build; если стабильно — разбор с дампом |
| READBACK: «stray items present» | лишний элемент — огрызок посева/прогрева 1 кадр (V1/A1 в нуле, хвосты дорожек петличек); бывает только у сборки от посева: удаление в живой 25.6/26 не срабатывает | дозамерить ingest (make_ingest.py --probe-audio --probe-fps --ingest …) → Rebuild из пресета; или удалить руками |
preset_missing в плане / «fallback seed+pre-warm» в логе | ingest не измерен: нет scene_fps или audio_streams у клипов — секвенция родится от посева, с огрызками и одной A-дорожкой на камеру | make_ingest.py --probe-audio --probe-fps --ingest … → 0113 → Rebuild |
| «Requires locked access» в логе | код панели создаёт action вне lockedAccess — регресс билдера | панель обновить/чинить; тесты мока этот класс ловят |
| coverage gap в 0113 | рекордер выключился раньше видео — в рендере тишина, величина указана | прочитать и принять (или знать, где нет петли) |
| verify: position/source_in off | Premiere положил не туда, куда просили | сверить fps секвенции с планом (seed!), см. лог «sequence timebase» |
Годно: verify_build_sync.py → PASS (все элементы всех дорожек всех сцен в пределах 2 мс от плана; фактически — 0 тиков) и ни одного tx_td_*-warning'а в плане. Проверять ВСЕ сцены — чинить по одной и сломать соседнюю здесь уже нельзя, но правило остаётся.
⚠️ PASS значит «легло по плану», а не «звучит синхронно»: план считает тот же планировщик, что и сборку. На YTUVIE01 сверка с планом (prproj_verify.py) дала «сошлось 21 из 21» при петличке на 28,7 с мимо. После PASS — замер по звуку, 4.4.
| значение | |
|---|---|
| обещание fine-sync в числах (ingest) | невязка ≤ 0.4 мс (24 пары, YTCH13) |
| на таймлайне ДО этапа | остаток −31.6 … +19.9 мс, у каждого слайса свой; ступеньки V/A; +1 кадр на overwrite; пропавшие слайсы |
| на таймлайне ПОСЛЕ (живая сборка 17.08.2026) | 26/26 identity-слайсов тик-в-тик (0 расхождений по A-дорожкам петличек); контент ≤ 1 сэмпла (0.02 мс) |
| рендер + патч ingest (шаг 1) | 999_extra/wordsync_multicam/0113_frame_align.py |
| гейт после сборки (шаг 3) | 999_extra/wordsync_multicam/verify_build_sync.py |
| офлайн-план = код панели (предполётная) | 0500_uxp/tools/plan_cli.js |
| секвенция из пресета | 0500_uxp/src/ingest/placement/presetFile.js · sequencePreset.js |
| маркеры говорящих (после 0113) | 999_extra/wordsync_multicam/ingest_markers.py · marker_titles.py |
| grid-раскладка (чистая логика + тесты) | 0500_uxp/src/ingest/layout/sceneLayout.js · gridAlignPlan · tests/ingest/wallclock/ |
| размещение (тики, lock, порядок) | 0500_uxp/src/ingest/placement/clipPlacer.js · wallClockBuilder.js |
| мок с реальным квантованием Premiere | 0500_uxp/tests/mocks/premierepro.js |
| шаги в конвейере | docs/wordsync/WORDSYNC_RUNBOOK.md — Шаг 4.6 + ГЕЙТ 6.5 |
| полная история вопроса (постановка → решение) | {YTCH13}/HANDOFF_frame_shift.md |
| предыдущий слой (звуковая корреляция) | /kb/fine-sync/ |