Рез больше не теряет настройки клипа

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

Перечисление заменено на CopySettingsFrom: одно место, где сказано, что
переносится при копировании клипа, а что делает клип этим клипом. Иначе
та же ловушка досталась бы следующему свойству.

Проверка в lfmcheck: рез клипа с ключом, прозрачностью, громкостью и
кадрированием — всё остаётся у обеих половин, связки у них разные и
непустые.

Проекты, разрезанные прежней сборкой, правка задним числом не чинит:
у хвоста ключ надо включить заново.
This commit is contained in:
4aiman 2026-08-31 01:08:02 +03:00
commit 34767fd711
4 changed files with 154 additions and 9 deletions

View file

@ -91,3 +91,27 @@ mpv 0.37, видео 2688x1512 HEVC 87 Мбит/с, 600 кадров (10 с ма
из-за которой монтаж не помещался в окно). Файл не пострадал — проверено
по содержимому, — но теперь тестовые скрипты задают `XDG_CONFIG_HOME`,
`XDG_CACHE_HOME` и `XDG_DATA_HOME` своими.
## Рез терял цветовой ключ
Показано пользователем на снимках: до реза оверлей лежал компасом в углу,
после реза (S) в окне остался только он сам, во весь кадр и с пурпуром.
Причина: разрез создаёт хвост и переносит на него настройки по одной —
громкость, прозрачность, кадрирование, затухания. Цветового ключа в этом
списке не было, потому что он появился позже. Хвост без ключа перестаёт
быть наложением, становится обычным победителем и закрывает собой всё,
что ниже.
Чинить перечислением ещё одного поля значило бы оставить ту же ловушку
следующему свойству. Поэтому у клипа появился `CopySettingsFrom`: одно
место, где сказано, что переносится при копировании, а что делает клип
этим клипом (имя, место, кусок исходника, связка, выделение).
Проверка добавлена в `lfmcheck`: рез клипа с ключом, прозрачностью,
громкостью и кадрированием — всё это должно оказаться у обеих половин, а
связки у них должны быть разные и непустые.
Важное для уже сделанных монтажей: правка не чинит проекты задним
числом. Если оверлей был разрезан старой сборкой, у хвоста ключ надо
включить заново.

View file

