nlve/src/uEncodeForm.pas
4aiman 80e6585134 Затухания видны и настраиваются, сборка с перекодированием, свой журнал в окне сборки
Затухания. Механика была написана давно и работала в сборке, но увидеть
или задать её было нечем. Теперь у клипа два верхних угла — ручки:
левая тянет появление, правая уход. Взятое затухание рисуется срезанным
углом, так это устроено в монтажных программах. Ручки перехватывают
нажатие раньше растягивания за край: они мелкие, промахнуться мимо них
легче, а взяться за край можно ниже.

Перекрытие двух клипов одной дорожки — это переход, и он теперь помечен
косым крестом во всю высоту. Раньше о нём можно было узнать только по
предупреждению перед сборкой.

Сборка с перекодированием. Отдельный пункт меню и отдельное окно:
размер кадра от 720p до 4K, поток в килобитах, кодек HEVC или H.264.
Перекодирование идёт одним проходом на весь ролик — пересобирать куски
по отдельности нельзя, на стыках была бы видна смена качества.

Кодирование на видеокарте, и распаковка тоже: замерено на паре кусков
2688x1512 — 9.18 с на процессоре против 1.77 с с аппаратной распаковкой,
впятеро. Полностью карточный путь через scale_cuda оказался медленнее
(5.74 с) и не взят. Если аппаратная распаковка не пойдёт, сборка
повторяется без неё: медленно лучше, чем никак. Окно честно
предупреждает, когда аппаратного кодировщика в ffmpeg нет вовсе.

Журнал в окне сборки заменён на свой: у системного поля оставались
светлые полосы прокрутки и не работала прокрутка вбок с Shift. Это было
последнее такое место.
2026-08-21 18:11:59 +03:00

