Кадрирование показывает кадр из клипа, а не из середины файла

Окно кадрирования брало кадр на десятой части от начала исходного файла,
не глядя, какой клип правят. Клип обычно вырезан из середины долгой
съёмки, и в окне была совсем другая сцена: прицелиться увеличением было
нечем.

Теперь для клипа берётся его собственное время: место курсора, если он
внутри клипа, иначе середина клипа. Кадрирование файла целиком из списка
работает как раньше.

Проверено на съёмке с дрона: клип из 900-й секунды, окно показало кадр
именно оттуда (совпал с эталонным кадром на 900 с), раньше показывало
кадр на пятой секунде.
This commit is contained in:
4aiman 2026-09-30 12:05:44 +03:00
commit 569d0382f7
2 changed files with 46 additions and 18 deletions

View file

@ -3012,6 +3012,7 @@ var
T: TMediaTransform;
Key: TColorKey;
Applied: Boolean;
At: Double;
begin
if AClip = nil then
Exit;
@ -3041,12 +3042,23 @@ begin
end;
T := AClip.Transform;
{ Кадр окну — из самого клипа, а не из середины исходного файла.
Клип обычно вырезан из середины долгой съёмки, и кадр «десятая часть
от начала файла» показывал совсем другую сцену: прицелиться
кадрированием было нечем. Берём то место, где стоит курсор, если он
внутри клипа, иначе середину клипа — то, на что человек и смотрит. }
At := FTimeline.Playhead;
if (At < AClip.TlStart) or (At > AClip.TlEnd) then
At := AClip.TlStart + AClip.Duration / 2;
At := AClip.SrcIn + (At - AClip.TlStart);
{ Ключ отдаётся окну по ссылке: правку он подтверждает сам, и «Отмена»
кадрирования её не отменяет. }
Key := AClip.ColorKey;
Applied := EditTransform(Self, Item.EffectivePath, Item.Info,
FProject.Master.Width, FProject.Master.Height, T,
FProject.CacheDir + 'pancrop.png', @Key);
FProject.CacheDir + 'pancrop.png', @Key, At);
if (not Applied) and SameColorKey(Key, AClip.ColorKey) then
Exit;

View file

@ -71,6 +71,8 @@ type
у заготовки для файла, у которой клипа ещё нет), и кнопки не
будет. }
FKey: PColorKey;
{ Какое место исходника показывать в окне. }
FAtTime: Double;
FSourceInfo: TMediaInfo;
FDstWidth: Integer;
FDstHeight: Integer;
@ -106,11 +108,18 @@ type
procedure WriteControls;
procedure ScheduleRefresh;
procedure RefreshPreview(Sender: TObject);
{ Время кадра в исходнике: заданное снаружи или своё. }
function FrameTime: Double;
public
{ Готовит окно. ADstWidth и ADstHeight — кадр проекта. }
{ AAtTime — какое место исходника показывать. Меньше нуля — выбрать
самим: десятая часть от начала файла. Для клипа передаётся его
собственное время, иначе прицелиться нечем: клип вырезан из середины
съёмки, а в окне показывалась совсем другая сцена. }
procedure Setup(const APath: string; const AInfo: TMediaInfo;
ADstWidth, ADstHeight: Integer; const ATransform: TMediaTransform;
const APreviewFile: string; AKey: PColorKey = nil);
const APreviewFile: string; AKey: PColorKey = nil;
AAtTime: Double = -1);
property Transform: TMediaTransform read FTransform;
end;
@ -124,7 +133,7 @@ type
function EditTransform(AOwner: TComponent; const APath: string;
const AInfo: TMediaInfo; ADstWidth, ADstHeight: Integer;
var ATransform: TMediaTransform; const APreviewFile: string;
AKey: PColorKey = nil): Boolean;
AKey: PColorKey = nil; AAtTime: Double = -1): Boolean;
implementation
@ -201,6 +210,21 @@ begin
end;
{ Какое место исходника показывать.
Задано снаружи — берём его: у клипа это его собственное время, и только
по нему можно прицелиться кадрированием. Не задано (кадрирование файла
целиком из списка) — десятая часть от начала, но не дальше пятой
секунды: первые кадры съёмки часто пустые. }
function TPanCropForm.FrameTime: Double;
begin
if FSourceInfo.Kind = mkImage then
Exit(0);
if FAtTime >= 0 then
Exit(Max(0, Min(FAtTime, Max(0, FSourceInfo.Duration - 0.05))));
Result := Min(5, Max(0, FSourceInfo.Duration * 0.1));
end;
procedure TPanCropForm.FormDestroy(Sender: TObject);
begin
FreeAndNil(FSourceBmp);
@ -211,8 +235,9 @@ end;
procedure TPanCropForm.Setup(const APath: string; const AInfo: TMediaInfo;
ADstWidth, ADstHeight: Integer; const ATransform: TMediaTransform;
const APreviewFile: string; AKey: PColorKey);
const APreviewFile: string; AKey: PColorKey; AAtTime: Double);
begin
FAtTime := AAtTime;
FKey := AKey;
btnKey.Visible := AKey <> nil;
FSourcePath := APath;
@ -321,10 +346,7 @@ var
begin
if FKey = nil then
Exit;
if FSourceInfo.Kind = mkImage then
AtTime := 0
else
AtTime := Min(5, Max(0, FSourceInfo.Duration * 0.1));
AtTime := FrameTime;
EditColorKey(FKey^, FSourcePath, AtTime, FPreviewFile + '.key.png');
end;
@ -515,10 +537,7 @@ begin
Exit;
Path := FPreviewFile + '.src.png';
if FSourceInfo.Kind = mkImage then
AtTime := 0
else
AtTime := Min(5, Max(0, FSourceInfo.Duration * 0.1));
AtTime := FrameTime;
{ Пустая цепочка фильтров: нужен именно исходный кадр, без вписывания и
полей — иначе рамку не по чему тянуть. }
@ -677,10 +696,7 @@ begin
PrevW, PrevH);
lblFilter.Caption := Filter;
if FSourceInfo.Kind = mkImage then
AtTime := 0
else
AtTime := Min(5, Max(0, FSourceInfo.Duration * 0.1));
AtTime := FrameTime;
Res := RunProcess(FfmpegExe,
ArgsPreviewFrame(FSourcePath, FPreviewFile, AtTime, Filter),
@ -720,14 +736,14 @@ end;
function EditTransform(AOwner: TComponent; const APath: string;
const AInfo: TMediaInfo; ADstWidth, ADstHeight: Integer;
var ATransform: TMediaTransform; const APreviewFile: string;
AKey: PColorKey): Boolean;
AKey: PColorKey; AAtTime: Double = -1): Boolean;
var
Form: TPanCropForm;
begin
Form := TPanCropForm.Create(AOwner);
try
Form.Setup(APath, AInfo, ADstWidth, ADstHeight, ATransform, APreviewFile,
AKey);
AKey, AAtTime);
Result := Form.ShowModal = mrOK;
if Result then
ATransform := Form.Transform;