AudionAudion

План: обрезка видео

PLAN_TRIM_RU.md

План: обрезка видео

Про интерфейс в этом документе. Точки реза здесь находят в плеере mpv,
который открывается кнопкой из раздела, и переносят в поля кнопками — либо
вводят таймкодом с клавиатуры. Форма волны и прослушивание кадра, о которых
говорится ниже, относятся к панели, отложенной до отдельного этапа: движок и
измерения от неё не зависят и остаются в силе.

Состояние на 25 августа 2026: три паттерна из четырёх работают, превью и два ползунка стоят в интерфейсе. Не сделано только вырезание куска из середины со склейкой и кроссфейдом — оно и задумывалось отдельным этапом.

ЧтоГде живётГотово
Арифметика реза, таймкод, ключевые кадрыsystem_core/core/trim_contract.pyда
Операция для GUIsystem_core/services/media_service.py, trim_mediaда
Раздел «Обрезка»config/tool_manifest.yaml, группа trimда
Превью: кадр, волны, два фейдераsystem_core/ui_nicegui/app.py, поля trim_points, trim_file_pickда
CLIScripts/ff-trim-start.cmd, -end, -both, kind: trimда
Проверки арифметикиtests/test_trim_contract.py, 57 тестовда
Матрица форматов и методы проверкиTRIM_MATRIX_RU.mdда
Приёмка на матрице источниковProRes, HEVC long-GOP, all-intra, 29,97 DF, VFR, 4 канала, MPEG-TS, кириллицада
Разделение файла надвое по точкеpattern: split, два выхода _part1 / _part2да
Снимок кадра в PNG/JPGtrim_save_still, Transcoded/Stillsда
Вырезание куска из середины, склейка, кроссфейднет

Ниже — исходный замысел; расхождения с ним, найденные на живом материале, отмечены прямо в тексте.

Зачем

Сегодня Media Tools конвертирует, но не режет. При этом самые частые задачи монтажа к монтажу отношения не имеют: снять заставку, отрезать хвост, убрать кусок из середины. Ради этого люди ставят комбайн, который весит гигабайты и перекодирует час ради двух склеек.

Материал — исходники с камер: MOV и MP4, H.264/H.265, ProRes, звук PCM или AAC, одна-две дорожки. Не фильмы с пятью языками, субтитрами и AC-3: там своя жизнь и свои инструменты. Из этого следует и набор параметров ниже — он про карточку с камеры, а не про мультиплексирование.

Отдельная причина — точность на дробных частотах. Разбор Shutter Encoder 20.2 (дефект держится с 19.1) показал: нарезка по таймкоду переводит время в кадры через round(fps), и на 23.976 / 29.97 / 59.94 длина уезжает на множитель 1001/1000 — почти четыре секунды на часе. В Media Tools частота живёт точной дробью (Fraction) от ffprobe до команды ffmpeg, так что округлять негде по построению.

Что делаем

Четыре паттерна, ничего сверх:

  1. отрезать начало;
  2. отрезать конец;
  3. отрезать с обеих сторон;
  4. вырезать кусок из середины (вторым этапом — там появляется склейка).

Первые три — одна операция с двумя параметрами. Четвёртый разрезает файл надвое и склеивает, и потому решается отдельно.

Резка только по ключевым кадрам

Видео копируется, не перекодируется: -c:v copy. Рез ложится на ближайший ключевой кадр, файл готов за секунды, поколение не теряется.

Точная резка сознательно не делается. Она требует перекодирования всего файла, и таких инструментов и без нас достаточно. Наша сильная сторона — быстро и без потерь; смешивать эти два поведения в одной кнопке значит врать о времени работы.

Уточнение по факту работы. К ключевому кадру привязано только начало — именно оно должно попасть в точку, с которой копирование потока вообще возможно. Конец такого ограничения не имеет: он уезжает числом кадров (-frames:v), посчитанным по точной дроби, и ложится ровно туда, куда просили. Поэтому в интерфейсе у начальной точки написано «ближайший ключевой», а у конечной — «рез на».