224 lines
8 KiB
ObjectPascal
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
uEncodeForm — окно настроек сборки с перекодированием.
Зачем оно нужно. Обычная сборка копирует потоки и потому мгновенна, но
отдаёт ролик ровно в том виде, в каком снят исходник. Когда ролик надо
отдать под площадку — в 1080p с определённым потоком, — приходится
перекодировать, и это уже осознанный выбор, а не поведение по
умолчанию. Поэтому отдельный пункт меню и отдельное окно.
Окно собирается кодом, без файла формы: элементов в нём меньше десятка,
и описание заняло бы больше места, чем сам код. Так же сделано окно
сообщения в uTheme.
Перекодирование идёт на видеокарте. Процессорный кодировщик на
2688x1512 работает медленнее реального времени, и часовой ролик считал
бы часами; выбор кодировщика делает uFfmpegCmd, предпочитая NVENC.
Если аппаратного кодировщика нет, окно об этом честно предупреждает.
}
unit uEncodeForm;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Controls, Forms, Graphics, StdCtrls, Dialogs,
uTheme, uExport;
{ Спрашивает настройки. Возвращает False, если человек передумал.
ADefaultCodec — кодек проекта: с него разумно начинать, иначе первый же
выбор менял бы формат без спроса. }
function AskEncodeProfile(out AProfile: TEncodeProfile;
const ADefaultCodec: string): Boolean;
implementation
uses
uFfmpegCmd;
type
TPreset = record
Caption: string;
Width: Integer;
Height: Integer;
{ Поток в килобитах в секунду. Значения взяты как обычная рабочая
середина для съёмки с движением: меньше — заметны квадраты на
траве и воде, больше — файл растёт без видимой пользы. }
Bitrate: Integer;
end;
const
Presets: array[0..3] of TPreset = (
(Caption: '1280x720 (720p)'; Width: 1280; Height: 720; Bitrate: 8000),
(Caption: '1920x1080 (1080p)'; Width: 1920; Height: 1080; Bitrate: 16000),
(Caption: '2560x1440 (1440p)'; Width: 2560; Height: 1440; Bitrate: 28000),
(Caption: '3840x2160 (4K)'; Width: 3840; Height: 2160; Bitrate: 50000)
);
type
{ Обработчики живут в объекте: событиям нужны методы. }
TEncodeDialog = class(TComponent)
public
Form: TForm;
cbPreset: TComboBox;
cbCodec: TComboBox;
edBitrate: TEdit;
lblEncoder: TLabel;
procedure PresetChange(Sender: TObject);
procedure CodecChange(Sender: TObject);
procedure UpdateEncoderNote;
end;
procedure TEncodeDialog.PresetChange(Sender: TObject);
begin
if (cbPreset.ItemIndex >= 0) and (cbPreset.ItemIndex <= High(Presets)) then
edBitrate.Text := IntToStr(Presets[cbPreset.ItemIndex].Bitrate);
end;
procedure TEncodeDialog.CodecChange(Sender: TObject);
begin
UpdateEncoderNote;
end;
procedure TEncodeDialog.UpdateEncoderNote;
var
Codec, Enc: string;
begin
if cbCodec.ItemIndex = 0 then
Codec := 'hevc'
else
Codec := 'h264';
Enc := PreferredEncoder(Codec);
if Pos('nvenc', Enc) > 0 then
begin
lblEncoder.Font.Color := ThemeTextDim;
lblEncoder.Caption := 'Кодировщик: ' + Enc + ' — на видеокарте.';
end
else
begin
{ Предупреждение, а не запрет: пусть человек сам решит, готов ли
ждать. Молча уйти на процессор нельзя — разница в разы. }
lblEncoder.Font.Color := ThemeAccent;
lblEncoder.Caption := 'Кодировщик: ' + Enc +
' — аппаратного не нашлось, сборка будет очень долгой.';
end;
end;
function AskEncodeProfile(out AProfile: TEncodeProfile;
const ADefaultCodec: string): Boolean;
var
Dlg: TEncodeDialog;
Lbl: TLabel;
BtnOk, BtnCancel: TThemedButton;
I, Bitrate: Integer;
begin
Result := False;
AProfile := Default(TEncodeProfile);
Dlg := TEncodeDialog.Create(nil);
try
Dlg.Form := TForm.CreateNew(nil);
Dlg.Form.Caption := 'Сборка с перекодированием';
Dlg.Form.BorderStyle := bsDialog;
Dlg.Form.Position := poScreenCenter;
Dlg.Form.ClientWidth := 460;
Dlg.Form.ClientHeight := 220;
Lbl := TLabel.Create(Dlg.Form);
Lbl.Parent := Dlg.Form;
Lbl.SetBounds(16, 16, 120, 20);
Lbl.Caption := 'Размер кадра';
Dlg.cbPreset := TComboBox.Create(Dlg.Form);
Dlg.cbPreset.Parent := Dlg.Form;
Dlg.cbPreset.SetBounds(160, 12, 280, 26);
Dlg.cbPreset.Style := csDropDownList;
for I := 0 to High(Presets) do
Dlg.cbPreset.Items.Add(Presets[I].Caption);
Dlg.cbPreset.ItemIndex := 1;
Dlg.cbPreset.OnChange := @Dlg.PresetChange;
Lbl := TLabel.Create(Dlg.Form);
Lbl.Parent := Dlg.Form;
Lbl.SetBounds(16, 56, 140, 20);
Lbl.Caption := 'Поток, кбит/с';
Dlg.edBitrate := TEdit.Create(Dlg.Form);
Dlg.edBitrate.Parent := Dlg.Form;
Dlg.edBitrate.SetBounds(160, 52, 120, 26);
Dlg.edBitrate.Text := IntToStr(Presets[1].Bitrate);
Lbl := TLabel.Create(Dlg.Form);
Lbl.Parent := Dlg.Form;
Lbl.SetBounds(16, 96, 140, 20);
Lbl.Caption := 'Кодек';
Dlg.cbCodec := TComboBox.Create(Dlg.Form);
Dlg.cbCodec.Parent := Dlg.Form;
Dlg.cbCodec.SetBounds(160, 92, 280, 26);
Dlg.cbCodec.Style := csDropDownList;
Dlg.cbCodec.Items.Add('HEVC (H.265) — меньше файл');
Dlg.cbCodec.Items.Add('H.264 — совместимее');
if LowerCase(ADefaultCodec) = 'h264' then
Dlg.cbCodec.ItemIndex := 1
else
Dlg.cbCodec.ItemIndex := 0;
Dlg.cbCodec.OnChange := @Dlg.CodecChange;
Dlg.lblEncoder := TLabel.Create(Dlg.Form);
Dlg.lblEncoder.Parent := Dlg.Form;
Dlg.lblEncoder.SetBounds(16, 132, 424, 36);
Dlg.lblEncoder.WordWrap := True;
Dlg.lblEncoder.AutoSize := False;
Dlg.lblEncoder.ParentColor := False;
BtnOk := TThemedButton.Create(Dlg.Form);
BtnOk.Parent := Dlg.Form;
BtnOk.SetBounds(240, 180, 100, 28);
BtnOk.Caption := 'Собрать';
BtnOk.ModalResult := mrOk;
BtnOk.Default := True;
BtnCancel := TThemedButton.Create(Dlg.Form);
BtnCancel.Parent := Dlg.Form;
BtnCancel.SetBounds(348, 180, 100, 28);
BtnCancel.Caption := 'Отмена';
BtnCancel.ModalResult := mrCancel;
BtnCancel.Cancel := True;
{ Цвета задаются до первого показа, как и во всех остальных окнах. }
ApplyDarkTheme(Dlg.Form);
Dlg.UpdateEncoderNote;
if Dlg.Form.ShowModal <> mrOk then
Exit;
I := Dlg.cbPreset.ItemIndex;
if (I < 0) or (I > High(Presets)) then
I := 1;
Bitrate := StrToIntDef(Trim(Dlg.edBitrate.Text), Presets[I].Bitrate);
{ Ноль и отрицательное — не поток, а описка. Верхняя граница выбрана
с запасом: выше него кодировщики уже отказываются работать. }
if (Bitrate < 100) or (Bitrate > 400000) then
Bitrate := Presets[I].Bitrate;
AProfile.Enabled := True;
AProfile.Width := Presets[I].Width;
AProfile.Height := Presets[I].Height;
AProfile.BitrateKbps := Bitrate;
if Dlg.cbCodec.ItemIndex = 1 then
AProfile.Codec := 'h264'
else
AProfile.Codec := 'hevc';
Result := True;
finally
Dlg.Form.Free;
Dlg.Free;
end;
end;
end.