Ключ и прозрачность видно прямо в предпросмотре
Оверлей показывался как есть, со своим фоном: чтобы увидеть, что выйдет после сборки, приходилось собирать. Теперь выбитый цвет и приглушение считаются в той же цепочке плеера, на лету. Строки фильтров стали общими со сборкой (ColorKeyFilter, OpacityFilter в uTransform): окно и готовый ролик больше не могут разойтись по тому, как записан цвет или порог. Переключатель в меню «Вид», состояние переживает перезапуск. Замер на 2688x1512, 600 кадров: основа 2.07 с, с наложением 2.43 с, с ключом и прозрачностью 2.66 с — вчетверо быстрее реального времени, но на слабой машине это заметнее, поэтому выключить можно. В бэклог записано окно настроек: переключателей, которые стоят машинного времени, набралось много, и живут они кто где — кнопками внизу, в меню, а выбор кодировщика вообще нигде.
This commit is contained in:
parent
4c422ef57d
commit
d098f1621e
7 changed files with 136 additions and 14 deletions
22
BACKLOG.md
22
BACKLOG.md
|
|
@ -76,6 +76,28 @@ ffmpeg проблемой не является: он запускается о
|
|||
MP4/H.264 нельзя — контейнер и кодек альфу не хранят. Для этого нужен
|
||||
отдельный вывод в VP9 или ProRes 4444.
|
||||
|
||||
## Окно настроек программы
|
||||
|
||||
Переключателей, которые стоят машинного времени, становится больше, а
|
||||
живут они кто где: прилипание и схлопывание — кнопками внизу, ключ в
|
||||
предпросмотре — в меню «Вид», выбор кодировщика вообще нигде (берётся
|
||||
NVENC, если он есть). Это разрозненно и не даёт увидеть общую цену.
|
||||
|
||||
Нужно одно окно, где собрано всё, что можно выключить ради скорости:
|
||||
|
||||
- ключ и прозрачность в предпросмотре (сейчас в меню «Вид»);
|
||||
- наложение в предпросмотре вообще — на слабой машине может не хватать и
|
||||
его, тогда честнее показывать одну основу;
|
||||
- кодировщик для пересборки: аппаратный или процессорный, и какой
|
||||
именно;
|
||||
- где держать кэш и когда его убирать;
|
||||
- прилипание клипов и курсора (сейчас кнопками, но им место и здесь).
|
||||
|
||||
Почему это важно: разработка идёт на машине с быстрой видеокартой, и
|
||||
всё, что здесь считается «недорогим», на другой может оказаться
|
||||
неподъёмным. Пока цифры замеряны только на одной машине, у человека
|
||||
должна быть возможность выключить любую из этих трат.
|
||||
|
||||
## Мелочи, до которых не дошли руки
|
||||
|
||||
**Значок файла программы под Windows.** Значок окна задаётся из
|
||||
|
|
|
|||
12
README.md
12
README.md
|
|
@ -141,6 +141,18 @@ xdg-mime query default application/x-nlve-project
|
|||
липнут при перетаскивании, курсор — при щелчке, и одно бывает нужно без
|
||||
другого.
|
||||
|
||||
**Ключ и прозрачность в предпросмотре** (меню «Вид»). Включено — окно
|
||||
показывает то же, что даст сборка: выбитый цвет и приглушение считаются
|
||||
на лету. Выключено — оверлей виден целиком, со своим фоном: так понятно,
|
||||
куда он встал и где его края.
|
||||
|
||||
Считается это по площади самого оверлея, а не всего кадра. Замер на
|
||||
2688x1512 (600 кадров, десять секунд материала): основа 2.07 с, с
|
||||
наложением 2.43 с, с ключом и прозрачностью 2.66 с. То есть наложение
|
||||
стоит 0.36 с, ключ добавляет ещё 0.23 с — вчетверо быстрее реального
|
||||
времени. На слабой машине это может быть заметнее, потому выключатель и
|
||||
есть.
|
||||
|
||||
**Схлопывание.** Кнопка внизу показывает и режим, и состояние: утоплена —
|
||||
автосхлопывание включено, и тогда при перетаскивании клипа всё, что
|
||||
начинается не раньше него, едет следом. Стрелка рядом открывает выбор
|
||||
|
|
|
|||
|
|
@ -235,7 +235,7 @@ function ParseProgressLine(const ALine: string; var ASeconds: Double;
|
|||
implementation
|
||||
|
||||
uses
|
||||
StrUtils;
|
||||
StrUtils, uTransform;
|
||||
|
||||
{ Общие параметры вывода в формате проекта. Объявлено заранее, потому что
|
||||
используется выше по тексту, чем определено. }
|
||||
|
|
@ -318,12 +318,8 @@ begin
|
|||
масштабом. Цвет записывается как 0xRRGGBB — порядок обратный тому,
|
||||
в каком он хранится у нас. }
|
||||
if ALayers[I].KeyEnabled then
|
||||
Fit := Fit + Format(',colorkey=0x%.2x%.2x%.2x:%s:%s',
|
||||
[ALayers[I].KeyColor and $FF,
|
||||
(ALayers[I].KeyColor shr 8) and $FF,
|
||||
(ALayers[I].KeyColor shr 16) and $FF,
|
||||
SecondsToStr(ALayers[I].KeySimilarity / 100),
|
||||
SecondsToStr(ALayers[I].KeyBlend / 100)]);
|
||||
Fit := Fit + ',' + ColorKeyFilter(ALayers[I].KeyColor,
|
||||
ALayers[I].KeySimilarity, ALayers[I].KeyBlend);
|
||||
Graph := Graph + Format('[%d:v]%s,format=yuva420p[v%d];', [I, Fit, I]);
|
||||
end;
|
||||
|
||||
|
|
|
|||
|
|
@ -140,11 +140,17 @@ function FlattenProject(AProject: TProject;
|
|||
под ними видно то, что лежит ниже, — а выдаются отдельным списком со
|
||||
временем и местом в кадре.
|
||||
|
||||
AWithKey — применять ли к оверлею цветовой ключ и прозрачность прямо в
|
||||
плеере. Тогда окно показывает то же, что даст сборка. Стоит это
|
||||
недорого: фильтр работает по площади самого оверлея, а не всего кадра,
|
||||
но на слабой машине лишним не будет, поэтому решает вызывающий.
|
||||
|
||||
Для сборки ролика это не применяется: там всё сводится по-настоящему. }
|
||||
function FlattenPreview(AProject: TProject;
|
||||
out AVideo, AAudio: TFlatSegmentArray;
|
||||
out AOverlays: TPreviewOverlays;
|
||||
out AMissing: TGenRequests): Boolean;
|
||||
out AMissing: TGenRequests;
|
||||
AWithKey: Boolean = False): Boolean;
|
||||
|
||||
{ Путь к сгенерированному клипу. Имя выводится из содержимого запроса,
|
||||
поэтому одинаковые запросы дают один и тот же файл, и он делается
|
||||
|
|
@ -1034,7 +1040,8 @@ end;
|
|||
function FlattenPreview(AProject: TProject;
|
||||
out AVideo, AAudio: TFlatSegmentArray;
|
||||
out AOverlays: TPreviewOverlays;
|
||||
out AMissing: TGenRequests): Boolean;
|
||||
out AMissing: TGenRequests;
|
||||
AWithKey: Boolean): Boolean;
|
||||
var
|
||||
Hidden: TFPList;
|
||||
MissA: TGenRequests;
|
||||
|
|
@ -1108,6 +1115,17 @@ begin
|
|||
Item.Info.Width, Item.Info.Height,
|
||||
AProject.Master.Width, AProject.Master.Height,
|
||||
Ov.X, Ov.Y, Ov.Width, Ov.Height);
|
||||
{ Порядок тот же, что при сборке: сперва вписали в кадр, потом
|
||||
выбили цвет, потом приглушили. Иначе смягчение края разошлось бы
|
||||
с тем, что даст готовый ролик. }
|
||||
if AWithKey then
|
||||
begin
|
||||
if Clip.ColorKey.Enabled then
|
||||
Ov.Filter := Ov.Filter + ',' + ColorKeyFilter(Clip.ColorKey.Color,
|
||||
Clip.ColorKey.Similarity, Clip.ColorKey.Blend);
|
||||
if OpacityFilter(Clip.Opacity) <> '' then
|
||||
Ov.Filter := Ov.Filter + ',' + OpacityFilter(Clip.Opacity);
|
||||
end;
|
||||
SetLength(AOverlays, N + 1);
|
||||
AOverlays[N] := Ov;
|
||||
Inc(N);
|
||||
|
|
|
|||
|
|
@ -129,6 +129,9 @@ type
|
|||
первую. }
|
||||
FErrorCount: Integer;
|
||||
FLogButton: TThemedButton;
|
||||
{ Пункт «Ключ и прозрачность в предпросмотре»: галку в нём приходится
|
||||
держать за руку — она не следует за настройкой сама. }
|
||||
FKeyPreviewItem: TMenuItem;
|
||||
{ Показанная доля выполнения. Отдельно от настоящей, потому что растёт
|
||||
она по своим правилам — см. AdvanceJobBar. }
|
||||
FJobShown: Double;
|
||||
|
|
@ -238,6 +241,7 @@ type
|
|||
procedure AdvanceJobBar;
|
||||
procedure LogButtonClick(Sender: TObject);
|
||||
procedure MenuToggleLog(Sender: TObject);
|
||||
procedure MenuTogglePreviewKey(Sender: TObject);
|
||||
procedure MenuAbout(Sender: TObject);
|
||||
procedure MenuAddText(Sender: TObject);
|
||||
{ Открывает редактор текста для клипа. True — что-то изменилось. }
|
||||
|
|
@ -583,8 +587,10 @@ procedure TMainForm.BuildMenu;
|
|||
FMenu.Items.Add(Result);
|
||||
end;
|
||||
|
||||
procedure AddItem(AParent: TMenuItem; const ACaption: string;
|
||||
AHandler: TNotifyEvent; AShortCut: TShortCut = 0);
|
||||
{ Возвращает созданный пункт: у пунктов с галкой её потом надо ставить
|
||||
и снимать, а искать пункт по названию — способ ненадёжный. }
|
||||
function AddItem(AParent: TMenuItem; const ACaption: string;
|
||||
AHandler: TNotifyEvent; AShortCut: TShortCut = 0): TMenuItem;
|
||||
var
|
||||
Item: TMenuItem;
|
||||
begin
|
||||
|
|
@ -593,6 +599,7 @@ procedure TMainForm.BuildMenu;
|
|||
Item.OnClick := AHandler;
|
||||
Item.ShortCut := AShortCut;
|
||||
AParent.Add(Item);
|
||||
Result := Item;
|
||||
end;
|
||||
|
||||
procedure AddSep(AParent: TMenuItem);
|
||||
|
|
@ -675,6 +682,11 @@ begin
|
|||
AddItem(MView, 'Отдалить', @MenuZoomOut, ShortCut(VK_SUBTRACT, [ssCtrl]));
|
||||
AddSep(MView);
|
||||
AddItem(MView, 'Журнал сообщений', @MenuToggleLog, ShortCut(VK_L, [ssCtrl]));
|
||||
AddSep(MView);
|
||||
FKeyPreviewItem := AddItem(MView, 'Ключ и прозрачность в предпросмотре',
|
||||
@MenuTogglePreviewKey);
|
||||
FKeyPreviewItem.AutoCheck := False;
|
||||
FKeyPreviewItem.Checked := FSettings.PreviewKey;
|
||||
|
||||
MProject := AddTop('Проект');
|
||||
AddItem(MProject, 'Сведения о проекте...', @MenuProjectInfo);
|
||||
|
|
@ -1882,7 +1894,8 @@ begin
|
|||
|
||||
{ Уплощение для предпросмотра: клипы, которые кладутся поверх, идут
|
||||
отдельно. Плеер положит их сам, ничего не собирая. }
|
||||
if not FlattenPreview(FProject, Segments, AudioSegs, Overlays, Missing) then
|
||||
if not FlattenPreview(FProject, Segments, AudioSegs, Overlays, Missing,
|
||||
FSettings.PreviewKey) then
|
||||
begin
|
||||
FPlayer.Stop;
|
||||
FLastLoadedEdl := '';
|
||||
|
|
@ -3111,6 +3124,29 @@ end;
|
|||
|
||||
{ К началу самого раннего клипа по всем дорожкам. Если монтаж начинается
|
||||
с пятой секунды, ноль — не то место, куда хотят попасть. }
|
||||
{ Показывать ли в предпросмотре то, что даст сборка.
|
||||
|
||||
Выключено — оверлей виден целиком, со своим фоном: так понятно, куда он
|
||||
встал и где его края. Включено — выбитый цвет и прозрачность считаются
|
||||
на лету, и окно совпадает с готовым роликом.
|
||||
|
||||
Считается это не бесплатно, хотя и недорого: фильтр работает по площади
|
||||
оверлея, а не всего кадра. Замерено на 2688x1512: наложение стоит 0.36 с
|
||||
на десять секунд материала, ключ добавляет ещё 0.23 с. }
|
||||
procedure TMainForm.MenuTogglePreviewKey(Sender: TObject);
|
||||
begin
|
||||
FSettings.PreviewKey := not FSettings.PreviewKey;
|
||||
FKeyPreviewItem.Checked := FSettings.PreviewKey;
|
||||
{ Список кусков не изменился, а показывать надо иначе — заставляем
|
||||
пересобрать цепочку. }
|
||||
FLastLoadedEdl := '';
|
||||
SchedulePreview;
|
||||
if FSettings.PreviewKey then
|
||||
SetStatus('Предпросмотр показывает ключ и прозрачность.')
|
||||
else
|
||||
SetStatus('Предпросмотр показывает оверлей как есть.');
|
||||
end;
|
||||
|
||||
procedure TMainForm.MenuToggleRipple(Sender: TObject);
|
||||
begin
|
||||
ToggleRipple;
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ type
|
|||
FVideoPanelHeight: Integer;
|
||||
FLogVisible: Boolean;
|
||||
FPlayheadSnap: Boolean;
|
||||
FPreviewKey: Boolean;
|
||||
FDepsReminder: Boolean;
|
||||
FRecentProjects: TStringList;
|
||||
function FileName: string;
|
||||
|
|
@ -70,6 +71,8 @@ type
|
|||
property LogVisible: Boolean read FLogVisible write FLogVisible;
|
||||
{ Прилипание курсора воспроизведения к границам клипов. }
|
||||
property PlayheadSnap: Boolean read FPlayheadSnap write FPlayheadSnap;
|
||||
{ Применять ли ключ и прозрачность прямо в предпросмотре. }
|
||||
property PreviewKey: Boolean read FPreviewKey write FPreviewKey;
|
||||
{ Напоминать ли о недостающих зависимостях при запуске. Гасится
|
||||
кнопкой «Не напоминать» в самом напоминании. }
|
||||
property DepsReminder: Boolean read FDepsReminder write FDepsReminder;
|
||||
|
|
@ -103,6 +106,7 @@ begin
|
|||
FVideoPanelHeight := DefaultVideoHeight;
|
||||
FLogVisible := True;
|
||||
FPlayheadSnap := True;
|
||||
FPreviewKey := True;
|
||||
FDepsReminder := True;
|
||||
end;
|
||||
|
||||
|
|
@ -184,6 +188,7 @@ begin
|
|||
FVideoPanelHeight := Obj.Get('video_panel_height', FVideoPanelHeight);
|
||||
FLogVisible := Obj.Get('log_visible', FLogVisible);
|
||||
FPlayheadSnap := Obj.Get('playhead_snap', FPlayheadSnap);
|
||||
FPreviewKey := Obj.Get('preview_key', FPreviewKey);
|
||||
FDepsReminder := Obj.Get('deps_reminder', FDepsReminder);
|
||||
|
||||
Arr := Obj.Find('recent_projects', jtArray) as TJSONArray;
|
||||
|
|
@ -236,6 +241,7 @@ begin
|
|||
Root.Add('video_panel_height', FVideoPanelHeight);
|
||||
Root.Add('log_visible', FLogVisible);
|
||||
Root.Add('playhead_snap', FPlayheadSnap);
|
||||
Root.Add('preview_key', FPreviewKey);
|
||||
Root.Add('deps_reminder', FDepsReminder);
|
||||
|
||||
Arr := TJSONArray.Create;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ unit uTransform;
|
|||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils;
|
||||
Classes, SysUtils, uAppTypes;
|
||||
|
||||
type
|
||||
{ Как соотносятся кадр исходника и кадр проекта. }
|
||||
|
|
@ -82,6 +82,16 @@ function OverlayFilter(const AT: TMediaTransform;
|
|||
ASrcWidth, ASrcHeight, ADstWidth, ADstHeight: Integer;
|
||||
out AX, AY, AWidth, AHeight: Integer): string;
|
||||
|
||||
{ Фильтр цветового ключа: этот цвет становится прозрачным.
|
||||
|
||||
Один на всех: и сборка, и предпросмотр должны выбивать одинаково, иначе
|
||||
окно обещает одно, а готовый ролик даёт другое. Цвет записывается как
|
||||
0xRRGGBB — порядок обратный тому, в каком он хранится у нас. }
|
||||
function ColorKeyFilter(AColor: TRgb; ASimilarity, ABlend: Integer): string;
|
||||
|
||||
{ Фильтр приглушения по альфа-каналу. Пусто, если приглушать нечего. }
|
||||
function OpacityFilter(const AOpacity: Double): string;
|
||||
|
||||
function PreviewFitFilter(ADstWidth, ADstHeight: Integer): string;
|
||||
|
||||
function FitModeToStr(AMode: TFitMode): string;
|
||||
|
|
@ -91,7 +101,7 @@ function FitModeCaption(AMode: TFitMode): string;
|
|||
implementation
|
||||
|
||||
uses
|
||||
Math, uAppTypes;
|
||||
Math;
|
||||
|
||||
function DefaultTransform: TMediaTransform;
|
||||
begin
|
||||
|
|
@ -143,6 +153,28 @@ begin
|
|||
(ASrcWidth = ADstWidth) and (ASrcHeight = ADstHeight);
|
||||
end;
|
||||
|
||||
{ Дробное число через точку: у ffmpeg разделитель всегда точка, а в
|
||||
русской локали Format вставил бы запятую и развалил бы всю цепочку. }
|
||||
function FilterNum(const AValue: Double): string;
|
||||
begin
|
||||
Result := SecondsToStr(AValue);
|
||||
end;
|
||||
|
||||
function ColorKeyFilter(AColor: TRgb; ASimilarity, ABlend: Integer): string;
|
||||
begin
|
||||
Result := Format('colorkey=0x%.2x%.2x%.2x:%s:%s',
|
||||
[AColor and $FF, (AColor shr 8) and $FF, (AColor shr 16) and $FF,
|
||||
FilterNum(ASimilarity / 100), FilterNum(ABlend / 100)]);
|
||||
end;
|
||||
|
||||
function OpacityFilter(const AOpacity: Double): string;
|
||||
begin
|
||||
if AOpacity >= 0.999 then
|
||||
Result := ''
|
||||
else
|
||||
Result := 'colorchannelmixer=aa=' + FilterNum(Max(0, AOpacity));
|
||||
end;
|
||||
|
||||
function PreviewFitFilter(ADstWidth, ADstHeight: Integer): string;
|
||||
var
|
||||
W, H: string;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue