nlve/README.md

531 lines
42 KiB
Markdown
Raw Normal View History

Нелинейный видеоредактор без перекодирования Куски видео вырезаются и склеиваются копированием потока: сборка ролика занимает время копирования файла, а не время рендера. Композитинга нет принципиально — при перекрытии побеждает клип с верхней дорожки целиком, и картинкой, и звуком. Именно это ограничение и делает сборку копированием возможной. Что внутри: - монтажный стол со своей отрисовкой: дорожки, масштаб, прилипание, схлопывание, громкость на клипе; - предпросмотр прямо с монтажа через libmpv и виртуальный список кусков, без промежуточных файлов; - сборка через ffmpeg с точными резами: перекодируется только огрызок от реза до ближайшего опорного кадра, остальное копируется; - разбор заголовка MP4 для поиска опорных кадров — в 500 раз быстрее опроса ffprobe; - перепаковка контейнеров, кадрирование и поворот, сведения о проекте; - свои цвета всего окна: системные элементы под Windows цвет не берут, поэтому кнопки, полоса меню, журнал и полосы прокрутки нарисованы сами; - окно зависимостей: недостающие ffmpeg, ffprobe и libmpv скачиваются с официальных источников; - кросс-сборка под Windows и работа под Wine. Первый коммит: до этого история не велась.
2026-08-19 18:36:16 +03:00
# nlve
Нелинейный видеоредактор без перекодирования. Куски видео вырезаются и
склеиваются копированием потока, поэтому сборка ролика занимает время
копирования файла, а не время рендера.
Композитинга нет принципиально: когда клипы перекрываются во времени,
побеждает тот, что на верхней дорожке — целиком, и картинкой, и звуком.
Именно это ограничение и делает возможной сборку копированием.
## Сборка
```
./build.sh редактор под эту систему
./build.sh windows редактор под Windows (кросс-сборка)
./build.sh tool консольный инструмент проверок
./build.sh test инструмент плюс прогон проверок
./build.sh all всё перечисленное
./build.sh run собрать и запустить
./build.sh clean убрать промежуточные файлы
```
Используется тулчейн fpcupdeluxe, а не системные fpc и Lazarus: системный
Lazarus другой версии, и собранные им модули LCL с нашими несовместимы.
Другой корень задаётся переменной: `FPCUP=/путь ./build.sh`.
То же самое вручную:
```
/home/chaiman/fpcupdeluxe/lazarus/lazbuild --primary-config-path=/home/chaiman/fpcupdeluxe/config_lazarus nlve.lpi
/home/chaiman/fpcupdeluxe/lazarus/lazbuild --primary-config-path=/home/chaiman/fpcupdeluxe/config_lazarus --cpu=x86_64 --os=win64 --ws=win32 nlve.lpi
```
Требования во время работы: `libmpv.so.2` (пакет `mpv`), `ffmpeg` и `ffprobe`
в `PATH`. Линковки с libav* нет — ffmpeg вызывается внешним процессом.
## Запуск
```
./nlve # пустой проект
./nlve <проект.nlve> # открыть проект
./nlve <видео> <картинка> ... # добавить файлы в новый проект
```
Окно перепаковки открывается из меню «Файл». Здесь раньше значился запуск
`./nlve remux <файл>...` — в коде этого нет и не было: слово `remux` не
является именем файла и молча пропускалось.
## Управление
| Действие | Клавиши |
|---|---|
| Пуск и пауза | Пробел |
| В начало / конец монтажа | Home / End |
| Разрезать по курсору | S |
| Разрезать точно (перекодирует стык) | Shift+S |
| Удалить выделенное | Delete |
| Выделить всё | Ctrl+A |
| Схлопнуть дыру под курсором | Ctrl+Shift+R |
| Отменить, повторить | Ctrl+Z, Ctrl+Y |
| Масштаб таймлайна | Ctrl + колесо мыши |
| Прокрутка по дорожкам | колесо мыши |
| Прокрутка по времени | Shift + колесо, средняя кнопка |
| Прокрутка журнала вбок | Shift + колесо над журналом |
Этап 1: форма звука на клипах Для каждого файла один раз строится список пиков: сто значений в секунду, каждое есть наибольший модуль отсчёта за свои десять миллисекунд. Рисовать по настоящим отсчётам нельзя — в секунде их 48 тысяч, а в клипе на экране сотни пикселей. Разбор идёт в отдельном потоке и запоминается в кэше проекта. Через временный файл, а не по трубе: запуск внешних программ режет вывод на строки, а тут поток байтов, где перевод строки — такой же отсчёт. Shift со стрелками вверх и вниз растягивает форму по высоте. Звук бывает тихим, и угадывать за человека нужное усиление неправильно. Меняется только рисунок, значение сохраняется между запусками. Проверено: файл «4 с тона, 4 с тишины, 4 с тона» даёт форму в первой и последней трети и пустоту посередине. Полная амплитуда даёт 254 из 255. Разбор записи на 892 с занимает 12.7 с — это чтение 6.3 ГБ с диска, быстрее не выйдет. Первоначальный критерий «меньше двух секунд» в плане был занижен и исправлен по замеру. Заодно снят риск четвёртого этапа: mpv принимает edl:// как внешнюю звуковую дорожку и берёт звук именно из неё. Проверено встречными списками — картинке «тишина, потом тон», звуку «тон, потом тишина».
2026-08-19 21:23:24 +03:00
| Растянуть форму звука по высоте | Shift + стрелки вверх и вниз |
Нелинейный видеоредактор без перекодирования Куски видео вырезаются и склеиваются копированием потока: сборка ролика занимает время копирования файла, а не время рендера. Композитинга нет принципиально — при перекрытии побеждает клип с верхней дорожки целиком, и картинкой, и звуком. Именно это ограничение и делает сборку копированием возможной. Что внутри: - монтажный стол со своей отрисовкой: дорожки, масштаб, прилипание, схлопывание, громкость на клипе; - предпросмотр прямо с монтажа через libmpv и виртуальный список кусков, без промежуточных файлов; - сборка через ffmpeg с точными резами: перекодируется только огрызок от реза до ближайшего опорного кадра, остальное копируется; - разбор заголовка MP4 для поиска опорных кадров — в 500 раз быстрее опроса ffprobe; - перепаковка контейнеров, кадрирование и поворот, сведения о проекте; - свои цвета всего окна: системные элементы под Windows цвет не берут, поэтому кнопки, полоса меню, журнал и полосы прокрутки нарисованы сами; - окно зависимостей: недостающие ffmpeg, ffprobe и libmpv скачиваются с официальных источников; - кросс-сборка под Windows и работа под Wine. Первый коммит: до этого история не велась.
2026-08-19 18:36:16 +03:00
| Выключить дорожку / оставить только её | кнопки М и С в заголовке |
| Громкость клипа | тянуть ручку на клипе вверх-вниз |
| Автосхлопывание вкл/выкл | Ctrl+K |
Этап 5: прозрачность, переходы и затухания Ползунок на клипе картинки теперь задаёт непрозрачность, на звуковом — громкость. Уплощение выдаёт для таких мест не одного победителя, а слои. Слои появляются в трёх случаях: клип полупрозрачен и сквозь него виден нижний; два клипа на одной дорожке наползли друг на друга и должны перетечь один в другой; у клипа задано плавное появление или уход. Перекрытие на разных дорожках переходом не считается — там по-прежнему побеждает верхний. Иначе в проекте, где дорожки перекрываются постоянно, каждое такое место пришлось бы пересобирать. Картинка собирается фильтрами ffmpeg: постоянная доля через overlay, переход через xfade, затухание через fade. Звук сводится прямо в отсчётах — доля каждого слоя считается на каждый отсчёт, и длина результата получается ровно такой, какая нужна. Перед сборкой программа сообщает, сколько секунд из скольких придётся пересобрать, и даёт отказаться. Проверено замерами: - красный с непрозрачностью 50% поверх синего дал (127, 0, 126); - переход на одной дорожке: синий плавно перешёл в красный за секунду; - звуковой переход: -24 dB, -27.5, -35, -51, тишина; - появление за 2 секунды: пересобрано 2 секунды из 6, остальное скопировано. Найдено по дороге: участок с наложением склеивался с соседним куском и терял слои — в условии склейки они не учитывались.
2026-08-20 10:11:20 +03:00
| Связать или развязать клипы | Ctrl+G |
Нелинейный видеоредактор без перекодирования Куски видео вырезаются и склеиваются копированием потока: сборка ролика занимает время копирования файла, а не время рендера. Композитинга нет принципиально — при перекрытии побеждает клип с верхней дорожки целиком, и картинкой, и звуком. Именно это ограничение и делает сборку копированием возможной. Что внутри: - монтажный стол со своей отрисовкой: дорожки, масштаб, прилипание, схлопывание, громкость на клипе; - предпросмотр прямо с монтажа через libmpv и виртуальный список кусков, без промежуточных файлов; - сборка через ffmpeg с точными резами: перекодируется только огрызок от реза до ближайшего опорного кадра, остальное копируется; - разбор заголовка MP4 для поиска опорных кадров — в 500 раз быстрее опроса ffprobe; - перепаковка контейнеров, кадрирование и поворот, сведения о проекте; - свои цвета всего окна: системные элементы под Windows цвет не берут, поэтому кнопки, полоса меню, журнал и полосы прокрутки нарисованы сами; - окно зависимостей: недостающие ffmpeg, ffprobe и libmpv скачиваются с официальных источников; - кросс-сборка под Windows и работа под Wine. Первый коммит: до этого история не велась.
2026-08-19 18:36:16 +03:00
| Отменить прилипание на время | Shift при перетаскивании |
| Вписать проект целиком | Ctrl+0 |
| Новый, открыть, сохранить проект | Ctrl+N, Ctrl+O, Ctrl+S |
| Добавить файлы | Ctrl+I |
| Собрать ролик | Ctrl+E |
Файлы попадают в проект через меню «Файл» или перетаскиванием прямо в окно
программы. Место броска решает, что произойдёт: бросили над монтажом — файл
и добавится в проект, и ляжет на дорожку под указателем; в любом другом
месте — только добавится в проект.
Файл из библиотеки кладётся на монтаж перетаскиванием либо через
контекстное меню списка. В нём: **добавить на дорожку** (подменю со
списком дорожек), **кадрирование и поворот**, **убрать со всех дорожек**
(файл остаётся в проекте), **удалить из проекта**. Дыры при этом намеренно не
схлопываются: убрать клип и переставить всё остальное — два разных
намерения.
Если бросить клип ниже всех дорожек, заведётся новая.
В заголовке каждой дорожки слева — её название, число клипов и две кнопки:
**М** выключает дорожку целиком (она не идёт ни в предпросмотр, ни в сборку),
**С** оставляет только её, заглушив остальные. Щелчок по заголовку делает
дорожку выбранной — на неё кладёт файл кнопка.
Клип переносится перетаскиванием, в том числе на другую дорожку и на
пустое место под последней — тогда дорожка тоже добавится. За края клипа
он растягивается и укорачивается. Картинку можно растянуть на любую
длину, видео — только в пределах исходника.
Щелчок по клипу переставляет курсор воспроизведения. Именно щелчок:
если кнопку нажали и потянули, курсор остаётся на месте, в том числе во
время воспроизведения.
**Прилипание.** Края клипов притягиваются друг к другу и к курсору.
Кнопка внизу включает и выключает его, а Shift во время перетаскивания
меняет решение на противоположное: при включённом прилипании временно
отключает, при выключенном — временно включает. Так одна клавиша полезна
в обоих режимах, и не нужно помнить, какой сейчас.
**Схлопывание.** Кнопка внизу показывает и режим, и состояние: утоплена —
автосхлопывание включено, и тогда при перетаскивании клипа всё, что
начинается не раньше него, едет следом. Стрелка рядом открывает выбор
режима: только текущая дорожка или все. Ctrl+K включает и выключает.
Режим действует и при выключенном автосхлопывании — по нему работает
Ctrl+Shift+R, который схлопывает дыру под курсором. То есть выключенная
кнопка означает лишь «не двигать ничего само собой при перетаскивании».
Во время воспроизведения курсор держится посередине окна, а монтаж едет
под ним. При нажатии на пуск, если курсор оказался за краем видимой
части, вид подтягивается к нему — масштаб при этом не меняется.
На каждом клипе проходит линия громкости с ручкой посередине видимой его
части: тянешь ручку вверх и вниз — меняется громкость, от тишины до
двойной. Ручка стоит посередине именно видимой части, а не всего клипа:
до середины часового клипа, уехавшей за край экрана, мышью не дотянуться.
Громкость, отличная от обычной, — единственное место, где звук
перекодируется: изменить громкость, не тронув сам звук, нельзя. Видео при
этом по-прежнему копируется. Проверено: клип с громкостью 30% дал в ролике
уровень на 10.5 дБ ниже исходного при ожидаемых 10.46 дБ.
Полоски по краям клипа показывают тип реза: серая — притянут к опорному
кадру, голубая — точный, стык будет перекодирован при сборке. Если край
подвинуть руками и он не попадёт на опорный кадр, рез сам становится
точным: раз человек выбрал конкретный кадр, притягивать его к соседнему
неправильно.
В списке файлов слева показывается кадр из каждого файла, его параметры
и длительность. Кадры делаются в фоне и запоминаются в кэше проекта.
Этап 1: форма звука на клипах Для каждого файла один раз строится список пиков: сто значений в секунду, каждое есть наибольший модуль отсчёта за свои десять миллисекунд. Рисовать по настоящим отсчётам нельзя — в секунде их 48 тысяч, а в клипе на экране сотни пикселей. Разбор идёт в отдельном потоке и запоминается в кэше проекта. Через временный файл, а не по трубе: запуск внешних программ режет вывод на строки, а тут поток байтов, где перевод строки — такой же отсчёт. Shift со стрелками вверх и вниз растягивает форму по высоте. Звук бывает тихим, и угадывать за человека нужное усиление неправильно. Меняется только рисунок, значение сохраняется между запусками. Проверено: файл «4 с тона, 4 с тишины, 4 с тона» даёт форму в первой и последней трети и пустоту посередине. Полная амплитуда даёт 254 из 255. Разбор записи на 892 с занимает 12.7 с — это чтение 6.3 ГБ с диска, быстрее не выйдет. Первоначальный критерий «меньше двух секунд» в плане был занижен и исправлен по замеру. Заодно снят риск четвёртого этапа: mpv принимает edl:// как внешнюю звуковую дорожку и берёт звук именно из неё. Проверено встречными списками — картинке «тишина, потом тон», звуку «тон, потом тишина».
2026-08-19 21:23:24 +03:00
На клипах видна форма звука. Считается она по заранее разобранным пикам —
сто значений в секунду, каждое есть наибольший модуль отсчёта за свои
десять миллисекунд. Рисовать по настоящим отсчётам нельзя: в секунде их
48 тысяч, а в клипе на экране сотни пикселей.
Разбор идёт в фоне и запоминается в кэше проекта. Для записи на 892 с это
около 13 секунд — столько занимает чтение шести гигабайт с диска, и
быстрее не выйдет; при следующем открытии проекта пики берутся готовыми.
**Shift со стрелками вверх и вниз растягивает форму по высоте.** Звук
бывает тихим, и угадывать за человека, во сколько раз растянуть картинку,
неправильно. Усиление меняет только рисунок, самого звука не касается, и
запоминается между запусками.
Нелинейный видеоредактор без перекодирования Куски видео вырезаются и склеиваются копированием потока: сборка ролика занимает время копирования файла, а не время рендера. Композитинга нет принципиально — при перекрытии побеждает клип с верхней дорожки целиком, и картинкой, и звуком. Именно это ограничение и делает сборку копированием возможной. Что внутри: - монтажный стол со своей отрисовкой: дорожки, масштаб, прилипание, схлопывание, громкость на клипе; - предпросмотр прямо с монтажа через libmpv и виртуальный список кусков, без промежуточных файлов; - сборка через ffmpeg с точными резами: перекодируется только огрызок от реза до ближайшего опорного кадра, остальное копируется; - разбор заголовка MP4 для поиска опорных кадров — в 500 раз быстрее опроса ffprobe; - перепаковка контейнеров, кадрирование и поворот, сведения о проекте; - свои цвета всего окна: системные элементы под Windows цвет не берут, поэтому кнопки, полоса меню, журнал и полосы прокрутки нарисованы сами; - окно зависимостей: недостающие ffmpeg, ffprobe и libmpv скачиваются с официальных источников; - кросс-сборка под Windows и работа под Wine. Первый коммит: до этого история не велась.
2026-08-19 18:36:16 +03:00
## Файл не совпадает с проектом
Проект имеет одно разрешение и один набор кодеков на всё — иначе куски не
склеить копированием. Файл с другими параметрами можно добавить, но его
куски скопировать не выйдет.
Пересобираются **только они**. Остальной материал по-прежнему копируется
без потерь и почти мгновенно. Ролик целиком не перекодируется никогда.
Этап 2: кадрирование на клипе с перетаскиваемой рамкой Настройка кадрирования переехала с файла на клип: два куска одного файла бывает нужно кадрировать по-разному. У файла осталась такая же, но лишь как заготовка — её берёт новый клип при добавлении. Проекты, записанные раньше, читаются: клип без своей настройки берёт её у файла. Открывается кнопкой в правом нижнем видимом углу клипа. Именно видимом: у клипа длиной в час настоящий угол почти всегда за краем экрана — та же задача, что уже решена для ручки громкости. В окне слева появился кадр источника с рамкой, которую тянут мышью за середину и за правый нижний угол. Рамка держит пропорции кадра проекта и пересчитывается в увеличение и смещение; ползунки и рамка следуют друг за другом. Кусок пересобирается теперь не только когда файл не совпадает с проектом, но и когда у клипа своё кадрирование. Проверено сборкой: первый кусок без кадрирования скопирован, второй с увеличением вдвое пересобран через scale=1280:720,crop=640:360:384:180. 154 пакета вошло, 154 вышло, декодирование без ошибок. Заодно: правый щелчок по списку файлов теперь выделяет файл под курсором. Без этого меню без выделения отключалось целиком и выглядело так, будто его нет. Цвет формы звука сменён на пастельно-жёлтый.
2026-08-19 23:10:08 +03:00
Как именно кадр ложится в кадр проекта, решает окно «Кадрирование и
поворот»: вписать целиком с полями, заполнить кадр с обрезкой или
растянуть, плюс поворот на 90, отражение, увеличение и смещение.
Слева в окне — кадр источника с рамкой: её тянут мышью за середину и за
правый нижний угол, и она держит пропорции кадра проекта. Справа —
настоящий результат, пропущенный через ту же цепочку фильтров, что пойдёт
в дело.
**Настройка принадлежит клипу**, а открывается кнопкой в правом нижнем
видимом углу самого клипа. Именно видимом: у клипа длиной в час настоящий
угол почти всегда за краем экрана. Два куска одного файла можно
кадрировать по-разному.
У файла в библиотеке хранится такая же настройка, но она лишь заготовка:
её берёт новый клип при добавлении. Правится она из контекстного меню
библиотеки.
Нелинейный видеоредактор без перекодирования Куски видео вырезаются и склеиваются копированием потока: сборка ролика занимает время копирования файла, а не время рендера. Композитинга нет принципиально — при перекрытии побеждает клип с верхней дорожки целиком, и картинкой, и звуком. Именно это ограничение и делает сборку копированием возможной. Что внутри: - монтажный стол со своей отрисовкой: дорожки, масштаб, прилипание, схлопывание, громкость на клипе; - предпросмотр прямо с монтажа через libmpv и виртуальный список кусков, без промежуточных файлов; - сборка через ffmpeg с точными резами: перекодируется только огрызок от реза до ближайшего опорного кадра, остальное копируется; - разбор заголовка MP4 для поиска опорных кадров — в 500 раз быстрее опроса ffprobe; - перепаковка контейнеров, кадрирование и поворот, сведения о проекте; - свои цвета всего окна: системные элементы под Windows цвет не берут, поэтому кнопки, полоса меню, журнал и полосы прокрутки нарисованы сами; - окно зависимостей: недостающие ffmpeg, ffprobe и libmpv скачиваются с официальных источников; - кросс-сборка под Windows и работа под Wine. Первый коммит: до этого история не велась.
2026-08-19 18:36:16 +03:00
По умолчанию кадр вписывается целиком. Растяжение надо выбрать руками:
раньше оно применялось молча, и вертикальное видео размазывалось по ширине.
«Приведение к параметрам проекта» делает ту же работу один раз при
добавлении: файл перекодируется целиком, зато каждая последующая сборка
остаётся быстрой.
Что и почему происходит с каждым файлом, видно в окне «Проект → Сведения
о проекте»: параметры проекта, параметры каждого файла, выбранное
вписывание и вердикт — копируется, пересобирается или из него делается
клип. Там же кнопка полного ответа ffprobe.
### Что показывает предпросмотр
Предпросмотр показывает **кадр проекта**, а не кадр исходника. Файл другого
соотношения сторон виден с полями по краям — ровно так, как он попадёт в
готовый ролик. Проверено сравнением: кадр предпросмотра и кадр сборки
совпадают бит в бит.
Само по себе это не бесплатно, поэтому включается только когда есть что
вписывать. Разное разрешение при одинаковом соотношении сторон (скажем,
3840x2160 в проекте 2688x1512) ничего не требует: mpv сам впишет такой
файл в панель, и картинка совпадёт до пикселя.
Когда вписывание нужно, декодирование переключается на вариант с
копированием кадров в обычную память — цепочка фильтров не умеет работать
с кадрами, лежащими в памяти видеокарты. Аппаратное декодирование при этом
сохраняется, видно в журнале: `nvdec` без вписывания, `nvdec-copy` с ним.
Замер на материале 2688x1512 при 60 к/с: 327 кадров в секунду без фильтра,
230 с настоящим масштабированием — запас четырёхкратный.
Чего предпросмотр не умеет: показать собственную настройку кадрирования
файла. Цепочка фильтров у mpv одна на всю склейку, а заполнение с обрезкой,
поворот и смещение задаются для каждого файла отдельно — выбрать между ними
общий фильтр не может, поэтому он всегда вписывает целиком. Точную картинку
для такого файла показывает окно «Кадрирование и поворот».
## Цвета окна
Все цвета заданы в `uTheme` и применяются принудительно, а не берутся у
системы. Причина простая: под Linux вид назначает тема GTK, под Windows —
тема оформления, и на светлой теме тёмный таймлайн оказывается вставленным
в белую рамку.
Что для этого пришлось сделать:
- **Свои кнопки** (`TThemedButton`). Системная кнопка под Windows свой цвет
не отдаёт: LCL рисует её через службу оформления, и та кладёт светлую
подложку поверх заданного цвета. Проверено по исходникам LCL —
`TCustomSpeedButton.PaintBackground` всегда вызывает
`ThemeServices.DrawElement`. Поведение обычной кнопки сохранено:
`Default`, `Cancel`, `ModalResult`, работа по Enter и Esc.
- **Своя полоса меню** из плоских кнопок со всплывающими списками.
Системную полосу рисует Windows, и её фон нашим цветом не закрашивается:
`SetMenuInfo` меняет подложку пунктов, но не самой полосы. Сочетания
клавиш при этом не теряются — их ищет `TMenu.IsShortCut`, вызываемый из
`TMainForm.FormShortCut`. Ценой стал переход по меню клавишей Alt: его
больше нет.
- **Своё окно сообщения** (`ThemedMessageDlg`) вместо `MessageDlg`.
- **Отрисовка пунктов меню и строк выпадающих списков** своими руками.
Чего сделать нельзя, и это надо знать:
- полосы прокрутки рисует система, и цвет им задать нечем. Единственный
способ получить их в своих цветах — не пользоваться системными вовсе.
Так и сделано: журнал (`uLogView`) и таблицы (`uThemedScroll`) обходятся
своими. Осталась системная полоса у списка файлов слева — там её нечем
выключить, не переписав сам список;
- рамка окна и её кнопки принадлежат оконному менеджеру;
- под gtk2 пункты меню своими руками не рисуются — в LCL это прямо
помечено как неподдерживаемое (`interfaces/gtk2/issues.xml`), там их
цвет назначает тема GTK. Под Windows рисуются, там это и нужно.
## Зависимости и запуск под Wine
Программе нужны `ffmpeg`, `ffprobe` и `libmpv`. Под Linux они ставятся
пакетным менеджером. Под Windows ставить нечем, поэтому файлы кладутся
рядом с программой, а меню «Файл → Зависимости...» умеет их скачать.
Список и адреса лежат в `deps-windows.json` или `deps-linux.json` рядом с
программой — имя своё для каждой системы. Общий файл приводил к тому, что
запущенная под Wine программа записывала windows-список, а сборка под
Linux потом искала по нему `.dll`.
Где имя файла сборки меняется с каждой датой, адрес не задаётся: программа
спрашивает последний выпуск у GitHub. Архивы `.7z` распаковываются внешним
`7zr.exe` — он тоже есть в списке и качается одним файлом, поэтому
замкнутого круга не возникает.
Под Wine программа узнаёт себя по наличию `wine_get_version` в `ntdll` и
настраивается сразу:
- **вывод через OpenGL**, а не Direct3D 11: последний в Wine падает при
создании контекста, ещё до первого кадра;
- **аппаратное декодирование выключено**. Дело не в бесполезных попытках:
для DXVA2 mpv создаёт устройство Direct3D 9, а Wine при его создании
сбрасывает гамму экрана в единицу — настройку всей системы. Замерено:
с `auto-safe` гамма 0.89 превращалась в 1.0 при каждом открытии
проекта, с `no` остаётся нетронутой.
На настоящей Windows обе настройки остаются обычными.
Журнал дублируется в `nlve.log` рядом с программой. Нужно ровно для
случая, когда программа падает до появления окна: иначе всё, что она о
себе рассказала, пропадает вместе с окном.
Этап 5: прозрачность, переходы и затухания Ползунок на клипе картинки теперь задаёт непрозрачность, на звуковом — громкость. Уплощение выдаёт для таких мест не одного победителя, а слои. Слои появляются в трёх случаях: клип полупрозрачен и сквозь него виден нижний; два клипа на одной дорожке наползли друг на друга и должны перетечь один в другой; у клипа задано плавное появление или уход. Перекрытие на разных дорожках переходом не считается — там по-прежнему побеждает верхний. Иначе в проекте, где дорожки перекрываются постоянно, каждое такое место пришлось бы пересобирать. Картинка собирается фильтрами ffmpeg: постоянная доля через overlay, переход через xfade, затухание через fade. Звук сводится прямо в отсчётах — доля каждого слоя считается на каждый отсчёт, и длина результата получается ровно такой, какая нужна. Перед сборкой программа сообщает, сколько секунд из скольких придётся пересобрать, и даёт отказаться. Проверено замерами: - красный с непрозрачностью 50% поверх синего дал (127, 0, 126); - переход на одной дорожке: синий плавно перешёл в красный за секунду; - звуковой переход: -24 dB, -27.5, -35, -51, тишина; - появление за 2 секунды: пересобрано 2 секунды из 6, остальное скопировано. Найдено по дороге: участок с наложением склеивался с соседним куском и терял слои — в условии склейки они не учитывались.
2026-08-20 10:11:20 +03:00
## Наложение, переходы и прозрачность
Ползунок на клипе картинки задаёт **непрозрачность**, на звуковом —
громкость. Клип с непрозрачностью меньше единицы смешивается с тем, что
под ним.
Два клипа **на одной дорожке**, наползшие друг на друга, дают переход:
один плавно перетекает в другой. На разных дорожках перекрытие означает
не переход, а наложение — там по-прежнему побеждает верхний, если он
непрозрачен.
Различие не косметическое. Если бы кроссфейдом считалось любое
перекрытие, то в проекте, где клипы на разных дорожках перекрываются
постоянно, каждое такое место пришлось бы пересобирать.
У клипа есть плавное появление и уход. Для картинки это затемнение, для
звука — нарастание громкости.
**Всё это стоит перекодирования** — но только своего участка. Перед
сборкой программа сообщает, сколько секунд из скольких придётся
пересобрать, и даёт отказаться.
Звук при наложении сводится прямо в отсчётах, без фильтров ffmpeg: доля
каждого слоя считается на каждый отсчёт, и длина результата получается
ровно такой, какая нужна.
Нелинейный видеоредактор без перекодирования Куски видео вырезаются и склеиваются копированием потока: сборка ролика занимает время копирования файла, а не время рендера. Композитинга нет принципиально — при перекрытии побеждает клип с верхней дорожки целиком, и картинкой, и звуком. Именно это ограничение и делает сборку копированием возможной. Что внутри: - монтажный стол со своей отрисовкой: дорожки, масштаб, прилипание, схлопывание, громкость на клипе; - предпросмотр прямо с монтажа через libmpv и виртуальный список кусков, без промежуточных файлов; - сборка через ffmpeg с точными резами: перекодируется только огрызок от реза до ближайшего опорного кадра, остальное копируется; - разбор заголовка MP4 для поиска опорных кадров — в 500 раз быстрее опроса ffprobe; - перепаковка контейнеров, кадрирование и поворот, сведения о проекте; - свои цвета всего окна: системные элементы под Windows цвет не берут, поэтому кнопки, полоса меню, журнал и полосы прокрутки нарисованы сами; - окно зависимостей: недостающие ffmpeg, ffprobe и libmpv скачиваются с официальных источников; - кросс-сборка под Windows и работа под Wine. Первый коммит: до этого история не велась.
2026-08-19 18:36:16 +03:00
## Как собирается ролик
1. Монтаж уплощается в последовательность непересекающихся кусков.
Перекрытия разрешаются в пользу верхней дорожки, дыры превращаются в
чёрные вставки.
2. Картинки и дыры материализуются в клипы в параметрах проекта. Иначе их
нечем показать: подсунуть mpv картинку или генератор прямо в списке
кусков нельзя, он на этом зависает. Заготовки делаются готовых длин
(15 с, 60 с, 5 мин и так далее), а нужный кусок из них вырезается —
тогда любое движение клипа не требует новой генерации.
3. Начало каждого куска притягивается к опорному кадру, потому что копировать
можно только с него. Конец притягивать не нужно — при копировании он и так
получается кадроточным.
4. При точном резе огрызок от точки реза до следующего опорного кадра
перекодируется, остальное копируется.
5. Куски склеиваются concat-демультиплексором, результат сверяется: сумма
пакетов кусков обязана совпасть с числом пакетов в готовом файле.
Напрашивающийся способ — один проход с директивами `inpoint` и `outpoint`,
без временных файлов — проверен и отвергнут: он давал 574 кадра вместо 600
и не пересчитывал метки времени.
## Ловушки, на которые уже наступили
Все перечисленные ниже вещи найдены замерами, а не рассуждениями.
**Локаль.** libmpv отказывается создаваться, если числовая часть локали
процесса не `C`: `mpv_create` возвращает nil. В русской локали десятичный
разделитель запятая, и разбор чисел внутри mpv ломается. Лечится вызовом
`ForceCNumericLocale` до `mpv_create` (`uMpvLib`).
**Панель без окна.** Под gtk2 панель LCL не имеет собственного окна X11, и
запрос окна возвращает окно формы. Отдав такой идентификатор mpv, получаем
видео поверх всего интерфейса. Поэтому под X11 создаётся собственное
дочернее окно по границам панели (`EnsureVideoWindow` в `uMpvPlayer`).
**Относительные пути в списке склейки.** Путь с префиксом `file:` внутри
списка concat разбирается относительно текущего рабочего каталога, а не
относительно самого списка. Все пути приводятся к абсолютным сразу.
**Смешивание кусков от разных кодировщиков.** MP4 и Matroska хранят набор
параметров кодека один на дорожку. Как только рядом оказываются
скопированный кусок из камеры и созданная нами вставка, декодер применяет
параметры первого ко всем: замерено 384 ошибки декодера HEVC при верном
числе кадров. Лечится склейкой через промежуточный MPEG-TS, где параметры
повторяются перед каждым опорным кадром — стало 0 ошибок. Способ выбирается
автоматически: один источник и никаких точных резов — прямая склейка,
иначе MPEG-TS.
**Лишний кадр на стыке точного реза.** При копировании `-to` работает как
«строго меньше», при перекодировании — как «меньше либо равно». Кадр на
границе попадал в ролик дважды: 601 вместо 600. Лечится отступом на
полкадра от границы.
**B-кадры во вставках.** Двунаправленные кадры от NVENC при переносе в
Matroska давали метки декодирования, идущие назад, и двадцать совпадающих
меток на шестьсот кадров. Коротким вставкам B-кадры не нужны, поэтому они
запрещены (`-bf 0`).
**Вставка под каждую длину дыры.** Сначала под каждую дыру делался чёрный
клип ровно её длины. Любое движение клипа меняло длину дыры, требовало
новой генерации, и до её готовности предпросмотр играл прежнюю расстановку.
Выглядело так, будто выключение дорожки или удаление клипа не действуют.
Лечится заготовками готовых длин, из которых вырезается нужный кусок.
**Первая загрузка начинала воспроизведение.** У незагруженного mpv признак
паузы снят, поэтому первый же список кусков ехал сам. Лечится опцией
`pause=yes` при создании плеера.
**Молчаливое растяжение кадра.** Приведение файла к проекту задавало размер
ключом `-s`, а он растягивает кадр не считаясь с соотношением сторон.
Вертикальное видео превращалось в размазанное по ширине. Теперь размер
задаётся цепочкой фильтров, а вписывание выбирается осознанно.
**Фильтр mpv и кадры в памяти видеокарты.** Цепочка фильтров ffmpeg
работает с обычной памятью, а при аппаратном декодировании кадры остаются
в памяти видеокарты. Граф фильтров тогда просто не собирается: `Impossible
to convert between the formats supported by the filter 'mpv_src_in0'`.
Лечится переключением на `hwdec=auto-copy`, пока фильтр нужен.
Ловушка была не в этом, а в том, как я это проверял: из командной строки
mpv сам выбрал `nvdec-copy`, всё работало, и ошибки не было видно. Она
вылезла только на снимке окна работающего редактора. Проверять такое надо
там, где оно живёт, а не в удобной обстановке.
**Скрипт сборки соврал об успехе.** `PIPESTATUS` живёт ровно до следующей
команды, а в разборе результата стояло двоеточие в ветке `then` — оно и
затирало состояние конвейера. Сборка падала, скрипт докладывал «готово».
Читать `PIPESTATUS` надо сразу и в переменную.
**Модуль Windows конфликтует именами.** Он объявляет свои `TRect`, `Point`
и `RegisterClass`, а имена в Паскале нечувствительны к регистру — обычные
вызовы LCL начинают разбираться как системные, с невнятными ошибками вида
«ожидалось `)`». Подключать его целиком не нужно: `SetMenuInfo` и
`MENUINFO` объявлены в `uTheme` напрямую.
**Знак «▼» в шрифте Windows.** Вместо стрелки выходил пустой квадрат.
Треугольник рисуется сам (`TThemedButton.Arrow`).
**Раскраска до построения меню.** `ApplyDarkTheme` вызывался в начале
`FormCreate`, а меню строится в середине — и цветов не получало. Вызов
должен идти последним.
**Исключения вещественной арифметики.** Паскаль по умолчанию требует
сообщать о каждой неточности вычислений. libmpv и ffmpeg внутри считают
как им удобно и на это не рассчитывают: при создании плеера вылезало
«Invalid floating point operation», и предпросмотр не запускался. Лечится
`SetExceptionMask` после загрузки библиотеки.
Звуковая дорожка подключается командой, а не свойством Найдена настоящая причина обеих жалоб на звук. Свойство audio-files, заданное перед загрузкой, не подключает внешний список кусков вовсе: проверено на живом mpv через его канал управления — после установки свойства дорожек по-прежнему две, обе не внешние. Играл звук, встроенный в саму картинку. Отсюда обе стороны: звук был слышен там, где на монтаже его нет (шёл из видеофайла), и не был слышен там, где есть (в этом месте картинкой была чёрная вставка, а она молчит). Правильный способ — команда audio-add с признаком select после загрузки. Проверено: дорожек становится четыре, внешняя звуковая выбрана, внутренняя снята с выбора. Если подключить не удалось, в журнал пишется предупреждение. Глушить звук на этот случай нельзя — прошлая попытка так сделать оставила без звука весь монтаж.
2026-08-20 11:58:29 +03:00
**Внешняя звуковая дорожка не подключается свойством.** Задать
`audio-files` перед загрузкой недостаточно: список дорожек после
загрузки остаётся прежним, внешний файл не подключается вовсе, и играет
звук, встроенный в саму картинку. Проверено на живом mpv через его канал
управления: после установки свойства дорожек по-прежнему две, обе не
внешние.
Из-за этого звук был слышен там, где на монтаже его нет. Попытка
защититься, отключая звук при ненайденной внешней дорожке, сделала
хуже — звука не стало нигде.
Правильный способ — команда `audio-add` с признаком `select` после
загрузки: она добавляет дорожку, выбирает её и снимает выбор с
внутренней. Проверено: дорожек становится четыре, внутренняя звуковая
помечается невыбранной.
Нелинейный видеоредактор без перекодирования Куски видео вырезаются и склеиваются копированием потока: сборка ролика занимает время копирования файла, а не время рендера. Композитинга нет принципиально — при перекрытии побеждает клип с верхней дорожки целиком, и картинкой, и звуком. Именно это ограничение и делает сборку копированием возможной. Что внутри: - монтажный стол со своей отрисовкой: дорожки, масштаб, прилипание, схлопывание, громкость на клипе; - предпросмотр прямо с монтажа через libmpv и виртуальный список кусков, без промежуточных файлов; - сборка через ffmpeg с точными резами: перекодируется только огрызок от реза до ближайшего опорного кадра, остальное копируется; - разбор заголовка MP4 для поиска опорных кадров — в 500 раз быстрее опроса ffprobe; - перепаковка контейнеров, кадрирование и поворот, сведения о проекте; - свои цвета всего окна: системные элементы под Windows цвет не берут, поэтому кнопки, полоса меню, журнал и полосы прокрутки нарисованы сами; - окно зависимостей: недостающие ffmpeg, ffprobe и libmpv скачиваются с официальных источников; - кросс-сборка под Windows и работа под Wine. Первый коммит: до этого история не велась.
2026-08-19 18:36:16 +03:00
**Аргументы loadfile.** В mpv 0.38 у команды появился четвёртый аргумент —
номер позиции в списке, и настройки файла сдвинулись на пятое место.
Прежний вызов стал ошибкой «The loadfile option must be an integer», и под
Wine с новой библиотекой не грузилось ничего. Гадать по номеру версии не
нужно: те же настройки задаются свойствами перед загрузкой, и называются
они одинаково во всех версиях.
**Модуль Windows конфликтует именами.** Он объявляет свои `TRect`,
`Point`, `DeleteFile`, `GetTempFileName` и `RegisterClass`, а имена в
Паскале нечувствительны к регистру — обычные вызовы LCL начинают
разбираться как системные, с невнятными ошибками вида «ожидалось `)`».
Подключать его целиком не нужно: `SetMenuInfo` и `MENUINFO` объявлены в
`uTheme` напрямую. За сессию эта ловушка сработала четыре раза.
**Порядок байт в TColor.** LCL хранит цвет как `$00BBGGRR`, а не как
привычный RGB. Записанный по привычке код даёт цвет с переставленными
красным и синим.
**Имена, занятые предками.** Локальная переменная перекрывает одноимённое
свойство класса: `Text` и `Position` заняты у `TControl`, `Finished` — у
`TThread`, `Name` и `Caption` — у формы. Компилятор ловит это как
«Duplicate identifier», но причину не объясняет.
## Консольный инструмент проверок
Часть вещей проверяется числами, а не глазами. Сборка: `./build.sh tool`.
```
./tests/nlvetool kf <файл> индекс опорных кадров
./tests/nlvetool kfcheck <файл> сверка быстрого пути с ffprobe
./tests/nlvetool flattest проверка уплощения монтажа
./tests/nlvetool edl <файл> <старт> <длина> [...]
./tests/nlvetool export <файл> <выход> <старт> <длина> [...]
./tests/nlvetool export --cancel-after=<сек> ... проверка остановки
./tests/nlvetool remux <файл>... <расширение>
./tests/nlvetool build <проект> <видео> [<картинка>]
./tests/nlvetool buildcut <проект> <видео>
./tests/nlvetool render <проект> <выход>
```
Команды возвращают ненулевой код при расхождении, поэтому годятся для
автоматической проверки.
## Структура
| Файл | Что это |
|---|---|
| `src/uAppTypes.pas` | Базовые типы, форматирование чисел без учёта локали |
| `src/uSettings.pas` | Настройки между запусками (JSON) |
| `src/uProcRunner.pas` | Запуск ffmpeg и ffprobe, разбор вывода, остановка |
| `src/uFfmpegCmd.pas` | Сборка аргументов ffmpeg, разбор его прогресса |
| `src/uMediaProbe.pas` | Что за файл нам дали и чем он отличается от проекта |
| `src/uKeyframeIndex.pas` | Где опорные кадры: разбор заголовка MP4 и запасной путь |
| `src/uProject.pas` | Модель монтажа, JSON проекта, отмена действий |
| `src/uFlatten.pas` | Монтаж в плоский список кусков |
| `src/uGenerate.pas` | Клипы для картинок и дыр |
| `src/uExport.pas` | Вырезание, склейка, сверка числа кадров |
| `src/uRemuxQueue.pas` | Очередь перепаковки |
| `src/uEdl.pas` | Сборка `edl://` для предпросмотра |
| `src/uMpvLib.pas` | Объявления libmpv, динамическая загрузка |
| `src/uMpvPlayer.pas` | Плеер: встраивание в окно, воспроизведение, события |
| `src/uTimelineCtrl.pas` | Компонент монтажного стола |
| `src/uTransform.pas` | Как кадр исходника ложится в кадр проекта |
| `src/uPanCropForm.pas` | Окно кадрирования и поворота |
| `src/uProjectInfoForm.pas` | Сведения о проекте и ответ ffprobe |
| `src/uThumbs.pas` | Кадры-миниатюры для списка файлов |
Этап 1: форма звука на клипах Для каждого файла один раз строится список пиков: сто значений в секунду, каждое есть наибольший модуль отсчёта за свои десять миллисекунд. Рисовать по настоящим отсчётам нельзя — в секунде их 48 тысяч, а в клипе на экране сотни пикселей. Разбор идёт в отдельном потоке и запоминается в кэше проекта. Через временный файл, а не по трубе: запуск внешних программ режет вывод на строки, а тут поток байтов, где перевод строки — такой же отсчёт. Shift со стрелками вверх и вниз растягивает форму по высоте. Звук бывает тихим, и угадывать за человека нужное усиление неправильно. Меняется только рисунок, значение сохраняется между запусками. Проверено: файл «4 с тона, 4 с тишины, 4 с тона» даёт форму в первой и последней трети и пустоту посередине. Полная амплитуда даёт 254 из 255. Разбор записи на 892 с занимает 12.7 с — это чтение 6.3 ГБ с диска, быстрее не выйдет. Первоначальный критерий «меньше двух секунд» в плане был занижен и исправлен по замеру. Заодно снят риск четвёртого этапа: mpv принимает edl:// как внешнюю звуковую дорожку и берёт звук именно из неё. Проверено встречными списками — картинке «тишина, потом тон», звуку «тон, потом тишина».
2026-08-19 21:23:24 +03:00
| `src/uWaveform.pas` | Пики звука для формы волны на клипах |
Нелинейный видеоредактор без перекодирования Куски видео вырезаются и склеиваются копированием потока: сборка ролика занимает время копирования файла, а не время рендера. Композитинга нет принципиально — при перекрытии побеждает клип с верхней дорожки целиком, и картинкой, и звуком. Именно это ограничение и делает сборку копированием возможной. Что внутри: - монтажный стол со своей отрисовкой: дорожки, масштаб, прилипание, схлопывание, громкость на клипе; - предпросмотр прямо с монтажа через libmpv и виртуальный список кусков, без промежуточных файлов; - сборка через ffmpeg с точными резами: перекодируется только огрызок от реза до ближайшего опорного кадра, остальное копируется; - разбор заголовка MP4 для поиска опорных кадров — в 500 раз быстрее опроса ffprobe; - перепаковка контейнеров, кадрирование и поворот, сведения о проекте; - свои цвета всего окна: системные элементы под Windows цвет не берут, поэтому кнопки, полоса меню, журнал и полосы прокрутки нарисованы сами; - окно зависимостей: недостающие ffmpeg, ffprobe и libmpv скачиваются с официальных источников; - кросс-сборка под Windows и работа под Wine. Первый коммит: до этого история не велась.
2026-08-19 18:36:16 +03:00
| `src/uTheme.pas` | Цвета окна, своя кнопка, своё окно сообщения |
| `src/uLogView.pas` | Журнал: своя отрисовка, свои полосы прокрутки |
| `src/uThemedScroll.pas` | Полоса прокрутки и обвязка для таблиц |
| `src/uDeps.pas` | Что нужно программе, где искать, как ставить |
| `src/uDownload.pas` | Загрузка по сети: WinHTTP под Windows, curl под Linux |
| `src/uDepsForm.pas` | Окно «Зависимости» |
| `src/uMainForm.pas` | Главное окно |
| `src/uProgressForm.pas` | Ход долгой операции |
| `src/uRemuxForm.pas` | Окно перепаковки |
| `src/uMismatchForm.pas` | Предупреждение о несовпадении параметров |
| `build.sh` | Сборка целями |
| `tests/` | Консольный инструмент проверок |
| `vendor/mpv-headers/` | Заголовки mpv 0.37.0 — эталон для биндингов |
## Чего пока нет
**Раздельные звуковые дорожки.** Звук идёт с того же клипа, что и картинка.
Громкость у клипа своя, но смешивать звук с нескольких дорожек нельзя:
это потребовало бы второго прохода по всему материалу. Модель данных к
разделению готова, само разделение не сделано.
**Осциллограмма звука на клипах.**
**Переходы, эффекты, изменение скорости.** Всё это требует перекодирования и
в замысел не входит.