Накладываемая картинка показывается в предпросмотре готовым куском

В окне картинки не было вовсе, хотя в ролик она попадала. Плеер получал
сам файл, а его декодер на этом webp спотыкался: «webp: invalid TIFF
header in Exif data» — накладывать стало нечего. У неподвижного кадра
есть и вторая беда: в списке кусков у него нет длины.

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

Заготовки наложения собираются отдельным списком: уплощение ниже
заполняет список недостающего заново и затёрло бы их.

Проверено на том самом файле: в окне картинка легла поверх видео, в
ролике слева нижнее видео (253, 0, 0), справа картинка. Числовые
проверки в flattest: кусок собирается слоями, у верхнего слоя полей нет,
предпросмотру отдан кусок из кэша, и он заказан к созданию.
This commit is contained in:
4aiman 2026-10-16 16:36:11 +03:00
commit 766f1ad196
3 changed files with 106 additions and 4 deletions

View file

@ -730,6 +730,13 @@ Linux потом искала по нему `.dll`.
что-то лежит, участок собирается наложением, и чёрным полям взяться
неоткуда. Заготовка с полями остаётся для случая, когда класть не на
что.
В предпросмотре накладываемая картинка тоже показывается заготовкой,
только без полей. Своих декодеров плееру хватает не на всё: настоящий
webp из сети дал «webp: invalid TIFF header in Exif data», и картинка
пропадала из окна, хотя в ролик попадала. У неподвижного кадра есть и
вторая беда — в списке кусков у него нет длины. Заготовка же обычное
видео нужной длины.
3. Границы кусков кладутся на кадровую сетку проекта: между кадрами
времени нет, а звук набирается с точностью до отсчёта, и доли кадра
разводят их в стороны.

View file