@ -143,6 +143,19 @@ type
FColorKey: TColorKey;
public
constructor Create;
{ Переносит на этот клип всё, что настроено у другого: громкость,
кадрирование, прозрачность, ключ, затухания, текст.
Не переносится то, что делает клип этим клипом: имя, место на
монтаже, кусок исходника, связка и выделение.
Зачем метод, а не переписывание полей на месте: копия клипа нужна в
нескольких местах (рез — самое частое), и каждое новое свойство
иначе приходится вспоминать в каждом из них. Один раз не вспомнили —
и после реза половина клипа теряла цветовой ключ, а вместе с ним и
прозрачность: она переставала быть наложением и закрывала собой всё,
что ниже. }
procedure CopySettingsFrom(ASource: TClip);
function TlEnd: Double;
function SrcOut: Double;
{ Попадает ли момент внутрь клипа. Правый край не включается: там уже
@ -402,6 +415,21 @@ end;
{ --- TClip --- }
procedure TClip.CopySettingsFrom(ASource: TClip);
begin
if ASource = nil then
Exit;
FKind := ASource.Kind;
FVolume := ASource.Volume;
FTransform := ASource.Transform;
FOpacity := ASource.Opacity;
FColorKey := ASource.ColorKey;
FFadeIn := ASource.FadeIn;
FFadeOut := ASource.FadeOut;
FText := ASource.Text;
FTextStyle := ASource.TextStyle;
end;
constructor TClip.Create;
begin
inherited Create;

View file

@ -2232,19 +2232,17 @@ begin
Tail := TClip.Create;
Tail.Id := Clip.Id + '_b';
Tail.MediaId := Clip.MediaId;
Tail.Kind := Clip.Kind;
Tail.SrcIn := Clip.SrcIn + Offset;
Tail.TlStart := FPlayhead;
Tail.Duration := Clip.Duration - Offset;
Tail.EdgeOut := Clip.EdgeOut;
{ Хвост — тот же клип, только вторая его половина. Всё, что было
настроено, должно перейти к нему: иначе после реза половина клипа
теряет громкость и кадрирование. }
Tail.Volume := Clip.Volume;
Tail.Opacity := Clip.Opacity;
{ Хвост — тот же клип, только вторая его половина. Всё настроенное
переходит к нему одним махом: перечислять поля по одному значит
однажды забыть новое. }
Tail.CopySettingsFrom(Clip);
{ Кроме появления: оно относится к началу клипа, а начало осталось у
первой половины. }
Tail.FadeIn := 0;
Tail.FadeOut := Clip.FadeOut;
Tail.Transform := Clip.Transform;
{ Связка хвоста — новая.

View file

@ -15,7 +15,7 @@ uses
{ Interfaces нужен, чтобы связалась вся LCL и заработала загрузка
файлов форм. }
Interfaces, Forms, Classes, SysUtils, Math, TypInfo, uTheme,
uMediaProbe, uProject, uTimelineCtrl,
uMediaProbe, uProject, uTransform, uTimelineCtrl,
uPanCropForm, uDepsForm, uMismatchForm, uProgressForm, uProjectInfoForm,
uRemuxForm;
@ -153,6 +153,100 @@ begin
end;
end;
{ Рез не должен терять настройки клипа.
Половина клипа после реза — тот же клип, и цветовой ключ, кадрирование,
прозрачность, громкость и текст должны остаться при обеих половинах.
Однажды не остались: у хвоста пропадал ключ, клип переставал быть
наложением и закрывал собой всё, что под ним. }
procedure CheckSplitKeepsSettings;
var
Proj: TProject;
Info: TMediaInfo;
Media: TMediaItem;
Timeline: TTimelineControl;
Head, Tail, Cl: TClip;
Key: TColorKey;
Tr: TMediaTransform;
I: Integer;
begin
WriteLn;
WriteLn('Рез сохраняет настройки клипа');
WriteLn;
Info := Default(TMediaInfo);
Info.Valid := True;
Info.Kind := mkVideo;
Info.Duration := 600;
Info.HasVideo := True;
Info.VideoCodec := 'hevc';
Info.Width := 1920;
Info.Height := 1080;
Info.FpsNum := 30;
Info.FpsDen := 1;
Info.PixFmt := 'yuv420p';
Proj := TProject.Create;
Timeline := TTimelineControl.Create(nil);
try
Proj.Clear;
Proj.AddTrack('V1');
Media := Proj.AddMedia('/несуществующий/видео.mp4', Info);
Cl := Proj.AddClip(Proj.Tracks[0], Media, 0, 10, 0);
Key := DefaultColorKey;
Key.Enabled := True;
Key.Similarity := 22;
Cl.ColorKey := Key;
Cl.Opacity := 0.5;
Cl.Volume := 0.3;
Tr := Cl.Transform;
Tr.Zoom := 0.4;
Tr.OffsetX := 0.7;
Cl.Transform := Tr;
Cl.GroupId := 'g1';
Timeline.Project := Proj;
Timeline.PixelsPerSecond := 100;
Timeline.Playhead := 4;
Timeline.SplitAtPlayhead(False, False);
CheckTrue('клипов стало два', Proj.Tracks[0].Count = 2);
if Proj.Tracks[0].Count <> 2 then
Exit;
Head := nil;
Tail := nil;
for I := 0 to Proj.Tracks[0].Count - 1 do
if Proj.Tracks[0][I].TlStart < 0.001 then
Head := Proj.Tracks[0][I]
else
Tail := Proj.Tracks[0][I];
CheckTrue('обе половины нашлись', (Head <> nil) and (Tail <> nil));
if (Head = nil) or (Tail = nil) then
Exit;
CheckTrue('ключ остался у хвоста', Tail.ColorKey.Enabled);
CheckTrue('и с тем же цветом', Tail.ColorKey.Color = Key.Color);
CheckTrue('и с тем же допуском', Tail.ColorKey.Similarity = 22);
CheckTrue('прозрачность осталась', Abs(Tail.Opacity - 0.5) < 0.001);
CheckTrue('громкость осталась', Abs(Tail.Volume - 0.3) < 0.001);
CheckTrue('кадрирование осталось',
(Abs(Tail.Transform.Zoom - 0.4) < 0.001) and
(Abs(Tail.Transform.OffsetX - 0.7) < 0.001));
CheckTrue('ключ остался и у головы', Head.ColorKey.Enabled);
{ Связки разные, и обе не пустые: иначе удаление одной половины
уносило бы вторую. }
CheckTrue('связка у половин разная и не пустая',
(Head.GroupId <> Tail.GroupId) and (Head.GroupId <> '') and
(Tail.GroupId <> ''));
CheckTrue('появление у хвоста снято', Abs(Tail.FadeIn) < 0.001);
finally
Timeline.Free;
Proj.Free;
end;
end;
begin
WriteLn('Свойства из файлов форм');
WriteLn;
@ -180,6 +274,7 @@ begin
CheckForm(TRemuxForm);
CheckPlayheadSnap;
CheckSplitKeepsSettings;
WriteLn;
if Failed = 0 then