Кадр для окна пишется в кэш проекта, а кэш заводится при первой сборке
вставок. Кадрирование открывают и раньше — тогда ffmpeg падал с «No such
file or directory», а окно показывало «Не удалось получить кадр» вместе с
цепочкой фильтров. Выглядело так, будто дело в цепочке; дело было в
отсутствующей папке.
Замерено на том же файле и той же цепочке: в существующей папке команда
отдаёт кадр в 320 КБ, в несуществующей — код выхода 254.
Папка теперь создаётся перед записью. Не создалась — кадр пишется во
временный файл, чтобы окно всё равно показало картинку.
Проверка tests/cropdialog.sh: проект без папки кэша, щелчок по кнопке
кадрирования, смотрим, появился ли файл кадра. Папка убирается прямо
перед щелчком: иначе её успевает создать заготовка чёрной вставки, и
проверка меряет не то. На прежней сборке падает.
Клип, не заполняющий кадр, стал уходить плееру отдельной дорожкой, и
весь показ пошёл через цепочку фильтров. Цепочка работает всё время
показа и считается процессором: на кадре 2688x1512 видео начало
отставать от звука.
Теперь плееру достаются только прозрачные клипы и клипы с ключом — ради
них это оправдано, их участки бывают длинными, и пересобирать минуты
материала дорого. Клип, который просто не заполняет кадр, сводится
заранее вместе с тем, что под ним: предпросмотр играет обычный файл и
показывает ровно то, что даст сборка.
Попутно, из разбора того же случая:
— заполнитель до первого наложения нарезается по длине файла. Одной
записью mpv её обрезает («entry 0 uses 7082.687000 seconds, but file
has only 15.021000»), дорожка оказывается короче монтажа и не
совмещается с ним по времени;
— появление заготовки вызывает перезагрузку списка: строка списка при
этом не меняется, и без признака готовности плеер так и оставался без
дорожки наложения;
— в журнал пишется, сколько окон наложения и готовы ли файлы, а также
собранная цепочка показа и неудача video-add. Без этого «картинки
нет» нечем объяснить.
В заголовке окна теперь номер сборки: nlve 42 — фильм.nlve. Он растёт на
единицу при каждой сборке. По нему сразу видно, та ли это сборка, где
правка уже есть, — сегодня время ушло на то, что проверяли старым
двоичным файлом. Счётчик в build.number, в репозиторий не входит.
Проверено на монтаже с картинкой на 300-й секунде: в окне она лежит
поверх видео, цепочки показа нет.
В окне картинки не было вовсе, хотя в ролик она попадала. Плеер получал
сам файл, а его декодер на этом webp спотыкался: «webp: invalid TIFF
header in Exif data» — накладывать стало нечего. У неподвижного кадра
есть и вторая беда: в списке кусков у него нет длины.
Теперь наложению отдаётся заготовка — обычное видео нужной длины,
сделанное тем же путём, что и остальные вставки, только цепочкой без
полей: поля тут вредны, чёрное закрыло бы нижнее видео. Масштаб уже
внутри куска, плееру остаётся поставить его на место.
Заготовки наложения собираются отдельным списком: уплощение ниже
заполняет список недостающего заново и затёрло бы их.
Проверено на том самом файле: в окне картинка легла поверх видео, в
ролике слева нижнее видео (253, 0, 0), справа картинка. Числовые
проверки в flattest: кусок собирается слоями, у верхнего слоя полей нет,
предпросмотру отдан кусок из кэша, и он заказан к созданию.
Уменьшенная и сдвинутая картинка выходила в ролик с чёрными полями, и
нижнюю дорожку закрывал не сам клип, а эти поля. Так же вело себя и
уменьшенное видео. Между тем уменьшают клип ровно затем, чтобы под ним
было видно другое.
Теперь «не заполняет кадр» означает то же, что «не сплошной»: наравне с
выбитым цветом и прозрачностью. Такой клип уходит в накладываемые —
предпросмотр рисует его поверх нижнего силами плеера, сборка собирает
участок слоями, где у верхнего слоя цепочка без полей и место, куда он
встаёт. Класть не на что — поля остаются полями, как и были.
Решение одно на оба пути: IsOverlayClip, и в нём TransformFillsFrame
считает по тем же числам, что уходят в фильтр.
Заодно две вещи, всплывшие по дороге:
— у картинки своя ветка уплощения, и слои там не строились вовсе: клип с
прозрачностью в окне накладывался, а в ролике нет. Теперь строятся.
— в сборке ветка «готовая вставка» стояла раньше ветки слоёв, и кусок со
слоями уходил в неё за несуществующей заготовкой. Заготовка для такого
куска больше и не заказывается: это были бы минуты впустую.
Проверка tests/imagecrop.sh получила второй случай: под картинкой видео,
вокруг неё обязано быть видно его. На прежней сборке там чёрное.
Наезд двух клипов на одной дорожке — заказ на переход, то есть обычное
дело. Нажатие же всегда попадало в верхний по списку, и нижний клип
нельзя было ни выделить, ни потащить: чтобы до него добраться,
приходилось сперва убирать верхний.
Правило такое. Нажатие берёт тот из накрытых клипов, который уже выделен,
иначе верхний. Щелчок без движения по уже выделенному переводит выделение
на следующий, после последнего снова на верхний. Переключение сделано на
отпускании намеренно: пока кнопку не отпустили, неизвестно, щелчок это
или начало протяжки, а менять клип под рукой посреди протяжки нельзя.
Благодаря этому протяжка тащит именно то, что перед ней выбрали
щелчками.
Строка состояния говорит, сколько клипов под указателем: перебор, о
котором не сказали, — тайное знание.
Отбор вынесен в TTrack.ClipsAt, числовая проверка — в lfmcheck.
tests/pickcycle.sh щёлкает дважды и тащит: на прежней сборке уезжает
верхний клип, а нижний едет за ним схлопыванием.
Окно «Кадрирование и поворот» показывало картинку уменьшенной и
сдвинутой, а в кадре она шла во весь экран. В уплощении преобразование
ставилось только обычному видео: у картинки своя ветка, и там его не
было вовсе. Кусок из картинки делался по жёсткой цепочке «вписать целиком
плюс поля», про клип она ничего не знала.
Кусок и так делается заново, поэтому цепочка запекается прямо в него —
тем же TransformFilter, которым считают видео и само окно. Разными путями
считать одно и то же нельзя: окно обещает ровно эту строку и показывает
её текстом.
Кадрирование входит в имя файла заготовки: одна картинка с разным
увеличением — разные куски, иначе второй клип получил бы из кэша чужой.
Для этого у куска заведено отдельное поле GenFilter: Filter применяется к
исходнику при пересборке, и смешать их значило бы кадрировать дважды.
Проверки: flattest сверяет, что заявка несёт цепочку и отдельный путь;
tests/imagecrop.sh проходит всю дорогу — делает картинку, собирает
секунду ролика и смотрит на точки готового кадра. На прежней сборке
картинка выходит во весь кадр, обе падают.
На монтаже рисовался косой крест через целый клип, строка состояния
обещала переход, а наложения не было: ролик собирался верно. Крест уходил
сам после Ctrl+Z, после любого действия с отменой и после схлопывания.
Это был не наезд, а сбитый порядок клипов в списке дорожки. Список
отсортирован по времени начала, и соседи по списку обязаны быть соседями
по времени — на этом стоят и отрисовка креста, и сообщение о наезде.
Перетаскивание же двигает не одну дорожку: связка ведёт звук за
картинкой, схлопывание ведёт соседей, в режиме «все дорожки» — сразу на
нескольких. Сортировалась только та дорожка, с которой тащили. Клип,
уехавший с конца монтажа в середину, оставался в списке чужой дорожки
последним, и её соседями по списку становились клипы, далёкие друг от
друга по времени.
Отмена и схлопывание лечили ровно потому, что пересортировывают списки.
Сборка всё это время была права: она идёт по границам и сортирует
дорожку сама.
Теперь порядок возвращается всем дорожкам, чьи клипы поехали
(SortMovedTracks). Оба места, считающие соседей по списку, заодно
пропускают пару, идущую не по времени: страховка от того же симптома в
другом месте.
Проверка tests/dragorder.sh тащит связку за звук с конца монтажа в
середину и читает порядок из сохранённого файла. На прежней сборке
дорожка картинки остаётся в порядке 0, 14, 7.991 — проверка падает.
Клип прилипал к концу соседа, а потом округлялся к ближайшему кадру — и
уезжал с той самой границы, к которой только что прилип. Границы клипов
в монтаже лежат не на сетке: они приходят из исходников. Влево это наезд
в семь миллисекунд: ноль пикселей на экране, а в модели заказ на переход.
Человек ищет, откуда взялся кроссфейд там, где ничего не накладывается.
Замерено: дыра, раздвинутая ровно под клип, давала после перетаскивания
наезд 0.007383 с слева и дыру такого же размера справа.
Теперь решение одно и принимается один раз: прилипло — ставим ровно
туда, не прилипло — на ближайший кадр (PlaceTime). Тем же путём идут и
оба растягивания за край.
Схлопывание больше не округляет каждого соседа по отдельности: все едут
на точную величину сдвига, и расстояния между ними остаются как были.
Иначе связка разъезжалась — картинка садилась на границу, а её звук
уезжал на полкадра.
Сообщение о наезде меряет той же мерой, что и сборка: кусок короче
половины кадра она пропускает, и пугать переходом из него незачем.
Проверка tests/dropgap.sh, на прежней сборке падает.
Три жалобы из настоящей работы над фильмом, и все про одно: положить
кусок туда, куда нужно, было нельзя.
Файл из списка падал в конец дорожки, стоило месту под ним оказаться
занятым, — а занято оно почти всегда: клип длиной в целый файл упирается
хвостом в то, что правее, даже когда начало легло в пустоту. Теперь
монтаж раздвигается: MakeRoomAt режет все дорожки в точке вставки и
сдвигает всё, что начинается с неё и правее, на длину нового клипа. Звук
и надписи едут вместе с картинкой, иначе синхрон рассыплется.
Точка вставки при этом округляется к кадровой сетке. Без этого вставка
давала наезд в пять миллисекунд: рез внутри раздвижки округляет время
сам, а клип клался на неокруглённый курсор. Отсюда и был «кроссфейд без
видимого перекрытия».
При перетаскивании у кромки окна монтаж прокручивается сам: иначе при
большом увеличении кусок упирается в край, и переносить его приходится в
несколько приёмов. Прилипание заодно считается и по концу клипа — иначе
клип встаёт началом в дыру, а хвостом на соседа, и это снова наезд.
Тяжёлая сборка больше не начинается с первым движением: список кусков
пересобирается сразу, а ffmpeg запускается, когда монтаж постоял тихо
полторы секунды и клип никто не держит мышью. Сам наезд перестал быть
сюрпризом — строка состояния называет дорожку, величину и клипы.
Проверки числами: раздвижка монтажа и прилипание хвостом. С окном:
insertat.sh, dragscroll.sh, genquiet.sh. Все три прогнаны и на прежней
сборке, где падают: клип в конце дорожки вместо курсора, 0.667 с вместо
4.238 с, сборка через 0.20 с вместо 1.62 с.
genrestart.sh закрывал все окна редактора разом, включая работу
пользователя на рабочем экране. Закрывает теперь только свои — те, что
живут на экране проверки.
Он же падал по всем случаям, потому что съёмки, на которую смотрит его
монтаж, на диске больше нет: ffmpeg открывать нечего, и прерывать нечего.
Теперь сразу называет ненайденные файлы и не притворяется ни пройденной,
ни сломанной.
zoomanchor.py не передавал XAUTHORITY своим командам: экран, поднятый
через xvfb-run, пускает только по своему файлу доступа.
Показатель висел на пяти процентах, потом прыгал к семидесяти девяти,
потом сразу готово. Виноват не ffmpeg: замерено, что он отдаёт ход
безупречно ровно — блок каждые полсекунды, 7%, 14%, 20%, 27% и дальше
без единого скачка.
Блок заканчивается строкой «progress=continue», в которой места нет:
она сообщает только, что работа продолжается, и разбор честно оставляет
переданное число как было. Я передавал туда ноль, поэтому каждый блок
заканчивался обнулением доли. Показатель стоял и дёргался вверх лишь
тогда, когда опрос попадал между строкой со временем и этой.
Теперь отсчёт начинается с уже известного места — как это давно делает
сборка ролика. Проверено глазами на невидимом экране: шесть снимков
подряд через полторы секунды дают 54, 65, 74, 82, 91, 99 процентов,
полоса растёт вместе с числом.
Ловушку закрепляет проверка в tests/lfmcheck: «progress=continue» и
«progress=end» узнаются, но времени не трогают.
Зона захвата совпадала с нарисованным квадратиком — девять точек на
девять. Пока затухание нулевое, ручка стоит в углу клипа и целиться есть
куда; стоит её сдвинуть, она висит посреди клипа сама по себе, и попасть
в неё почти нельзя.
Зона захвата теперь шире нарисованного: по шесть точек в стороны и семь
вниз, внутрь клипа, — рука уводит курсор туда, а не выше клипа. Если на
узком клипе зоны обеих ручек налезли друг на друга, берётся та, к которой
ближе.
Заодно над зоной меняется курсор. Раньше над ручкой он был такой же, как
над всем клипом, и где она — узнать было нельзя.
Проверено протяжкой мыши на невидимом экране (tests/fadegrab.sh): первый
захват за угол даёт затухание 1.387 с, второй — с прицелом на пять точек
ниже и четыре в стороне от середины ручки — доводит до 2.261 с. На сборке
из прошлого коммита второй захват не делает ничего.
Полоса ползла по догадке, а подпись показывала замер — и человек видел
полосу, заполненную на девять десятых, рядом с нулём процентов. Что в
этот момент происходит, по такой паре понять нельзя.
Теперь доля одна на обоих. Берётся она у самого ffmpeg: сборка участка с
затуханием идёт минуту и дольше, а «кусок 1 из 1» всё это время не
меняется, поэтому ffmpeg спрашивают ключом -progress, докуда он дошёл.
Пока он не ответил ни разу, полоса трогается с места на пару процентов и
там стоит — пустая полоса и есть правда о только что начатой работе.
Догадка заодно перестала закрывать промежуток до вехи целиком: предел —
девять десятых, остаток виден глазом. Проверка на это добавлена в
tests/progress.
Полоса в окне сборки была системной, и под gtk2 заполнялась ступенями по
восемь кусков на ширину. Теперь там та же своя полоса, что и внизу
главного окна, — заполняется сплошь. Чтобы она читалась из файла формы,
у неё опубликованы Anchors, Align и Visible: читатель формы берёт только
опубликованные свойства, и без этого окно падало при открытии.
Проверено глазами на невидимом экране: «Собираю наложение 1 из 1 — 2%»
при полосе в 2%, через четыре секунды 69% при полосе в 69%.
Затухание в полторы-две минуты собиралось минутами, и всё это время
предпросмотр ждёт. Виноваты оказались не фильтры: на десяти секундах
2688x1512 цепочка со сведением стоит 13.5 с, а вовсе без фильтров —
12.7 с. Время уходит на разбор HEVC процессором.
Ключ -hwaccel cuda перед входом убирает почти всё: настоящее затухание
на семьдесят секунд собирается 18.3 с вместо 75.9 с, картинка бит в бит
та же (одинаковый md5 потока, 4196 кадров там и там).
Просится он только там, где кодировать будет та же карта — то есть
выбран NVENC. На машине без неё ключ не появляется вовсе. Сверх того у
подготовки вставок есть откат: не пошло — повторяем на процессоре, как
это уже делает склейка с перекодированием.
Сборка ролика пока по-старому: там кусок с наложением идёт в общий
поток, и повторить его после отказа посреди работы нельзя — данные уже
ушли в трубу. Это отдельная задача.
Затухание в семьдесят секунд не собиралось никогда: сборка сведённого
куска шла полторы минуты, и любая правка в это время сбрасывала её на
ноль. В журнале это видно как «Расстановка изменилась» и тот же самый
запуск ffmpeg по кругу.
Причина — в вопросе, который задавался. «Нужна ли ещё эта работа»
проверялось по списку недостающего, а недостающее у нас — это то, чего
нет на диске. У куска, который делается прямо сейчас, файл уже есть:
ffmpeg создаёт его в первую же секунду. Кусок в список не попадал, ответ
был «не нужна», работа отменялась.
Теперь уплощение отдельно отдаёт список нужного — независимо от того,
лежит файл на диске или ещё пишется. Заодно закрыта та же дыра со
вставками тишины: проверка их вообще не видела, потому что звуковые
дорожки считаются отдельным проходом.
Проверено обеими сторонами (tests/genrestart.sh, на настоящем материале,
затухание 70 с): пять резов в стороне от затухания — один запуск и ни
одной отмены, было шесть запусков и десять отмен. Удалили всё — работа
прекращается за две секунды, как и прежде.
Клип со своим кадрированием играл в окне во весь кадр: цепочка фильтров
у mpv одна на весь список кусков, а включить её на одном куске и
выключить на следующем нечем — из фильтров ffmpeg по времени
переключается только наложение.
Обошлось без цепочки вовсе. У вывода mpv своя обрезка, и делает её
видеокарта: курсор зашёл в клип с кадрированием — выставили, вышел —
сняли. Ни перестройки цепочки, ни копирования кадров из памяти
видеокарты в обычную.
Рамку источника считает теперь один код на две стороны: по ней окно
«Кадрирование и поворот» рисует рамку, и её же плеер отдаёт как обрезку.
Проверка в tests/lfmcheck сверяет рамку с фильтром сборки: увеличение
2.77 даёт 970x546 посередине, и это ровно то, что вырезает
crop=2688:1512:2379:1338 после scale=7446:4188.
Проверено глазами на невидимом экране, на настоящем материале с дрона:
картинка в окне совпала с готовым роликом, на границе с обычным клипом
обрезка снимается и возвращается. Сверено со сборкой из прошлого
коммита — там на том же месте кадр без кадрирования.
Не покрыто и записано в BACKLOG: отражение (свойства для него нет),
растяжение и участки с наложением — там кадр уже сведён цепочкой.
Ctrl с колесом должен оставлять на месте то, на что смотрит мышь, а
оставлял точку левее — ровно на левый край таймлайна. При мыши у левого
края опора и вовсе уходила за пределы окна.
Причина: MousePos у DoMouseWheel по описанию экранный, но под gtk2 в нём
уже клиентские координаты, и ScreenToClient переводил их второй раз.
Берём Mouse.CursorPos — он экранный в любой системе.
Замерено на невидимом экране: до правки мышь в 700, 1000 и 1300 давала
опору в 260, 560 и 860; после — ровно 700, 1000 и 1300. Проверка
оформлена в tests/zoomanchor.py и прогнана на обеих сборках: на прежней
она падает, на новой проходит.
Потоком кэша кусков нет, и продолжать было не с чего. Но недописанный
файл сам по себе — уже собранная часть фильма: в нём видно, сколько
целых кадров успело записаться. Программа отступает до границы
последнего целого куска, переносит эту часть и досылает остаток.
Дописать в конец существующего MKV нельзя, поэтому продолжение пишется
в новый файл. MP4 продолжать нельзя вовсе: оглавление в конце, и
оборванный файл не открывается — об этом говорится прямо.
Готовый файл теперь цел до последнего: сборка идёт в файл рядом и
занимает место готового, только когда дописана. Сорвавшаяся попытка не
отбирает уже собранное — из двух недописанных остаётся тот, где кадров
больше, и считаются они даже после запроса остановки.
Отдельно пришлось снять сдвиг, который приёмник накладывает на весь
ролик: перенесённая часть несла его в себе, и куски за ней задваивали
кадр. Замерено: обрыв в четырёх разных местах, продолженная сборка
каждый раз совпала с собранной за один раз попиксельно.
В nlvetool у render появились --cancel-after=<сек> и сочетание слов:
cache resume проверяет, что старый путь через кэш не сломался.
Раньше каждый кусок монтажа вырезался в отдельный файл, и только потом
список этих файлов шёл на склейку. Кэш весил столько же, сколько готовый
фильм — замерено 110 ГБ на часе материала, — а диск писал всё дважды.
Теперь каждый кусок режется своим ffmpeg прямо в MPEG-TS на стандартный
вывод, все потоки подряд переливаются в один принимающий ffmpeg, а тот
пишет готовый файл. Место куску задаёт -output_ts_offset, считаемый в
кадрах от начала. На диск ложится только результат и звуковая дорожка.
Через кэш по-прежнему собирается то, что потоком не выходит: кодек, не
переносимый в MPEG-TS, и пересборка всего ролика под площадку.
Сверено на четырёх монтажах, включая настоящий материал с дрона
(HEVC 2688x1512, 55 Мбит/с, файлы с шестью потоками): картинка совпала
попиксельно, PSNR бесконечен на каждом кадре, длина и размер те же.
Сверка шла и с прошлым выпуском, а не только нового пути со старым.
На отрывке в 17.5 с: 14.2 с и 421 КБ на диске против 17.5 с и 152 МБ.
По дороге найдены две ловушки, обе записаны в README:
-avoid_negative_ts make_zero отменяет -output_ts_offset, а -fflags
+igndts у приёмника сминает кадры на стыках.
Две беды у всех кнопок нижней полосы.
Всплывающая подсказка появлялась прямо поверх кнопки, о которой
рассказывает, и перехватывала щелчок: нажать кнопку было нельзя.
Подсказка переехала в строку состояния — она в той же полосе, ничего не
закрывает и меняется сразу после щелчка, не дожидаясь нового наведения.
Строка состояния теперь занимает всё свободное место справа, пока рядом
не показан ход работы, а самые длинные подсказки укорочены: раньше их
обрезало на полуслове.
Второй щелчок подряд виджетсет отдавал как двойной, и кнопка его не
видела: быстрая очередь щелчков срабатывала через раз. У кнопки убран
csDoubleClicks, а на случай, если двойной всё же дойдёт, он считается
за обычный.
Проверено: tests/lfmcheck, раздел «Кнопки нижней панели», и
tests/buttonclicks.sh мышью на невидимом экране — по всем шести
переключателям очереди из 1-5 щелчков отрабатываются полностью.
Режимов выделения стало два, переключаются клавишей D и кнопкой внизу,
режим показан её значком.
Рамкой по клипам — как было. По времени — протяжка отмечает промежуток по
всем дорожкам сразу, он остаётся виден пунктиром, и S режет все дорожки по
обоим его краям. Выделенной после этого остаётся ровно середина: следующим
действием её обычно удаляют, а выделение на старых клипах убрало бы не то.
Курсор теперь идёт за резом: после S встаёт на место реза (в режиме по
времени — на начало промежутка), после удаления куска — на место стыка,
после схлопывания дыры — туда же. Раньше в это место приходилось попадать
мышью заново.
Рез кладётся на кадровую сетку: между кадрами времени нет, и рез на доле
кадра оставлял бы в монтаже время, которого не бывает.
Выделение рамкой требует настоящего перекрытия, а не касания краями:
сосед, кончающийся ровно на краю рамки, попадал в выделение из-за разницы
в последнем знаке дробей.
Кнопка журнала и строка состояния сдвинуты вправо: переключатель выделения
встал в один ряд с прилипанием и схлопыванием.
Значки — icons8, набор ios-glyphs, авторство дописано в attribution.txt.
Набора «ios26» на их раздаче нет (404), взят ближайший по виду к нынешним.
Проверки: рез по курсору и по краям промежутка, кадровая сетка, выделение
середины, курсор после удаления и после схлопывания.
Меню «Проект» → «Проверить и починить проект...». Показывает найденное и
чинит только по согласию, отменяется через Ctrl+Z.
Ищутся клипы мимо кадровой сетки, клипы на дорожках чужого вида и
повторяющиеся имена дорожек. Первое — наследство: в старых проектах
времена записаны как попало (2.457599 с при 59.94 к/с — это 147.31 кадра),
и пока монтаж просит доли кадра, предпросмотр и сборка округляют их
каждый по-своему. Сдвиг при починке не больше полкадра.
Края отмеченного участка при сборке тоже кладутся на сетку: подрезка
возвращала в крайние куски доли кадра, которые уплощение только что
убрало.
Проверено на копии настоящего проекта: 82 клипа, после починки мимо сетки
ни одного, клипы остались встык, общая длина изменилась на 0.028 с за два
часа; Ctrl+Z возвращает как было. Плюс проверка в lfmcheck, включая клип
короче кадра — он не должен схлопнуться в ничто.
Рез всегда точный. Раньше начало куска притягивалось к ближайшему
опорному кадру, а звук брался из непритянутой точки: по журналам сборки
средняя притяжка 0.24 с, худшая 0.99 с, и на длинном куске этот сдвиг
держится все его минуты. Плата за точность — доли секунды перекодирования
на рез. Второй вид реза (Shift+S) стал тем же самым и убран.
Разбор заголовка MP4 не учитывал список правок (elst): метки опорных
кадров уезжали на задержку показа, на пробе два кадра. Сверка быстрого
пути с ffprobe теперь совпадает точно, в том числе на записи с B-кадрами.
Куску заказывается точное число кадров, а не только время: при
копировании ffmpeg отмеряет конец по времени декодирования, и у записи с
B-кадрами кусок выходил длиннее.
Склейке длительность каждого куска задаётся явно. Последний пакет
скопированного куска показывается позже прочих, контейнер считал его в
длительность, и время картинки растягивалось — 0.05 с на кусок.
Звука в кусках картинки больше нет: он не используется, а в MPEG-TS ещё и
сбивает выравнивание склейки на 36 мс.
Границы кусков ложатся на кадровую сетку: картинка выходит целым числом
кадров, звук набирается до отсчёта, и доли кадра разводили их в стороны.
Разогрев кодировщика звука компенсируется: сжатый AAC начинается со
служебных отсчётов, MP4 помечает их пропускаемыми, Matroska — нет, и звук
ехал на 21.3 мс позже. Величина меряется пробным сжатием.
Продолжение прерванной сборки чинится заодно: готовность проверяется у
каждой части, а не у куска целиком. При точном резе частей две, и вторая
при продолжении терялась.
В журнал сборки добавлена проверка синхронности: сколько кадров вышло и
сколько просил монтаж, с вердиктом и худшей частью.
Замер (tests/syncprobe.sh, исходник со вспышкой и щелчком на каждой
секунде): было до 0.6 с и росло по ходу ролика, стало 0.02-0.04 с без
накопления при точности измерения 0.017 с.
Рамка выделения: протяжка по пустому месту дорожек обтягивает клипы,
Delete убирает всё, что она задела. Щелчок без движения по-прежнему
ставит курсор.
Рез по курсору (S) режет все дорожки, а не ту, что под указателем мыши:
стоило указателю оказаться над пустой дорожкой, и клавиша молча не
делала ничего. Теперь рез отвечает числом разрезанных клипов.
Курсор после правки монтажа улетал в конец: при перезагрузке списка
кусков mpv сбрасывает отсчёт времени, программа ждала перемотки, не
дожидалась и подтягивала курсор к плееру. На паузе курсор теперь
остаётся там, куда его поставил человек. Это и делало Ctrl+Shift+R
неудобным: после каждого схлопывания приходилось целиться заново.
Схлопывание дыры ищет её с допуском и по всем дорожкам, а не только под
мышью, и называет дорожку в строке состояния.
Ответ на команду держится в строке состояния несколько секунд: раньше
его через долю секунды затирало сообщение о пересборке предпросмотра.
Проверки: рамка, рез всех дорожек, допуск при схлопывании (lfmcheck).
Отмену вставок просили мы сами, когда правка сделала работу ненужной, а
журнал сообщал «Не удалось подготовить вставки: Отменено» — неправда там,
где всё шло по плану. Теперь отмена докладывается как отмена и сразу
планирует пересборку предпросмотра, а не ждёт таймера.
Вчерашнее дробление дыр не дошло до звуковых дорожек: они уплощаются
отдельной функцией. Кусок тишины в три минуты ссылался на минутный файл, и
плеер жаловался «uses 192.5 seconds, but file has only 60». Теперь длинная
тишина набирается повторами, а одинаковые файлы не заказываются дважды.
Проверено живьём на материале 2688x1512: отмена ловится, обе строки в
журнале верные, предупреждений о коротких файлах ноль. Проверка 15 в
flattest следит за дроблением тишины.
Отбивку или текстовый блок проще размножить копией, чем заводить заново
через меню и настраивать оформление с нуля.
Копируются выделенные клипы, вставляются по курсору. Связанные берутся
вместе с парой — у картинки со звуком иначе уехала бы половина, — но
связка у копий своя: копия и подлинник не должны ездить вместе. Взаимное
расположение сохраняется, набор сдвигается к курсору.
Дорожка выбирается сама: сначала та, откуда копировали, если это время на
ней свободно; потом любая подходящая по виду; в крайнем случае заводится
новая. Наложить копию на соседа по дорожке нельзя — это означало бы
переход, которого никто не заказывал.
В модели появился TClip.CopyAllFrom: полная копия, кроме места на
монтаже. Проверка в lfmcheck перебирает перенесённые свойства и отдельно
убеждается, что место и связка не переносятся.
Дыра в сорок минут требовала двухчасового чёрного файла: лестница готовых
длин доходила до восьми часов, и NVENC кодировал такое минутами. Отсюда
«Делаю чёрную вставку 1 из 3», которое не кончается. Теперь длины две, 15
и 60 секунд, длинная дыра набирается повторами минутного файла, а само
чёрное кодируется мегабитом вместо полусотни.
Промежуток короче половины кадра дырой больше не считается: между
клипами, сдвинутыми встык мышью, оставались микросекунды. Мышь при этом
кладёт клипы на кадровую сетку — между кадрами времени не существует.
Правка монтажа прекращает генерацию, ставшую ненужной: раньше закрытая
дыра не отменяла уже начатую работу.
Ctrl+Shift+R работает без мыши: дыра ищется на активной дорожке, если
указатель не над дорожками, и команда отвечает в строке состояния, а не
молчит.
Вжигание текста режет кусок на части и пересобирает только середину.
Надпись в тридцать секунд внутри сорокаминутного куска стоила сорока
минут пересборки. Часть после надписи помечается точным резом, иначе
копия пошла бы от опорного кадра и картинка сдвинулась бы.
Проверки: flattest 13 и 14, схлопывание без мыши в lfmcheck, сборка с
надписью внутри длинного куска — 1.000000 с пересборки вместо 6.
Картинка собиралась в промежуточный файл, а потом сшивалась со звуком в
конечный. На часовом ролике это 91 ГБ в кэше плюс 91 ГБ рядом и лишний
полный проход копирования — ради того, чтобы приписать звуковую дорожку.
Теперь звук собирается раньше картинки, а склейка сразу кладёт всё вместе
в конечный файл. Тем же путём пошла и сборка с перекодированием.
Промежуточного файла нет вовсе, проход на один меньше.
Куски после удачной сборки удаляются, и в журнале сказано, сколько места
освободилось: на часовой съёмке их набирается больше сотни гигабайт, а
нужны они только чтобы продолжить прерванную сборку. После неудачной
сборки они остаются — с них и продолжают. Настройка keep_parts оставляет
их всегда.
Копирование оформления: Ctrl+Shift+C берёт с выделенного клипа
кадрирование, поворот, увеличение, смещение, прозрачность, ключ,
затухания и громкость, Ctrl+Shift+V применяет их ко всем выделенным. Вид
клипа и текст не переносятся: свойства копируют между разными клипами.
Порядок дорожек: слева в заголовке две стрелки, у крайних дорожек
соответствующая нарисована тускло и не нажимается. Двойной щелчок по
названию переименовывает дорожку — на монтаже из десятка дорожек «карта»
и «титры» читаются лучше, чем V1 и Т1. То же есть и в меню по правому
щелчку. Ввод имени — своим тёмным окном: системный InputQuery выглядел бы
белым пятном.
Окно сборки показывает, сколько прошло и сколько примерно осталось.
Оценка по пройденной доле; скорость на деле меняется, поэтому «примерно»
и пересчёт на каждом шаге.
Прерванная сборка продолжается с готовых кусков. Каждый доведённый до
конца кусок помечается в списке рядом с ними: одного наличия файла мало —
сборку могли прервать посреди записи. Первая строка списка — отпечаток
плана; не совпал, значит монтаж менялся, и всё собирается заново.
Проверено: четыре куска взяты готовыми, два пересобраны, склейка не
потеряла ни одного кадра; при изменённом монтаже готовые куски отвергнуты.
Миникарта была видна в окне, но не в ролике. Предпросмотр рисовал оверлей
поверх всего, не глядя на порядок дорожек, а сборка живёт по правилу
«верхний клип побеждает целиком»: фотография лежала выше оверлея и в
ролике его закрывала. Теперь окно считает так же — оверлей показывается
только там, где его не закрывает сплошной клип с дорожки выше, а закрытый
посередине разбивается на несколько окон показа.
Текст не появлялся в окне. Разметка лежит в файле с одним именем на весь
проект, а плеер сравнивал имена: имя прежнее — перечитывать нечего.
Правка доходила до окна только вместе с перезагрузкой списка кусков.
Теперь сравнивается сама разметка, и при изменении файл перечитывается.
Отдельно проверено, что цепочка lavfi-complex субтитрам не мешает.
Две дорожки Т2. Имя новой дорожке давалось по счёту дорожек своего вида
плюс единица, и после удаления средней счёт совпадал с занятым именем.
Имя подбирается свободное, а повторы из старых проектов разводятся при
чтении.
Проверки 11 и 12 в nlvetool flattest.
В живом проекте фотография оказалась на текстовой дорожке, а надпись — на
дорожке картинки. Уплощение обходит дорожки по видам, и такой клип не
видит ни сборка, ни предпросмотр: на монтаже он нарисован, в готовом
ролике его нет, и ни одной жалобы по дороге. Именно это и было причиной
пропавших фотографии и текста, а не ошибка со стоп-кадром.
Теперь перетаскивание на дорожку чужого вида не проходит, а файл из
списка, брошенный на такую дорожку, ложится на подходящую — с записью в
журнал, куда именно. Новая дорожка, заведённая перетаскиванием вниз,
заводится того вида, какой нужен клипу.
При открытии проекта такие клипы пересчитываются, и программа предлагает
перенести их на подходящие дорожки, сохранив время. Молча не переносим:
клип мог оказаться там осознанно, и внезапный переезд — сюрприз хуже
пропажи.
Проверка 10 в nlvetool flattest: картинка на текстовой дорожке в
уплощение не попадает, после переноса попадает и остаётся на своём месте.
Отмеченный участок. Клавиши I и O ставят начало и конец по курсору,
полоса под линейкой показывает отмеченное, сборка собирает только его.
Без этого проверить правку на длинном монтаже нельзя: каждая проверка
стоит столько же, сколько идёт весь ролик. Отметки живут в проекте.
Разметка текста при этом берётся с начала участка, иначе надписи легли бы
со сдвигом.
Фотография пропадала из ролика. Кусок с наложением подаёт слои в ffmpeg
как есть, а одиночная картинка даёт ровно один кадр: кусок в три секунды
получался длиной 0.016667 с. Теперь у слоя есть признак «это картинка», и
она повторяется ключом -loop с частотой кадров проекта.
Текст пропадал там же. Вжигание помечает кусок разметкой, но ветка
наложения про неё не знала. Пока оверлей лежит на всём ролике, этой
веткой идут все куски — и текста не было нигде. Теперь разметка вжигается
последним звеном цепочки, поверх сведённой картинки.
Качество пересобранных кусков. Битрейт не задавался вовсе, и кодировщик
брал своё значение по умолчанию: 5.9 Мбит/с там, где исходник 56. Теперь
пересборка целится в наибольший битрейт среди файлов проекта, с потолком
в полтора раза выше и буфером вдвое. Тем же битрейтом делаются вставки из
фотографий.
Проверки: обрезка списка кусков по участку (nlvetool flattest, 9),
сборка фотографии под наложением и вжигание текста поверх наложения
(nlvetool render ... burn).
После разреза хвост оставался без цветового ключа: перенос настроек был
написан перечислением полей, а ключ появился позже и в перечень не попал.
Хвост переставал быть наложением, становился обычным победителем и
закрывал собой всё, что ниже — вместо компаса в углу окно показывало
пурпурную плашку во весь кадр.
Перечисление заменено на CopySettingsFrom: одно место, где сказано, что
переносится при копировании клипа, а что делает клип этим клипом. Иначе
та же ловушка досталась бы следующему свойству.
Проверка в lfmcheck: рез клипа с ключом, прозрачностью, громкостью и
кадрированием — всё остаётся у обеих половин, связки у них разные и
непустые.
Проекты, разрезанные прежней сборкой, правка задним числом не чинит:
у хвоста ключ надо включить заново.
Курсор воспроизведения по щелчку вставал ровно туда, куда попала мышь.
В стык двух кусков так не попасть: на общем плане одна точка экрана —
это секунды материала, а ставят курсор чаще всего именно на стык.
Теперь он притягивается к границам клипов на любой дорожке и к началу
монтажа. Работает и при щелчке по клипу, и по линейке, и по пустому
месту, и при протяжке. Shift меняет решение на противоположное — как у
прилипания клипов.
Переключатель свой, со значком клея, рядом с магнитом: клипы липнут при
перетаскивании, курсор — при щелчке, это разные привычки, и одно бывает
нужно без другого. Состояние переживает перезапуск.
Расчёт открыт наружу и проверяется числами: семь проверок в lfmcheck,
от «стык рядом — притянуло» до «выключено — не притягивает».
Клипу задаётся цвет, который при сборке становится прозрачным. Окно
ключа показывает кадр: цвет берётся щелчком по нему, выбитое показано
клеткой, допуск и смягчение края — ползунками. Открывается из окна
кадрирования и из меню «Правка», на монтаже клип помечен квадратиком
выбиваемого цвета.
Предпросмотр перестал сводить такие участки в файлы. Плеер играет две
дорожки одна поверх другой: оверлей во весь ролик больше не заставляет
пересобирать весь фильм после каждой правки. Замерено: наложение стоит
0.4 с на 10 с материала 2688x1512, вчетверо быстрее реального времени.
Починено в сборке: уменьшенный клип обкладывался чёрными полями, и они
закрывали собой нижнее видео — оставалась видна только дырка, выбитая
ключом. Теперь верхний слой кладётся по координатам, тем же, по которым
его показывает предпросмотр.
Смещение по горизонтали и вертикали работает и там, где картинка меньше
кадра проекта: раньше уменьшенный клип нельзя было сдвинуть в угол.
Проверки форм теперь создают каждое окно из файла формы: элемент,
забытый в классе, вскрывается на сборке, а не при открытии окна.
ffmpeg умеет рисовать текст, но не тем фильтром, о котором думают.
drawtext в местной сборке отсутствует: с версии 7.1 ему нужен harfbuzz.
Зато есть libass — и он для задачи лучше. Проверено отрисовкой: цвет,
обводка, тень, шрифт по имени, размер, поля и девять положений. Девять
положений — родное понятие ASS, ровно та сетка, которую просили.
Чего libass не делает — не уменьшает шрифт, чтобы текст влез. Это считаем
мы: сами переносим по словам и сами подбираем размер, а libass получает
готовые переводы строк, чтобы нарисовать ровно то, что мы померили.
Размер ищется делением отрезка пополам. Прикидка «уменьшить во столько же
раз, во сколько не влезли» давала слишком мелко: после переноса строк
становится меньше, и места остаётся вдоволь — на длинной надписи вместо
29 помещался 37. Замер готового блока: ровно 144 точки при разрешённых
144, то есть пятая часть кадра 720p.
Текст живёт на своей дорожке, как звук: правило «верхний клип побеждает»
на него не распространяется, он лежит поверх и ничего не закрывает.
Предпросмотр ничего не стоит. mpv рисует внешний файл ASS поверх нашего
виртуального списка кусков — правка текста видна сразу, без пересборки
участка, в отличие от прозрачности. Подключение сделано в двух местах:
при смене файла и при загрузке нового списка кусков, потому что тогда mpv
забывает все внешние дорожки.
Редактор: текст, шрифт из установленных в системе, размер, три цвета
образцами, толщина обводки и тени, сетка три на три, доля высоты. Рядом
сказано, во что превратится надпись, — считает тот же код, что и сборка
разметки.
Замер ширины подключается снаружи: точный требует холста, а холст —
оконного набора. Служебная программа без экрана обходится оценкой, окно
ставит точный. Из-за этого же из модели убран TColor: она не должна
тянуть за собой оконную библиотеку.
Сборки ролика с текстом пока нет — это следующий этап, он расписан в
IMPLEMENTATION_PLAN.md.
Двойной щелчок по кнопке журнала. Причина оказалась не в кнопке: журнал
лежал ниже полосы состояния. Спрятали журнал — полоса уехала вниз на сто
тридцать пикселей, кнопка ушла из-под курсора, и следующий щелчок в то же
место попадал уже мимо. Порядок исправлен: полоса состояния теперь самая
нижняя, журнал над ней, и при переключении ничего не движется.
Прилипание, схлопывание и журнал стали квадратными кнопками со значками и
собраны в один набор. Надписи переехали в подсказки — целиком, вместе с
состоянием: у значка нет другого способа рассказать о себе. Подсказка
всплывает через десятую долю секунды и держится пятнадцать.
Значки рисуются кодом, а не берутся из файла: программа не должна
зависеть от того, лежит ли рядом с ней картинка. Рисунки придуманы в
icons/*.svg — оттуда их можно перегнать в png чем угодно, проверено
командой convert.
Свойства из файлов форм теперь проверяются отдельной программой по
сведениям о типах. Файл формы разбирается при запуске, а не при сборке:
опечатка в имени свойства или в значении перечисления даёт не ошибку
компиляции, а падение окна на старте. Проверка включена в build.sh.
Полоска выполнения теперь показывает всё, что делается в стороне от
окна: сборку наложений и затуханий, разбор звука, миниатюры. Раньше она
знала только про звук и миниатюры, а самое долгое — пересборку участков
с прозрачностью — не показывала вовсе.
Как она растёт. Настоящая доля известна только вехами: вставка готова,
файл разобран. Между вехами не происходит ничего видимого, и полоска,
стоящая на месте, выглядит как зависшая программа. Поэтому между вехами
она ползёт сама — к следующей вехе, но никогда её не достигая: каждый
шаг покрывает малую долю оставшегося расстояния. Работа идёт как
задумано — веха приходит быстро и полоска прыгает к ней; затянулась —
шаги мельчают, и заполнение само собой замедляется. Назад не идёт
никогда, полной без настоящего конца не становится.
Расчёт вынесен в uAppTypes отдельной функцией именно затем, чтобы его
можно было прогнать числами, и прогоняется: проверки в
tests/progress/progtest.lpr включены в build.sh. Замер: до половины пути
десять шагов таймера, до трёх четвертей — ещё десять.
Участок монтажа, который сейчас пересобирается, помечается косой
штриховкой во всю высоту дорожек. Сплошная заливка закрыла бы клипы, а
рамка на узком участке была бы неразличима; штрих наклонный, чтобы его
не спутать с курсором и границами клипов — те все вертикальные.
Кнопка «Журнал» прячет и показывает нижнюю полосу с сообщениями,
состояние переживает перезапуск.
Системный TStatusBar не взят намеренно: его рисует система, и под
Windows он остался бы светлой полосой — ровно та беда, из-за которой уже
переписаны кнопки, полосы прокрутки, журнал и меню. Всё сделано на своей
нижней панели, теми же средствами.
Плеер играет список кусков, а список накладывать не умеет: там побеждает
верхний клип целиком. Поэтому участки с прозрачностью и переходами
заранее сводятся в отдельные файлы кэша, и предпросмотр играет уже их.
Файл считается один раз, имя выводится из описания слоёв — при следующем
открытии ждать нечего.
Считает их тот же код, что и сборка ролика. Разными путями считать одно
и то же нельзя: предпросмотр разошёлся бы с результатом.
Для сборки подстановка не применяется: там наложение считается заново с
параметрами вывода, и промежуточный файл был бы лишней потерей качества.
Проверено на монтаже с клипом в 50% прозрачности, где оба слоя из одного
файла: кадр наложения ближе к полусумме двух слоёв (отклонение 13.8), чем
к любому из них по отдельности (26.0 и 18.4).
Попутно найдено и исправлено: наложения кодировались процессорным x265,
потому что ffmpeg выбирает кодировщик по имени кодека, а мы передавали
«hevc». Замерено: 9.6 с материала за 11.2 с работы. Теперь и здесь
берётся предпочтительный кодировщик, аппаратный.
В служебную программу добавлена команда preview: список кусков ровно
такой, каким его видит предпросмотр, вместе с готовыми наложениями.
Затухания. Механика была написана давно и работала в сборке, но увидеть
или задать её было нечем. Теперь у клипа два верхних угла — ручки:
левая тянет появление, правая уход. Взятое затухание рисуется срезанным
углом, так это устроено в монтажных программах. Ручки перехватывают
нажатие раньше растягивания за край: они мелкие, промахнуться мимо них
легче, а взяться за край можно ниже.
Перекрытие двух клипов одной дорожки — это переход, и он теперь помечен
косым крестом во всю высоту. Раньше о нём можно было узнать только по
предупреждению перед сборкой.
Сборка с перекодированием. Отдельный пункт меню и отдельное окно:
размер кадра от 720p до 4K, поток в килобитах, кодек HEVC или H.264.
Перекодирование идёт одним проходом на весь ролик — пересобирать куски
по отдельности нельзя, на стыках была бы видна смена качества.
Кодирование на видеокарте, и распаковка тоже: замерено на паре кусков
2688x1512 — 9.18 с на процессоре против 1.77 с с аппаратной распаковкой,
впятеро. Полностью карточный путь через scale_cuda оказался медленнее
(5.74 с) и не взят. Если аппаратная распаковка не пойдёт, сборка
повторяется без неё: медленно лучше, чем никак. Окно честно
предупреждает, когда аппаратного кодировщика в ffmpeg нет вовсе.
Журнал в окне сборки заменён на свой: у системного поля оставались
светлые полосы прокрутки и не работала прокрутка вбок с Shift. Это было
последнее такое место.
Звук пропадал кусками, и причина оказалась не в громкости и не в
раскладке дорожек: обе были верны. Дыры на звуковой дорожке заполнялись
тем же чёрным клипом, что и дыры на видеодорожке, а опорные кадры в нём
стоят через 4.171 с. Перемотка внутри такой вставки откатывается к
ближайшему опорному кадру, дорожка начинает отдавать звук на несколько
секунд раньше соседних, смешивание равняется по ней — и содержимое
соседней дорожки в это окно не попадает.
Замерено на файле, без запуска окна. Смешивание трёх дорожек проекта:
старт 0 с: -23.3 dB старт 24 с: -91.0 dB (полная тишина)
старт 12 с: -10.1 dB старт 30 с: -18.0 dB
При этом та же дорожка A2 отдельно на 24-й секунде даёт -11.7 dB, то
есть содержимое на месте. Смешивание A2 с A4 работает, A1 с A2 — нет:
у A1 в этой точке идёт длинная чёрная вставка, и перемотка в ней
откатывается на 3.1 с назад.
Теперь у звуковых дорожек своя вставка — anullsrc в звуковом кодеке
проекта, без видеопотока. В звуке опорным является каждый кадр, и
отката нет. После правки во всех проверенных точках звук есть:
0 с -24.1 7 с -19.0 12 с -11.9 18 с -17.5 24 с -11.7
25 с -9.6 30 с -9.5 40 с -15.6 60 с -19.5 72 с -15.9
Сборка ролика от этого не страдает: она декодирует куски в отсчёты, и
файл без картинки ей подходит так же.
Заодно в служебную программу добавлена команда audio: раскладка
звуковых дорожек проекта на куски вместе со строками edl://. Ею это и
было найдено.
Первая: при выключенных дорожках картинки не играло вообще ничего. Поток
картинки опустевал, уплощение возвращало неудачу, и предпросмотр
останавливался вместе со звуком, которого выключение не касалось.
Теперь длина берётся по обоим видам дорожек сразу, и недостающее
заполняется чёрным и тишиной. Оба потока всегда одной длины.
Вторая: звук был слышен там, где на монтаже его нет. mpv добавляет
внешний список кусков ещё одной дорожкой, а какую играть, решает сам, и
мог взять звук, встроенный в саму картинку.
Теперь после загрузки внешняя дорожка выбирается явно. Если её не
оказалось, звук выключается совсем: играть вместо него звук из картинки
нельзя — на монтаже его там нет.
Проверено: при выключенных дорожках картинки получается чёрный кадр и
сохранившийся звук (-21 dB); внешний список кусков выбирается верно и в
ту, и в другую сторону.
У дорожки появился вид — картинка или звук, у клипа принадлежность к
связке. Проект заводит две дорожки картинки и две звука; при добавлении
файла со звуком клип кладётся парой, связанной общим номером.
Уплощение теперь делается двумя проходами: отдельно для картинки,
отдельно для звука. Одним проходом такое не выразить — в один и тот же
момент картинка берётся с одной дорожки, а звук с другой.
Сборка соответственно тоже раздваивается. Картинка собирается прежним
способом, копированием. Звук собирается в сырой поток и сжимается одним
проходом: куски дописываются друг за другом байтами, и длина каждого
считается из длительности, а не берётся у ffmpeg. Поэтому стык получается
с точностью до отсчёта, а не до звукового кадра, и звук не уезжает от
картинки. Тишина в дырах пишется нулями, без запуска ffmpeg вовсе.
Готовые потоки сшиваются копированием.
Предпросмотр получает второй список кусков как внешнюю звуковую дорожку.
Проекты, записанные раньше, переносятся при чтении: звук там шёл вместе с
клипом картинки и без пары просто пропал бы.
Проверено сборкой:
- картинка из молчащего синего файла 0..6 с, звук из другого файла с 2 по
6 с. В результате тишина первые две секунды и тон дальше — звук встал
туда, куда положен, а не туда, где картинка;
- обычный парный случай: клип с 2 по 6 с, тишина до него и тон в нём;
- проверки уплощения пройдены.
Куски видео вырезаются и склеиваются копированием потока: сборка ролика
занимает время копирования файла, а не время рендера. Композитинга нет
принципиально — при перекрытии побеждает клип с верхней дорожки целиком,
и картинкой, и звуком. Именно это ограничение и делает сборку копированием
возможной.
Что внутри:
- монтажный стол со своей отрисовкой: дорожки, масштаб, прилипание,
схлопывание, громкость на клипе;
- предпросмотр прямо с монтажа через libmpv и виртуальный список кусков,
без промежуточных файлов;
- сборка через ffmpeg с точными резами: перекодируется только огрызок от
реза до ближайшего опорного кадра, остальное копируется;
- разбор заголовка MP4 для поиска опорных кадров — в 500 раз быстрее
опроса ffprobe;
- перепаковка контейнеров, кадрирование и поворот, сведения о проекте;
- свои цвета всего окна: системные элементы под Windows цвет не берут,
поэтому кнопки, полоса меню, журнал и полосы прокрутки нарисованы сами;
- окно зависимостей: недостающие ffmpeg, ffprobe и libmpv скачиваются с
официальных источников;
- кросс-сборка под Windows и работа под Wine.
Первый коммит: до этого история не велась.