@ -228,6 +228,18 @@ uses
поверх нижнего, а само правило описано ниже, рядом с предпросмотром. }
function IsOverlayClip(AProject: TProject; AClip: TClip): Boolean; forward;
{ Сцепить две цепочки фильтров через запятую. Пустые не дают ни лишней
запятой, ни пустого звена: и то, и другое ffmpeg не прощает. }
function JoinFilters(const A, B: string): string;
begin
if A = '' then
Result := B
else if B = '' then
Result := A
else
Result := A + ',' + B;
end;
const
{ Длительность округляется до миллисекунд: иначе крошечное расхождение
в дробной части плодило бы почти одинаковые файлы. }
@ -1414,6 +1426,10 @@ var
RangeA, RangeB: TDoubleArray;
RangeCount: Integer;
Ov: TPreviewOverlay;
ImgReq: TGenRequest;
{ Заготовки картинок для наложения. Отдельным списком, потому что
уплощение ниже заполняет AMissing заново и затёрло бы их. }
MissOv: TGenRequests;
{ Занято ли это время другим оверлеем. }
function OverlapsTaken(const AFrom, ATo: Double): Boolean;
@ -1441,6 +1457,7 @@ begin
if Total <= 0 then
Exit;
MissOv := nil;
Hidden := TFPList.Create;
try
N := 0;
@ -1513,16 +1530,58 @@ begin
Item.Info.Width, Item.Info.Height,
AProject.Master.Width, AProject.Master.Height,
Ov.X, Ov.Y, Ov.Width, Ov.Height);
{ Картинку плееру отдаём не файлом, а готовым куском.
Своих декодеров у плеера хватает не на всё: настоящий webp из
сети вызвал «webp: invalid TIFF header in Exif data», и
накладывать стало нечего — картинка пропала из окна, хотя в
ролик попадала. У неподвижного кадра есть и вторая беда: в
списке кусков у него нет длины. Кусок же — обычное видео
нужной длины, которое играется как всё остальное.
Масштаб уже внутри куска, поэтому плееру остаётся только
поставить его на место; ключ и приглушение добавятся ниже. }
if (Clip.Kind = ckImage) or (Item.Info.Kind = mkImage) then
begin
ImgReq := Default(TGenRequest);
ImgReq.Kind := gkImage;
ImgReq.SourcePath := Item.EffectivePath;
ImgReq.Duration := BucketFor(Ov.TlEnd - Ov.TlStart);
{ Цепочка без полей: поля тут и вредны — чёрное поле закрыло
бы нижнее видео, ради которого всё и затевается. }
ImgReq.Filter := Ov.Filter;
ImgReq.TargetPath := GeneratedPath(AProject.CacheDir, ImgReq);
ImgReq.TlStart := Ov.TlStart;
ImgReq.TlEnd := Ov.TlEnd;
Ov.FilePath := ImgReq.TargetPath;
Ov.SrcStart := 0;
Ov.Filter := '';
if not FileExists(ImgReq.TargetPath) then
begin
{ Одна и та же картинка под одним и тем же кадрированием —
один кусок, сколько бы раз она ни лежала на монтаже. }
I := 0;
while (I < Length(MissOv)) and
(MissOv[I].TargetPath <> ImgReq.TargetPath) do
Inc(I);
if I >= Length(MissOv) then
begin
SetLength(MissOv, Length(MissOv) + 1);
MissOv[High(MissOv)] := ImgReq;
end;
end;
end;
{ Порядок тот же, что при сборке: сперва вписали в кадр, потом
выбили цвет, потом приглушили. Иначе смягчение края разошлось
бы с тем, что даст готовый ролик. }
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);
Ov.Filter := JoinFilters(Ov.Filter,
ColorKeyFilter(Clip.ColorKey.Color,
Clip.ColorKey.Similarity, Clip.ColorKey.Blend));
Ov.Filter := JoinFilters(Ov.Filter, OpacityFilter(Clip.Opacity));
end;
SetLength(AOverlays, N + 1);
@ -1559,6 +1618,13 @@ begin
SetLength(AMissing, Length(AMissing) + 1);
AMissing[High(AMissing)] := MissA[I];
end;
{ Заготовки накладываемых картинок добавляются здесь, а не на месте:
уплощение выше заполняет AMissing с нуля и затёрло бы их. }
for I := 0 to High(MissOv) do
begin
SetLength(AMissing, Length(AMissing) + 1);
AMissing[High(AMissing)] := MissOv[I];
end;
end;
end.

View file

@ -1122,6 +1122,35 @@ begin
Check(Missing[0].TargetPath <> PlainPath,
'кусок с кадрированием — отдельный файл');
end;
{ Под картинкой появляется видео — и она должна лечь поверх него, а
не закрыть его своими чёрными полями. }
Proj.AddTrack('V2');
Proj.AddClip(Proj.Tracks[1], Media, 0, 5, 0);
FlattenStream(Proj, tkVideo, 0, Segs, Missing);
Check((Length(Segs) = 1) and (Length(Segs[0].Layers) = 2),
'кусок собирается слоями: нижнее видео и картинка поверх');
if (Length(Segs) = 1) and (Length(Segs[0].Layers) = 2) then
Check(Pos('pad=', Segs[0].Layers[1].FitFilter) = 0,
'у верхнего слоя полей нет');
{ Предпросмотру картинка отдаётся готовым куском, а не самим файлом:
своих декодеров у плеера хватает не на всё, а у неподвижного кадра
нет длины. }
FlattenPreview(Proj, Segs, AudioSegs, Overlays, Missing, True);
Check(Length(Overlays) = 1, 'картинка ушла в накладываемые');
if Length(Overlays) = 1 then
begin
Check(Pos('.nlve-cache', Overlays[0].FilePath) > 0,
'показывается готовый кусок, а не сам файл');
Check(Pos('pad=', Overlays[0].Filter) = 0,
'полей в цепочке наложения нет');
PlainPath := '';
for I := 0 to High(Missing) do
if Missing[I].TargetPath = Overlays[0].FilePath then
PlainPath := Missing[I].TargetPath;
Check(PlainPath <> '', 'этот кусок заказан к созданию');
end;
finally
Proj.Free;
end;