nlve/src/uTransform.pas

334 lines
14 KiB
ObjectPascal
Raw Normal View History

Нелинейный видеоредактор без перекодирования Куски видео вырезаются и склеиваются копированием потока: сборка ролика занимает время копирования файла, а не время рендера. Композитинга нет принципиально — при перекрытии побеждает клип с верхней дорожки целиком, и картинкой, и звуком. Именно это ограничение и делает сборку копированием возможной. Что внутри: - монтажный стол со своей отрисовкой: дорожки, масштаб, прилипание, схлопывание, громкость на клипе; - предпросмотр прямо с монтажа через libmpv и виртуальный список кусков, без промежуточных файлов; - сборка через ffmpeg с точными резами: перекодируется только огрызок от реза до ближайшего опорного кадра, остальное копируется; - разбор заголовка MP4 для поиска опорных кадров — в 500 раз быстрее опроса ffprobe; - перепаковка контейнеров, кадрирование и поворот, сведения о проекте; - свои цвета всего окна: системные элементы под Windows цвет не берут, поэтому кнопки, полоса меню, журнал и полосы прокрутки нарисованы сами; - окно зависимостей: недостающие ffmpeg, ffprobe и libmpv скачиваются с официальных источников; - кросс-сборка под Windows и работа под Wine. Первый коммит: до этого история не велась.
2026-08-19 18:36:16 +03:00
{
uTransform — как вписать кадр исходника в кадр проекта.
Зачем это нужно. Проект имеет одно разрешение на всё — иначе куски не
склеить копированием. Файл с другим разрешением или другим соотношением
сторон надо во что-то превратить, и вариантов ровно три: вписать целиком
с полями, заполнить кадр с обрезкой краёв, растянуть с искажением
пропорций. Раньше молча делалось третье, и вертикальное видео
превращалось в размазанное по ширине — это была ошибка.
Все размеры считаются числами здесь, а не выражениями внутри фильтра
ffmpeg. Выражения там пишутся через экранированные запятые и читаются
тяжело, а проверить посчитанные числа можно глазами прямо в журнале.
Преобразование хранится у файла, а не у клипа: оно описывает, как этот
материал вообще ложится в проект, и одинаково для всех его кусков.
}
unit uTransform;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils;
type
{ Как соотносятся кадр исходника и кадр проекта. }
TFitMode = (
fmFit, // вписать целиком, по краям поля
fmFill, // заполнить кадр, лишнее обрезать
fmStretch // растянуть, пропорции исказятся
);
TMediaTransform = record
Fit: TFitMode;
{ Поворот по часовой стрелке: 0, 90, 180 или 270. }
Rotation: Integer;
FlipH: Boolean;
FlipV: Boolean;
{ Дополнительное увеличение поверх выбранного режима. Единица —
без увеличения. }
Zoom: Double;
{ Смещение кадра в долях от размера кадра проекта. Ноль — по центру. }
OffsetX: Double;
OffsetY: Double;
end;
function DefaultTransform: TMediaTransform;
{ Нужно ли вообще что-то делать: совпадающий по размеру кадр без поворота
и смещений трогать незачем. }
function TransformIsIdentity(const AT: TMediaTransform;
ASrcWidth, ASrcHeight, ADstWidth, ADstHeight: Integer): Boolean;
{ Цепочка фильтров ffmpeg. Пустая строка означает «ничего делать не надо». }
function TransformFilter(const AT: TMediaTransform;
ASrcWidth, ASrcHeight, ADstWidth, ADstHeight: Integer): string;
{ Цепочка для предпросмотра: вписать любой кадр в кадр проекта.
От TransformFilter отличается двумя вещами. Во-первых, размеры исходника
здесь неизвестны — цепочка одна на весь предпросмотр, а куски в нём из
разных файлов, поэтому вместо посчитанных чисел идут выражения самого
ffmpeg. Во-вторых, она знает только вписывание целиком: заполнение с
обрезкой, поворот и смещение задаются для каждого файла отдельно, а
фильтр общий, и выбрать между ними он не может.
Отсюда честное ограничение: предпросмотр показывает кадр проекта верно,
но собственную настройку кадрирования файла не учитывает. Её видно в
окне кадрирования и в готовом ролике. }
Цветовой ключ и живое наложение в предпросмотре Клипу задаётся цвет, который при сборке становится прозрачным. Окно ключа показывает кадр: цвет берётся щелчком по нему, выбитое показано клеткой, допуск и смягчение края — ползунками. Открывается из окна кадрирования и из меню «Правка», на монтаже клип помечен квадратиком выбиваемого цвета. Предпросмотр перестал сводить такие участки в файлы. Плеер играет две дорожки одна поверх другой: оверлей во весь ролик больше не заставляет пересобирать весь фильм после каждой правки. Замерено: наложение стоит 0.4 с на 10 с материала 2688x1512, вчетверо быстрее реального времени. Починено в сборке: уменьшенный клип обкладывался чёрными полями, и они закрывали собой нижнее видео — оставалась видна только дырка, выбитая ключом. Теперь верхний слой кладётся по координатам, тем же, по которым его показывает предпросмотр. Смещение по горизонтали и вертикали работает и там, где картинка меньше кадра проекта: раньше уменьшенный клип нельзя было сдвинуть в угол. Проверки форм теперь создают каждое окно из файла формы: элемент, забытый в классе, вскрывается на сборке, а не при открытии окна.
2026-08-31 00:10:28 +03:00
{ То же самое, но для наложения в плеере: цепочка без обкладывания
полями и отдельно точка, куда встанет получившаяся картинка.
Поля и наложение — две записи одного и того же. Сборка обкладывает
картинку полями до кадра проекта, потому что кусок должен быть
самостоятельным файлом. Плееру поля не нужны и вредны: чёрное поле
закрыло бы нижнее видео. Ему нужны те же самые координаты, но как место
наложения. Считаются они одним и тем же кодом — иначе окно показывало
бы одно, а сборка давала другое. }
function OverlayFilter(const AT: TMediaTransform;
ASrcWidth, ASrcHeight, ADstWidth, ADstHeight: Integer;
out AX, AY, AWidth, AHeight: Integer): string;
Нелинейный видеоредактор без перекодирования Куски видео вырезаются и склеиваются копированием потока: сборка ролика занимает время копирования файла, а не время рендера. Композитинга нет принципиально — при перекрытии побеждает клип с верхней дорожки целиком, и картинкой, и звуком. Именно это ограничение и делает сборку копированием возможной. Что внутри: - монтажный стол со своей отрисовкой: дорожки, масштаб, прилипание, схлопывание, громкость на клипе; - предпросмотр прямо с монтажа через libmpv и виртуальный список кусков, без промежуточных файлов; - сборка через ffmpeg с точными резами: перекодируется только огрызок от реза до ближайшего опорного кадра, остальное копируется; - разбор заголовка MP4 для поиска опорных кадров — в 500 раз быстрее опроса ffprobe; - перепаковка контейнеров, кадрирование и поворот, сведения о проекте; - свои цвета всего окна: системные элементы под Windows цвет не берут, поэтому кнопки, полоса меню, журнал и полосы прокрутки нарисованы сами; - окно зависимостей: недостающие ffmpeg, ffprobe и libmpv скачиваются с официальных источников; - кросс-сборка под Windows и работа под Wine. Первый коммит: до этого история не велась.
2026-08-19 18:36:16 +03:00
function PreviewFitFilter(ADstWidth, ADstHeight: Integer): string;
function FitModeToStr(AMode: TFitMode): string;
function StrToFitMode(const AValue: string): TFitMode;
function FitModeCaption(AMode: TFitMode): string;
implementation
uses
Math, uAppTypes;
function DefaultTransform: TMediaTransform;
begin
Result.Fit := fmFit;
Result.Rotation := 0;
Result.FlipH := False;
Result.FlipV := False;
Result.Zoom := 1;
Result.OffsetX := 0;
Result.OffsetY := 0;
end;
function FitModeToStr(AMode: TFitMode): string;
begin
case AMode of
fmFill: Result := 'fill';
fmStretch: Result := 'stretch';
else
Result := 'fit';
end;
end;
function StrToFitMode(const AValue: string): TFitMode;
begin
if AValue = 'fill' then
Result := fmFill
else if AValue = 'stretch' then
Result := fmStretch
else
Result := fmFit;
end;
function FitModeCaption(AMode: TFitMode): string;
begin
case AMode of
fmFill: Result := 'Заполнить кадр (лишнее обрезается)';
fmStretch: Result := 'Растянуть (пропорции исказятся)';
else
Result := 'Вписать целиком (по краям поля)';
end;
end;
function TransformIsIdentity(const AT: TMediaTransform;
ASrcWidth, ASrcHeight, ADstWidth, ADstHeight: Integer): Boolean;
begin
Result := (AT.Rotation mod 360 = 0) and (not AT.FlipH) and (not AT.FlipV) and
(Abs(AT.Zoom - 1) < 0.001) and (Abs(AT.OffsetX) < 0.001) and
(Abs(AT.OffsetY) < 0.001) and
(ASrcWidth = ADstWidth) and (ASrcHeight = ADstHeight);
end;
function PreviewFitFilter(ADstWidth, ADstHeight: Integer): string;
var
W, H: string;
begin
Result := '';
if (ADstWidth < 2) or (ADstHeight < 2) then
Exit;
W := IntToStr(ADstWidth);
H := IntToStr(ADstHeight);
{ force_original_aspect_ratio=decrease вписывает кадр целиком, не искажая
пропорции; pad добавляет поля до размера проекта; setsar=1 убирает
неквадратный пиксель, иначе mpv растянет уже готовый кадр обратно. }
Result :=
'scale=' + W + ':' + H + ':force_original_aspect_ratio=decrease,' +
'pad=' + W + ':' + H + ':(ow-iw)/2:(oh-ih)/2,' +
'setsar=1';
end;
{ Кодировщики требуют чётных сторон: цветность в yuv420p хранится вдвое
прореженной, и нечётный размер ей некуда деть. }
function MakeEven(AValue: Integer): Integer;
begin
Result := AValue;
if Odd(Result) then
Inc(Result);
if Result < 2 then
Result := 2;
end;
Цветовой ключ и живое наложение в предпросмотре Клипу задаётся цвет, который при сборке становится прозрачным. Окно ключа показывает кадр: цвет берётся щелчком по нему, выбитое показано клеткой, допуск и смягчение края — ползунками. Открывается из окна кадрирования и из меню «Правка», на монтаже клип помечен квадратиком выбиваемого цвета. Предпросмотр перестал сводить такие участки в файлы. Плеер играет две дорожки одна поверх другой: оверлей во весь ролик больше не заставляет пересобирать весь фильм после каждой правки. Замерено: наложение стоит 0.4 с на 10 с материала 2688x1512, вчетверо быстрее реального времени. Починено в сборке: уменьшенный клип обкладывался чёрными полями, и они закрывали собой нижнее видео — оставалась видна только дырка, выбитая ключом. Теперь верхний слой кладётся по координатам, тем же, по которым его показывает предпросмотр. Смещение по горизонтали и вертикали работает и там, где картинка меньше кадра проекта: раньше уменьшенный клип нельзя было сдвинуть в угол. Проверки форм теперь создают каждое окно из файла формы: элемент, забытый в классе, вскрывается на сборке, а не при открытии окна.
2026-08-31 00:10:28 +03:00
{ Общая часть: цепочка до обкладывания полями и место, куда встанет
получившаяся картинка в кадре проекта. }
function BuildTransform(const AT: TMediaTransform;
ASrcWidth, ASrcHeight, ADstWidth, ADstHeight: Integer;
out AX, AY, AWidth, AHeight: Integer): string;
Нелинейный видеоредактор без перекодирования Куски видео вырезаются и склеиваются копированием потока: сборка ролика занимает время копирования файла, а не время рендера. Композитинга нет принципиально — при перекрытии побеждает клип с верхней дорожки целиком, и картинкой, и звуком. Именно это ограничение и делает сборку копированием возможной. Что внутри: - монтажный стол со своей отрисовкой: дорожки, масштаб, прилипание, схлопывание, громкость на клипе; - предпросмотр прямо с монтажа через libmpv и виртуальный список кусков, без промежуточных файлов; - сборка через ffmpeg с точными резами: перекодируется только огрызок от реза до ближайшего опорного кадра, остальное копируется; - разбор заголовка MP4 для поиска опорных кадров — в 500 раз быстрее опроса ffprobe; - перепаковка контейнеров, кадрирование и поворот, сведения о проекте; - свои цвета всего окна: системные элементы под Windows цвет не берут, поэтому кнопки, полоса меню, журнал и полосы прокрутки нарисованы сами; - окно зависимостей: недостающие ffmpeg, ffprobe и libmpv скачиваются с официальных источников; - кросс-сборка под Windows и работа под Wine. Первый коммит: до этого история не велась.
2026-08-19 18:36:16 +03:00
var
Chain: string;
K, KFit, KFill: Double;
SW, SH: Integer;
CW, CH, CX, CY: Integer;
Кадрирование: поворот после обрезки, рамка не расходится с настройкой Поворот и отражение применялись к исходному кадру, а обрезка шла после них. Из-за этого рамка показывала одно место, а вырезалось другое. Теперь наоборот: сначала выбирается кусок, потом он поворачивается и отражается. Если после поворота кусок лёг поперёк кадра проекта, он вписывается целиком с полями. Проверено: с отражением левый и правый края вырезанного куска меняются местами, то есть отражается именно он, а не оригинал. Вторая причина расхождения — предел ползунков смещения. Рамку можно было утащить туда, куда ползунок не дотягивается: рамка рисовалась от неограниченного значения, а фильтр считался от обрезанного. Предел расширен вчетверо, а рамка теперь пересчитывается обратно из настройки — если та упёрлась, это сразу видно. Отдельно: разбор звука больше не держит главный поток. Перезапуск ждал окончания текущего файла, а это чтение гигабайтов — окно всё это время не отвечало, и меню не открывалось вовсе. Теперь проход доигрывает сам и запускается заново, а идёт он с наинизшим приоритетом.
2026-08-20 19:23:54 +03:00
PW, PH, PX, PY: Integer;
Нелинейный видеоредактор без перекодирования Куски видео вырезаются и склеиваются копированием потока: сборка ролика занимает время копирования файла, а не время рендера. Композитинга нет принципиально — при перекрытии побеждает клип с верхней дорожки целиком, и картинкой, и звуком. Именно это ограничение и делает сборку копированием возможной. Что внутри: - монтажный стол со своей отрисовкой: дорожки, масштаб, прилипание, схлопывание, громкость на клипе; - предпросмотр прямо с монтажа через libmpv и виртуальный список кусков, без промежуточных файлов; - сборка через ffmpeg с точными резами: перекодируется только огрызок от реза до ближайшего опорного кадра, остальное копируется; - разбор заголовка MP4 для поиска опорных кадров — в 500 раз быстрее опроса ffprobe; - перепаковка контейнеров, кадрирование и поворот, сведения о проекте; - свои цвета всего окна: системные элементы под Windows цвет не берут, поэтому кнопки, полоса меню, журнал и полосы прокрутки нарисованы сами; - окно зависимостей: недостающие ffmpeg, ffprobe и libmpv скачиваются с официальных источников; - кросс-сборка под Windows и работа под Wine. Первый коммит: до этого история не велась.
2026-08-19 18:36:16 +03:00
Rot: Integer;
procedure Add(const AFilter: string);
begin
if Chain <> '' then
Chain := Chain + ',';
Chain := Chain + AFilter;
end;
begin
Result := '';
if (ASrcWidth <= 0) or (ASrcHeight <= 0) or
(ADstWidth <= 0) or (ADstHeight <= 0) then
Exit;
Chain := '';
Кадрирование: поворот после обрезки, рамка не расходится с настройкой Поворот и отражение применялись к исходному кадру, а обрезка шла после них. Из-за этого рамка показывала одно место, а вырезалось другое. Теперь наоборот: сначала выбирается кусок, потом он поворачивается и отражается. Если после поворота кусок лёг поперёк кадра проекта, он вписывается целиком с полями. Проверено: с отражением левый и правый края вырезанного куска меняются местами, то есть отражается именно он, а не оригинал. Вторая причина расхождения — предел ползунков смещения. Рамку можно было утащить туда, куда ползунок не дотягивается: рамка рисовалась от неограниченного значения, а фильтр считался от обрезанного. Предел расширен вчетверо, а рамка теперь пересчитывается обратно из настройки — если та упёрлась, это сразу видно. Отдельно: разбор звука больше не держит главный поток. Перезапуск ждал окончания текущего файла, а это чтение гигабайтов — окно всё это время не отвечало, и меню не открывалось вовсе. Теперь проход доигрывает сам и запускается заново, а идёт он с наинизшим приоритетом.
2026-08-20 19:23:54 +03:00
{ Порядок действий: сначала выбирается кусок кадра, и только потом он
поворачивается и отражается.
Так это устроено в монтажных программах, и так понятнее: рамка
показывает, что́ останется, а поворот применяется к оставшемуся. При
обратном порядке обрезался бы уже перевёрнутый кадр, и в рамку
попадало бы совсем не то, что видно. }
Нелинейный видеоредактор без перекодирования Куски видео вырезаются и склеиваются копированием потока: сборка ролика занимает время копирования файла, а не время рендера. Композитинга нет принципиально — при перекрытии побеждает клип с верхней дорожки целиком, и картинкой, и звуком. Именно это ограничение и делает сборку копированием возможной. Что внутри: - монтажный стол со своей отрисовкой: дорожки, масштаб, прилипание, схлопывание, громкость на клипе; - предпросмотр прямо с монтажа через libmpv и виртуальный список кусков, без промежуточных файлов; - сборка через ffmpeg с точными резами: перекодируется только огрызок от реза до ближайшего опорного кадра, остальное копируется; - разбор заголовка MP4 для поиска опорных кадров — в 500 раз быстрее опроса ffprobe; - перепаковка контейнеров, кадрирование и поворот, сведения о проекте; - свои цвета всего окна: системные элементы под Windows цвет не берут, поэтому кнопки, полоса меню, журнал и полосы прокрутки нарисованы сами; - окно зависимостей: недостающие ffmpeg, ffprobe и libmpv скачиваются с официальных источников; - кросс-сборка под Windows и работа под Wine. Первый коммит: до этого история не велась.
2026-08-19 18:36:16 +03:00
{ --- Масштаб --- }
if AT.Fit = fmStretch then
begin
SW := MakeEven(ADstWidth);
SH := MakeEven(ADstHeight);
end
else
begin
Кадрирование: поворот после обрезки, рамка не расходится с настройкой Поворот и отражение применялись к исходному кадру, а обрезка шла после них. Из-за этого рамка показывала одно место, а вырезалось другое. Теперь наоборот: сначала выбирается кусок, потом он поворачивается и отражается. Если после поворота кусок лёг поперёк кадра проекта, он вписывается целиком с полями. Проверено: с отражением левый и правый края вырезанного куска меняются местами, то есть отражается именно он, а не оригинал. Вторая причина расхождения — предел ползунков смещения. Рамку можно было утащить туда, куда ползунок не дотягивается: рамка рисовалась от неограниченного значения, а фильтр считался от обрезанного. Предел расширен вчетверо, а рамка теперь пересчитывается обратно из настройки — если та упёрлась, это сразу видно. Отдельно: разбор звука больше не держит главный поток. Перезапуск ждал окончания текущего файла, а это чтение гигабайтов — окно всё это время не отвечало, и меню не открывалось вовсе. Теперь проход доигрывает сам и запускается заново, а идёт он с наинизшим приоритетом.
2026-08-20 19:23:54 +03:00
KFit := Min(ADstWidth / ASrcWidth, ADstHeight / ASrcHeight);
KFill := Max(ADstWidth / ASrcWidth, ADstHeight / ASrcHeight);
Нелинейный видеоредактор без перекодирования Куски видео вырезаются и склеиваются копированием потока: сборка ролика занимает время копирования файла, а не время рендера. Композитинга нет принципиально — при перекрытии побеждает клип с верхней дорожки целиком, и картинкой, и звуком. Именно это ограничение и делает сборку копированием возможной. Что внутри: - монтажный стол со своей отрисовкой: дорожки, масштаб, прилипание, схлопывание, громкость на клипе; - предпросмотр прямо с монтажа через libmpv и виртуальный список кусков, без промежуточных файлов; - сборка через ffmpeg с точными резами: перекодируется только огрызок от реза до ближайшего опорного кадра, остальное копируется; - разбор заголовка MP4 для поиска опорных кадров — в 500 раз быстрее опроса ffprobe; - перепаковка контейнеров, кадрирование и поворот, сведения о проекте; - свои цвета всего окна: системные элементы под Windows цвет не берут, поэтому кнопки, полоса меню, журнал и полосы прокрутки нарисованы сами; - окно зависимостей: недостающие ffmpeg, ffprobe и libmpv скачиваются с официальных источников; - кросс-сборка под Windows и работа под Wine. Первый коммит: до этого история не велась.
2026-08-19 18:36:16 +03:00
if AT.Fit = fmFill then
K := KFill
else
K := KFit;
if AT.Zoom > 0 then
K := K * AT.Zoom;
Кадрирование: поворот после обрезки, рамка не расходится с настройкой Поворот и отражение применялись к исходному кадру, а обрезка шла после них. Из-за этого рамка показывала одно место, а вырезалось другое. Теперь наоборот: сначала выбирается кусок, потом он поворачивается и отражается. Если после поворота кусок лёг поперёк кадра проекта, он вписывается целиком с полями. Проверено: с отражением левый и правый края вырезанного куска меняются местами, то есть отражается именно он, а не оригинал. Вторая причина расхождения — предел ползунков смещения. Рамку можно было утащить туда, куда ползунок не дотягивается: рамка рисовалась от неограниченного значения, а фильтр считался от обрезанного. Предел расширен вчетверо, а рамка теперь пересчитывается обратно из настройки — если та упёрлась, это сразу видно. Отдельно: разбор звука больше не держит главный поток. Перезапуск ждал окончания текущего файла, а это чтение гигабайтов — окно всё это время не отвечало, и меню не открывалось вовсе. Теперь проход доигрывает сам и запускается заново, а идёт он с наинизшим приоритетом.
2026-08-20 19:23:54 +03:00
SW := MakeEven(Round(ASrcWidth * K));
SH := MakeEven(Round(ASrcHeight * K));
Нелинейный видеоредактор без перекодирования Куски видео вырезаются и склеиваются копированием потока: сборка ролика занимает время копирования файла, а не время рендера. Композитинга нет принципиально — при перекрытии побеждает клип с верхней дорожки целиком, и картинкой, и звуком. Именно это ограничение и делает сборку копированием возможной. Что внутри: - монтажный стол со своей отрисовкой: дорожки, масштаб, прилипание, схлопывание, громкость на клипе; - предпросмотр прямо с монтажа через libmpv и виртуальный список кусков, без промежуточных файлов; - сборка через ffmpeg с точными резами: перекодируется только огрызок от реза до ближайшего опорного кадра, остальное копируется; - разбор заголовка MP4 для поиска опорных кадров — в 500 раз быстрее опроса ffprobe; - перепаковка контейнеров, кадрирование и поворот, сведения о проекте; - свои цвета всего окна: системные элементы под Windows цвет не берут, поэтому кнопки, полоса меню, журнал и полосы прокрутки нарисованы сами; - окно зависимостей: недостающие ffmpeg, ffprobe и libmpv скачиваются с официальных источников; - кросс-сборка под Windows и работа под Wine. Первый коммит: до этого история не велась.
2026-08-19 18:36:16 +03:00
end;
Add(Format('scale=%d:%d', [SW, SH]));
{ --- Обрезка того, что вылезло за кадр --- }
CW := Min(SW, MakeEven(ADstWidth));
CH := Min(SH, MakeEven(ADstHeight));
if (CW < SW) or (CH < SH) then
begin
{ Смещение задаётся в долях кадра проекта и прижимается к границам:
выйти за пределы картинки нельзя, ffmpeg на это ругается. }
CX := Round((SW - CW) / 2 + AT.OffsetX * ADstWidth);
CY := Round((SH - CH) / 2 + AT.OffsetY * ADstHeight);
CX := Max(0, Min(SW - CW, CX));
CY := Max(0, Min(SH - CH, CY));
Add(Format('crop=%d:%d:%d:%d', [CW, CH, CX, CY]));
end;
Кадрирование: поворот после обрезки, рамка не расходится с настройкой Поворот и отражение применялись к исходному кадру, а обрезка шла после них. Из-за этого рамка показывала одно место, а вырезалось другое. Теперь наоборот: сначала выбирается кусок, потом он поворачивается и отражается. Если после поворота кусок лёг поперёк кадра проекта, он вписывается целиком с полями. Проверено: с отражением левый и правый края вырезанного куска меняются местами, то есть отражается именно он, а не оригинал. Вторая причина расхождения — предел ползунков смещения. Рамку можно было утащить туда, куда ползунок не дотягивается: рамка рисовалась от неограниченного значения, а фильтр считался от обрезанного. Предел расширен вчетверо, а рамка теперь пересчитывается обратно из настройки — если та упёрлась, это сразу видно. Отдельно: разбор звука больше не держит главный поток. Перезапуск ждал окончания текущего файла, а это чтение гигабайтов — окно всё это время не отвечало, и меню не открывалось вовсе. Теперь проход доигрывает сам и запускается заново, а идёт он с наинизшим приоритетом.
2026-08-20 19:23:54 +03:00
{ --- Поворот и отражение оставшегося куска --- }
Rot := ((AT.Rotation mod 360) + 360) mod 360;
case Rot of
90:
begin
{ transpose=1 — поворот на 90 по часовой стрелке. }
Add('transpose=1');
PW := CH; PH := CW;
end;
180:
begin
{ Два поворота подряд дешевле и точнее, чем поворот на
произвольный угол: последний пересчитывает каждый пиксель. }
Add('transpose=1,transpose=1');
PW := CW; PH := CH;
end;
270:
begin
Add('transpose=2');
PW := CH; PH := CW;
end;
else
PW := CW; PH := CH;
end;
if AT.FlipH then
Add('hflip');
if AT.FlipV then
Add('vflip');
{ --- Приведение к кадру проекта --- }
{ После поворота на 90 кусок лежит поперёк и в кадр проекта уже не
вписывается. Вписываем целиком, с полями. }
if (PW > ADstWidth) or (PH > ADstHeight) then
begin
K := Min(ADstWidth / PW, ADstHeight / PH);
PW := MakeEven(Round(PW * K));
PH := MakeEven(Round(PH * K));
Add(Format('scale=%d:%d', [PW, PH]));
end;
Цветовой ключ и живое наложение в предпросмотре Клипу задаётся цвет, который при сборке становится прозрачным. Окно ключа показывает кадр: цвет берётся щелчком по нему, выбитое показано клеткой, допуск и смягчение края — ползунками. Открывается из окна кадрирования и из меню «Правка», на монтаже клип помечен квадратиком выбиваемого цвета. Предпросмотр перестал сводить такие участки в файлы. Плеер играет две дорожки одна поверх другой: оверлей во весь ролик больше не заставляет пересобирать весь фильм после каждой правки. Замерено: наложение стоит 0.4 с на 10 с материала 2688x1512, вчетверо быстрее реального времени. Починено в сборке: уменьшенный клип обкладывался чёрными полями, и они закрывали собой нижнее видео — оставалась видна только дырка, выбитая ключом. Теперь верхний слой кладётся по координатам, тем же, по которым его показывает предпросмотр. Смещение по горизонтали и вертикали работает и там, где картинка меньше кадра проекта: раньше уменьшенный клип нельзя было сдвинуть в угол. Проверки форм теперь создают каждое окно из файла формы: элемент, забытый в классе, вскрывается на сборке, а не при открытии окна.
2026-08-31 00:10:28 +03:00
{ Смещение двигает картинку и там, где её не обрезали. Когда картинка
больше кадра, двигать можно лишь окно, через которое на неё смотрят,
— это делает crop выше. Когда она меньше, двигать надо её саму по
полю, иначе уменьшенный кадр навсегда прибит к середине и в угол его
не отправить. Обе стороны считают смещение в долях кадра проекта, так
что число значит одно и то же. }
PX := (ADstWidth - PW) div 2 + Round(AT.OffsetX * ADstWidth);
PY := (ADstHeight - PH) div 2 + Round(AT.OffsetY * ADstHeight);
PX := Max(0, Min(Max(0, ADstWidth - PW), PX));
PY := Max(0, Min(Max(0, ADstHeight - PH), PY));
AX := PX;
AY := PY;
AWidth := PW;
AHeight := PH;
Result := Chain;
end;
function TransformFilter(const AT: TMediaTransform;
ASrcWidth, ASrcHeight, ADstWidth, ADstHeight: Integer): string;
var
X, Y, W, H: Integer;
begin
Result := BuildTransform(AT, ASrcWidth, ASrcHeight, ADstWidth, ADstHeight,
X, Y, W, H);
if Result = '' then
Exit;
if (W < ADstWidth) or (H < ADstHeight) then
Result := Result + Format(',pad=%d:%d:%d:%d', [ADstWidth, ADstHeight, X, Y]);
Нелинейный видеоредактор без перекодирования Куски видео вырезаются и склеиваются копированием потока: сборка ролика занимает время копирования файла, а не время рендера. Композитинга нет принципиально — при перекрытии побеждает клип с верхней дорожки целиком, и картинкой, и звуком. Именно это ограничение и делает сборку копированием возможной. Что внутри: - монтажный стол со своей отрисовкой: дорожки, масштаб, прилипание, схлопывание, громкость на клипе; - предпросмотр прямо с монтажа через libmpv и виртуальный список кусков, без промежуточных файлов; - сборка через ffmpeg с точными резами: перекодируется только огрызок от реза до ближайшего опорного кадра, остальное копируется; - разбор заголовка MP4 для поиска опорных кадров — в 500 раз быстрее опроса ffprobe; - перепаковка контейнеров, кадрирование и поворот, сведения о проекте; - свои цвета всего окна: системные элементы под Windows цвет не берут, поэтому кнопки, полоса меню, журнал и полосы прокрутки нарисованы сами; - окно зависимостей: недостающие ffmpeg, ffprobe и libmpv скачиваются с официальных источников; - кросс-сборка под Windows и работа под Wine. Первый коммит: до этого история не велась.
2026-08-19 18:36:16 +03:00
{ Пиксель считаем квадратным: иначе плеер растянет кадр по своему
разумению, и результат разойдётся с тем, что видно в предпросмотре. }
Цветовой ключ и живое наложение в предпросмотре Клипу задаётся цвет, который при сборке становится прозрачным. Окно ключа показывает кадр: цвет берётся щелчком по нему, выбитое показано клеткой, допуск и смягчение края — ползунками. Открывается из окна кадрирования и из меню «Правка», на монтаже клип помечен квадратиком выбиваемого цвета. Предпросмотр перестал сводить такие участки в файлы. Плеер играет две дорожки одна поверх другой: оверлей во весь ролик больше не заставляет пересобирать весь фильм после каждой правки. Замерено: наложение стоит 0.4 с на 10 с материала 2688x1512, вчетверо быстрее реального времени. Починено в сборке: уменьшенный клип обкладывался чёрными полями, и они закрывали собой нижнее видео — оставалась видна только дырка, выбитая ключом. Теперь верхний слой кладётся по координатам, тем же, по которым его показывает предпросмотр. Смещение по горизонтали и вертикали работает и там, где картинка меньше кадра проекта: раньше уменьшенный клип нельзя было сдвинуть в угол. Проверки форм теперь создают каждое окно из файла формы: элемент, забытый в классе, вскрывается на сборке, а не при открытии окна.
2026-08-31 00:10:28 +03:00
Result := Result + ',setsar=1';
end;
function OverlayFilter(const AT: TMediaTransform;
ASrcWidth, ASrcHeight, ADstWidth, ADstHeight: Integer;
out AX, AY, AWidth, AHeight: Integer): string;
begin
Result := BuildTransform(AT, ASrcWidth, ASrcHeight, ADstWidth, ADstHeight,
AX, AY, AWidth, AHeight);
if Result <> '' then
Result := Result + ',setsar=1';
Нелинейный видеоредактор без перекодирования Куски видео вырезаются и склеиваются копированием потока: сборка ролика занимает время копирования файла, а не время рендера. Композитинга нет принципиально — при перекрытии побеждает клип с верхней дорожки целиком, и картинкой, и звуком. Именно это ограничение и делает сборку копированием возможной. Что внутри: - монтажный стол со своей отрисовкой: дорожки, масштаб, прилипание, схлопывание, громкость на клипе; - предпросмотр прямо с монтажа через libmpv и виртуальный список кусков, без промежуточных файлов; - сборка через ffmpeg с точными резами: перекодируется только огрызок от реза до ближайшего опорного кадра, остальное копируется; - разбор заголовка MP4 для поиска опорных кадров — в 500 раз быстрее опроса ffprobe; - перепаковка контейнеров, кадрирование и поворот, сведения о проекте; - свои цвета всего окна: системные элементы под Windows цвет не берут, поэтому кнопки, полоса меню, журнал и полосы прокрутки нарисованы сами; - окно зависимостей: недостающие ffmpeg, ffprobe и libmpv скачиваются с официальных источников; - кросс-сборка под Windows и работа под Wine. Первый коммит: до этого история не велась.
2026-08-19 18:36:16 +03:00
end;
end.