Куски видео вырезаются и склеиваются копированием потока: сборка ролика занимает время копирования файла, а не время рендера. Композитинга нет принципиально — при перекрытии побеждает клип с верхней дорожки целиком, и картинкой, и звуком. Именно это ограничение и делает сборку копированием возможной. Что внутри: - монтажный стол со своей отрисовкой: дорожки, масштаб, прилипание, схлопывание, громкость на клипе; - предпросмотр прямо с монтажа через libmpv и виртуальный список кусков, без промежуточных файлов; - сборка через ffmpeg с точными резами: перекодируется только огрызок от реза до ближайшего опорного кадра, остальное копируется; - разбор заголовка MP4 для поиска опорных кадров — в 500 раз быстрее опроса ffprobe; - перепаковка контейнеров, кадрирование и поворот, сведения о проекте; - свои цвета всего окна: системные элементы под Windows цвет не берут, поэтому кнопки, полоса меню, журнал и полосы прокрутки нарисованы сами; - окно зависимостей: недостающие ffmpeg, ffprobe и libmpv скачиваются с официальных источников; - кросс-сборка под Windows и работа под Wine. Первый коммит: до этого история не велась.
166 lines
5.6 KiB
ObjectPascal
166 lines
5.6 KiB
ObjectPascal
{
|
||
uMismatchForm — предупреждение о том, что файл не совпадает с проектом.
|
||
|
||
Показывается в момент, когда файл кладут на монтаж, а не когда его
|
||
добавляют в проект: до попадания на монтаж он ни с чем не склеивается,
|
||
и повода тревожить пользователя нет.
|
||
|
||
Три исхода:
|
||
|
||
«Добавить как есть» — клип помечается, и при сборке ролика его придётся
|
||
перекодировать. Быстро сейчас, медленно потом.
|
||
|
||
«Привести к проекту» — файл один раз перекодируется в параметры проекта,
|
||
дальше всё копируется. Медленно сейчас, быстро потом и всегда.
|
||
|
||
«Отмена» — не добавлять.
|
||
}
|
||
unit uMismatchForm;
|
||
|
||
{$mode objfpc}{$H+}
|
||
|
||
interface
|
||
|
||
uses
|
||
Classes, SysUtils, Forms, Controls, Graphics, StdCtrls, Grids, ExtCtrls,
|
||
uMediaProbe, uTheme;
|
||
|
||
type
|
||
TMismatchChoice = (mcCancel, mcAsIs, mcNormalize);
|
||
|
||
{ TMismatchForm }
|
||
|
||
TMismatchForm = class(TForm)
|
||
btnAsIs: TThemedButton;
|
||
btnCancel: TThemedButton;
|
||
btnNormalize: TThemedButton;
|
||
grid: TStringGrid;
|
||
lblHead: TLabel;
|
||
lblTail: TLabel;
|
||
pnlBottom: TPanel;
|
||
pnlTop: TPanel;
|
||
procedure btnAsIsClick(Sender: TObject);
|
||
procedure btnCancelClick(Sender: TObject);
|
||
procedure btnNormalizeClick(Sender: TObject);
|
||
procedure FormCreate(Sender: TObject);
|
||
private
|
||
FChoice: TMismatchChoice;
|
||
public
|
||
procedure Fill(const AFileName: string; const AMismatches: TMismatches);
|
||
property Choice: TMismatchChoice read FChoice;
|
||
end;
|
||
|
||
{ Показывает диалог и возвращает решение пользователя. }
|
||
function AskAboutMismatch(AOwner: TComponent; const AFileName: string;
|
||
const AMismatches: TMismatches): TMismatchChoice;
|
||
|
||
implementation
|
||
|
||
{$R *.lfm}
|
||
|
||
const
|
||
ColWhat = 0;
|
||
ColExpected = 1;
|
||
ColActual = 2;
|
||
ColEffect = 3;
|
||
|
||
{ TMismatchForm }
|
||
|
||
procedure TMismatchForm.FormCreate(Sender: TObject);
|
||
begin
|
||
{ Цвета задаём сами: системная тема может быть светлой, и тогда
|
||
тёмный таймлайн оказался бы в белой рамке. }
|
||
ApplyDarkTheme(Self);
|
||
FChoice := mcCancel;
|
||
grid.ColCount := 4;
|
||
grid.RowCount := 1;
|
||
grid.FixedRows := 1;
|
||
grid.Cells[ColWhat, 0] := 'Параметр';
|
||
grid.Cells[ColExpected, 0] := 'В проекте';
|
||
grid.Cells[ColActual, 0] := 'В файле';
|
||
grid.Cells[ColEffect, 0] := 'Последствие';
|
||
grid.ColWidths[ColWhat] := 200;
|
||
grid.ColWidths[ColExpected] := 170;
|
||
grid.ColWidths[ColActual] := 170;
|
||
grid.ColWidths[ColEffect] := 260;
|
||
end;
|
||
|
||
procedure TMismatchForm.Fill(const AFileName: string;
|
||
const AMismatches: TMismatches);
|
||
var
|
||
I: Integer;
|
||
Blocking: Boolean;
|
||
begin
|
||
lblHead.Caption := 'Файл ' + ExtractFileName(AFileName) +
|
||
' не совпадает с параметрами проекта.';
|
||
|
||
grid.RowCount := Length(AMismatches) + 1;
|
||
for I := 0 to High(AMismatches) do
|
||
begin
|
||
grid.Cells[ColWhat, I + 1] := AMismatches[I].What;
|
||
grid.Cells[ColExpected, I + 1] := AMismatches[I].Expected;
|
||
grid.Cells[ColActual, I + 1] := AMismatches[I].Actual;
|
||
if AMismatches[I].Blocking then
|
||
grid.Cells[ColEffect, I + 1] := 'склейка без перекодирования невозможна'
|
||
else
|
||
grid.Cells[ColEffect, I + 1] := 'к сведению';
|
||
end;
|
||
|
||
Blocking := HasBlocking(AMismatches);
|
||
if Blocking then
|
||
lblTail.Caption :=
|
||
'Куски этого файла нельзя будет скопировать — при сборке они будут ' +
|
||
'пересобраны. Пересобираются только они: остальной материал ' +
|
||
'по-прежнему копируется без потерь и почти мгновенно.' + LineEnding +
|
||
'Приведение файла к параметрам проекта делает ту же работу один раз ' +
|
||
'сейчас, зато каждая последующая сборка остаётся быстрой.'
|
||
else
|
||
lblTail.Caption :=
|
||
'Эти расхождения склейке не мешают — они приведены для сведения.';
|
||
|
||
{ Приводить к проекту имеет смысл только тогда, когда есть что приводить. }
|
||
btnNormalize.Enabled := Blocking;
|
||
if Blocking then
|
||
btnAsIs.Caption := 'Добавить как есть (пересобирать его куски при сборке)'
|
||
else
|
||
btnAsIs.Caption := 'Добавить';
|
||
end;
|
||
|
||
procedure TMismatchForm.btnAsIsClick(Sender: TObject);
|
||
begin
|
||
FChoice := mcAsIs;
|
||
ModalResult := mrOK;
|
||
end;
|
||
|
||
procedure TMismatchForm.btnNormalizeClick(Sender: TObject);
|
||
begin
|
||
FChoice := mcNormalize;
|
||
ModalResult := mrOK;
|
||
end;
|
||
|
||
procedure TMismatchForm.btnCancelClick(Sender: TObject);
|
||
begin
|
||
FChoice := mcCancel;
|
||
ModalResult := mrCancel;
|
||
end;
|
||
|
||
function AskAboutMismatch(AOwner: TComponent; const AFileName: string;
|
||
const AMismatches: TMismatches): TMismatchChoice;
|
||
var
|
||
Form: TMismatchForm;
|
||
begin
|
||
{ Расхождений нет — спрашивать не о чем. }
|
||
if Length(AMismatches) = 0 then
|
||
Exit(mcAsIs);
|
||
|
||
Form := TMismatchForm.Create(AOwner);
|
||
try
|
||
Form.Fill(AFileName, AMismatches);
|
||
Form.ShowModal;
|
||
Result := Form.Choice;
|
||
finally
|
||
Form.Free;
|
||
end;
|
||
end;
|
||
|
||
end.
|