Из этого следует главное требование к интерфейсу: человеку показывается фактическая точка реза, а не запрошенная. Ближайший ключевой кадр берётся у ffprobe, и рядом пишется расхождение: «ближайший ключевой: 00:01:28,4 (−1,6 с)». Тогда решение остаётся за человеком, а не оказывается сюрпризом в готовом файле.

Превью

Дёшево и достаточно. Плеера с перемоткой не делаем.

Кадр. Одна картинка из фактической точки реза, 426×240 (16:9). Такого размера хватает узнать сцену, а стоит она долей секунды:

ffmpeg -ss <точка> -i <файл> -frames:v 1 -s 426x240 -q:v 3 preview.jpg

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

Как сделано. Волна не картинка сбоку, а шкала: каждый фейдер несёт свою полосу прямо над собой и делит с ней диапазон, поэтому движок идёт по форме волны, а не рядом с ней. Верхняя полоса — весь файл (грубая наводка), нижняя — те же десять секунд, что и точный фейдер. Обе рисуются тёмно-оранжевым по морскому фону, showwavespic с scale=sqrt: на линейной шкале обычная речь даёт волосок в три процента заливки, на корневой — читаемую форму.

Полоса всего файла стоит дорого только один раз: час звука — около двух с половиной секунд, поэтому она считается в фоне и кладётся в workspace/trim_waves, а дальше берётся оттуда за сотые доли секунды. Окно в десять секунд не кешируется вовсе — оно рисуется за 0,14 с и каждый раз новое.

Ширина полосы — 2560 точек: этого хватает на 4K в штатном окне 1600×900, а при растягивании картинка просто интерполируется. Волна показывает пики, а не спектр, поэтому лишняя точность здесь ничего не добавит.

ffmpeg -ss <точка−2> -t 4 -i <файл> \
  -filter_complex "showwavespic=s=760x150:colors=#e08a63" -frames:v 1 wave.png

Декодируются только эти четыре секунды, поэтому картинку можно перерисовывать на каждое движение ползунка. На волне: отметка реза по центру и засечки по секундам — без них не читается масштаб.

Стык на прослушивание (для вырезания куска). Три-четыре секунды вокруг шва, склеенные так, как будет в результате, отдаются тегом <audio>. Только так слышно, будет щелчок или нет.

Паттерны — переключателями секций

Четыре паттерна — это ряд кнопок-переключателей, а не список выбора. Нажатая кнопка открывает свою секцию, и в ней ровно те поля, которые нужны:

КнопкаЧто в секции
Началоодна точка: докуда отрезать
Конецодна точка: откуда отрезать
Оба краядве точки
Вырезать кусокдве точки + чекбокс кроссфейда

Так человек видит устройство операции до того, как её выберет, и не гадает, что скрывается за строкой списка. Лишние поля не показываются вовсе — не гасятся, а именно отсутствуют, чтобы не отвлекали.

Ползунки и превью общие для всех секций: меняется только число точек, которые ими задаются.

Два ползунка

Классическая беда длинного файла: на часовой записи пиксель мыши равен пяти секундам, и точку поставить нечем. Решение — два ползунка друг над другом, одного размера, разных цветов:

ширину; там пиксель уже равен кадру.

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

Что видно с первого взгляда

Форма отвечает на один вопрос: что останется. Поэтому режимы названы по результату — «Убрать начало», «Убрать конец», «Оставить середину», «Разделить надвое», — а на обеих полосах волны сохраняемый кусок подсвечен зелёным, и он же обведён; всё, что уйдёт, закрыто тёмной вуалью. Сравнивать числа не нужно, видно глазами.

Точки называются IN и OUT — терминами монтажа, а не «начало/конец», которые совпадали со словами в названиях режимов и путали. В режиме разделения точка одна и называется CUT: там ничего не выбрасывается, файл просто режется надвое (_part1 и _part2), у второй части таймкод сдвинут.

Транспортная панель

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

ГруппаЧто в ней
ТочкиIN / OUT (или CUT) — какую точку двигают фейдеры
Перемещениев начало, к предыдущему ключевому, кадр назад, поле времени, кадр вперёд, к следующему ключевому, в конец
Снимоксохранить текущий кадр в PNG или в JPG, полным разрешением, в Transcoded/Stills

Подписей на кнопках нет — только иконки и тултипы. Единственный текст в ряду — поле времени в формате чч:мм:сс,мс (не таймкод и не сэмплы; это сказано в тултипе). Отсчёт «кадр из общего · время» и сводка результата идут отдельной строкой под кнопками: в одной строке с ними они на узком окне выдавливали кнопки за край. Ряд переносится по группам, на 920 px всё умещается без горизонтальной прокрутки.

В режиме «Оставить середину» у грубой полосы две ручки — левая ставит IN, правая OUT, а участок между ними и есть то, что останется: он подсвечен и на рейке, и на волне над ней. Точная полоса остаётся одиночной (в её десятисекундном окне второй край обычно не помещается) и следует за той ручкой, которую двигали последней.

Пульт входных данных

Файл выбирается не списком, а перелистыванием: бейдж с именем текущего файла и стрелки по краям, по алфавиту и по кругу. Бейдж занимает 60% ширины (камерные имена длинные), стоит по центру, фон у него терминальный, шрифт Cascadia Mono 11 pt полужирный — это пульт, а не поле формы. Рядом счётчик «3/12», закладка на отмеченных файлах и кнопка сведений: таблица ffprobe (кодек, точная дробь частоты, VFR, таймкод, начало таймлайна) — то, что нужно знать до реза.

Точки помнятся по файлам. У каждого файла свои A и B; шагнул к следующему — твои точки остались, вернулся — они на месте. Ключ кэша не имя, а дешёвый хеш содержимого (размер плюс первый и последний мегабайт), поэтому переименование дубля отметку не теряет. Кэш живёт в окне: закрыл программу — кэш чистый.

Запуск режет всё отмеченное разом, каждый файл по своим точкам. Резать всю папку по одним и тем же точкам инструмент больше не предлагает: у дублей не совпадают ни ключевые кадры, ни границы. Пакета «одинаково всем» больше нет нигде: CLI тоже режет один файл — тот, на который указали AUDION_TRIM_FILE, либо первый в Source, и обёртка говорит, какой из двух вариантов выбрала.

Как это выглядит в сборке

Комплект контролов один на всю операцию, а точек может быть две — поэтому они переключаются кнопками A (начало куска) и B (конец). Второй комплект ползунков не нужен: точка меняется, ползунки те же. Рядом всегда видна сводка «A · B · длина куска», так что переключение не прячет вторую точку.

Раскладка блока: слева «телевизор» — кадр и под ним кнопки перехода, справа две подгруппы со своими заголовками, «Весь файл» и «Окно 10 секунд», в каждой полоса волны и фейдер под ней. Ряд управления — кнопки A/B, покадровые стрелки, поле времени и сводка «A · B · длина куска» — идёт снизу, под всем блоком. Превью стоит сбоку, а не под ползунками, чтобы форма не съедала высоту окна.

Ползунки сделаны фейдерами: рейка 26 px тёмно-морского цвета с обводкой, оранжевый движок 14×34. Высоту рейки Quasar пишет инлайном из track-size, так что она задаётся пропом, а не стилем.

Покадровая точность — тремя способами: стрелки ◀ ▶ рядом с маркером, шаг ровно кадр; горизонтальный скролл (MX Master и подобные) — кадр за щелчок на точном ползунке и полсекунды на грубом; и просто ввод времени в поле.

Точки A и B стоят на обеих полосах зелёным пунктиром, а текущее положение фейдера — светлой сплошной чертой. Поэтому кусок, который останется, виден целиком, даже когда двигаешь только один его край.

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

Кадр перерисовывается через 0,35 с после того, как рука остановилась: сама отрисовка стоит ~90 мс, но дёргать её на каждый пиксель протяжки незачем.

Кроссфейд

Чекбокс, всегда на виду. Включён — стык звука сводится за 20–30 мс, щелчка нет.

Важно: кроссфейд означает пересборку звука, скопировать дорожку уже нельзя. Но видео остаётся -c:v copy, а перекодируется только аудио — секунды на файл. То есть галочка не превращает быструю резку в медленную, и обещание «быстро» остаётся честным.

Подсказка, которую стоит сделать: если по обе стороны шва сигнал не в нуле — подсветить чекбокс. Человек не обязан знать, когда бывает щелчок.

Параметры

Критерий отбора один: параметр либо ничего не стоит при копировании потоков, либо ломает обещание «две секунды вместо двадцати минут». Дорогого здесь нет.

Таймкод — первое, что нельзя потерять

Камера пишет таймкод в файл, и по нему на монтаже синхронизируют камеры между собой и со звуком с рекордера. Обрезка обязана сдвинуть его правильно: начало нового файла — это старый TC плюс отрезанное.

Отдельная беда, которую надо проверять и говорить вслух: при перекладывании MOV → MP4 дорожка tmcd часто теряется молча. Материал после этого выглядит целым, а синхронизировать его уже нечем. Если таймкод не переносится — сказать об этом до запуска, а не оставить человека выяснять это на монтаже.

По умолчанию TC сдвигается. Обнуление — отдельным выбором, для случаев, когда файл уходит наружу как самостоятельный.

Что показала проверка. FFmpeg 9.0.1 переносит дорожку tmcd при копировании MOV → MP4, но значение оставляет прежним — сдвиг всё равно надо писать самому через -timecode. В MKV таймкод живёт текстовым тегом, а не дорожкой, и монтажка может его не прочитать: об этом сказано предупреждением до запуска. Ещё одно предупреждение — PCM в MP4 (нестандартный тег ipcm), для камерного звука дом — MOV.

Частота — та, что в файле

ffprobe отдаёт точную дробь, поэтому ничего не домысливается: снятое на 23,976 объявляет 24000/1001, снятое на честные 24,000 — 24/1. Оба читаются как написано.

Это отменяет прежнее правило «целое NTSC читаем как 1000/1001». Оно исходило из того, что контейнеры округляют, — они не округляют. ARRI Alexa Mini, снимавшая ровно 24,000 (260 кадров на 10,8333 с — измерено), объявляет 24/1, и множитель превращал верную частоту в неверную: пять кадров за четыре минуты, ровно та же ошибка 1001/1000, ради которой галочку и заводили, только в другую сторону.

Галочки больше нет ни на панели, ни в CLI. Частота, которую не удалось прочитать, отклоняется, а не заменяется догадкой.

Что можно менять бесплатно

ПараметрЗачем
Контейнер: как есть / MP4 / MOV / MKVMOV с камеры → MP4 для монтажной, без единого пережатия. ProRes при этом остаётся ProRes
Звук: копировать / убратьНемой файл для монтажа или для гифки — частый случай
Канал звука: оба / первый / второй / свести в моноУ камеры это обычно пушка и петличка в двух моно-каналах. Выбор канала — работа внутри дорожки, при PCM бесплатная
faststartТолько когда на выходе MP4: заголовок в начало, файл начинает играть не скачавшись целиком

Метаданные камеры — беречь

Модель, режим съёмки, гамма, имя ролика. На монтаже это контекст, без которого материал обезличивается. Чистка метаданных — операция для публикации в сеть, и живёт она в другом разделе; здесь всё переносится как есть.

Чего здесь нет

Битрейта, кодека, разрешения, частоты кадров, нормализации громкости. Как только они появляются в окне обрезки, их трогают — и получают двадцать минут вместо двух секунд, не поняв, за что заплатили.

Нормализация вредна и по существу: у исходников она убивает запас и связь между дублями. Для записи совещания она, наоборот, нужна — но это отдельный профиль («запись встречи → нормализовать и пожать»), а не галочка в резке.

Выбор языковых дорожек и работа с AC-3 — тоже не сюда. Это мультиплексирование фильмов, для него есть свои инструменты.

Как ложится в проект

Операции описаны строкой в config/script_profiles.yaml и разведены по kind в диспетчере Scripts/Common/script_runner.py. Обрезка — новый kind: trim рядом с encode, editing, remux, fps. Чтение источника, вывод, логи и отчёт уже работают и переиспользуются.

Оценка: первые три паттерна с превью — день-полтора. Вырезание куска со склейкой и кроссфейдом — ещё день, отдельно.

Что источник может сделать с резом

Проверка на разношёрстном материале дала пять правок, которые стоит помнить:

29,97 тридцатая секунда — это кадр 899,1, а кадр 899 стоит на 29,9966, то есть внутри.

ffmpeg считает кадры от предыдущего ключевого и выбрасывает те, что до запрошенной точки, — кусок выходит короче ровно на этот зазор.

устойчив для потока, но не выводится из has_b_frames, поэтому он измеряется: кадры пересчитываются, и при недоборе рез повторяется один раз с поправкой.

среднее, и счёт по нему покрывает не тот отрезок (двенадцать секунд просили, шестнадцать получили). Такой источник режется по времени, о чём сказано в логе.

точки человека считаются от первой картинки, ffmpeg — от начала таймлайна потока, поэтому смещение снимается с ключевых кадров и возвращается при seek.

Отдельно про MPEG-TS: индекса в нём нет, seek интерполируется по байтам — в середине файла точно, у самого конца может промахнуться. Об этом предупреждение, а слишком короткий результат считается неудачей, а не успехом со странной длительностью.

Синхрон звука: проверено, а не обещано

Рез, который двигает одну дорожку и не двигает другую, вылезает позже всего — на монтаже, а не в логе. Поэтому синхрон меряется меткой, которая есть в обеих дорожках одновременно: белый кадр в начале каждой секунды и щелчок 20 мс в тот же момент. Видео читается через signalstats с fps_mode passthrough (иначе фильтр дублирует кадры под свои часы), звук — по сэмплам; каждая вспышка сопоставляется со своим щелчком по фактическим меткам времени файла.

Отклонение от исходника после реза:

КонтейнерИзменение
MP40…2 мс
MOV0…5 мс
MKV1…3 мс
MXF0…5 мс (после правки ниже)

Проверены все режимы — убрать начало, убрать конец, оставить середину, разделить надвое — на AAC, PCM 24 бит и ProRes, включая выбор канала (там звук пересобирается).

MXF отдельно. Сначала он сдвигал звук на 9–18 мс, и это было неприемлемо: MXF — рабочий формат Premiere. Замер по сэмплам показал причину: при копировании PCM в MXF в файл попадают 384 сэмпла между границей чанка и точкой реза — 8 мс звука, который принадлежит моменту до реза. Если тот же PCM переписать той же разрядности, кодировщик режет ровно по запрошенному сэмплу, и звук совпадает с эталонной нарезкой байт в байт. Так и сделано: MXF теперь меряется наравне с MOV, а в логе сказано, что перезапись была (PCM→PCM поколение не тратит).

Сжатый звук в MXF не попадает вовсе: мультиплексор отказывает и AAC, и AC-3, и MP3, и FLAC, и ALAC. Такой файл пропускается до запуска с объяснением и подсказкой (MOV возьмёт как есть), а не падает с кодом 4294967274.

Проверка на настоящем материале

Canon MVI_*.MP4: HEVC Rext 4:2:2 10 бит UHD, 23,976, звук LPCM pcm_s16be (twos), таймкод 15:58:57:17. Прогнаны все режимы — кадры точны везде, разделение дало 144 + 170 = все 314 кадров исходника, таймкод сдвинулся верно, а звук нашёлся в исходнике дословно: кусок начинается на 48 сэмплов (одна миллисекунда, сороковая доля кадра) после точки реза, то есть скопирован, а не пережат.

Нашлись две вещи, которых синтетика показать не могла:

в боксе mdta, и -map_metadata 0 их не переносит — мультиплексор писал общий isomiso2mp41. Добавлен -movflags use_metadata_tags, после чего теги сверены с исходником по одному.

те же сэмплы, переставленные байты, и другой тег в файле. Теперь порядок байт берётся из источника.

Что осталось на второй этап

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

Чего не делаем

исправлено втихую.

Все проекты