From 766f1ad1967753dbf4e7cd0465a4a1f4197e13e3 Mon Sep 17 00:00:00 2001 From: 4aiman Date: Fri, 16 Oct 2026 16:36:11 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=B0=D0=BA=D0=BB=D0=B0=D0=B4=D1=8B?= =?UTF-8?q?=D0=B2=D0=B0=D0=B5=D0=BC=D0=B0=D1=8F=20=D0=BA=D0=B0=D1=80=D1=82?= =?UTF-8?q?=D0=B8=D0=BD=D0=BA=D0=B0=20=D0=BF=D0=BE=D0=BA=D0=B0=D0=B7=D1=8B?= =?UTF-8?q?=D0=B2=D0=B0=D0=B5=D1=82=D1=81=D1=8F=20=D0=B2=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=D0=BF=D1=80=D0=BE=D1=81=D0=BC=D0=BE=D1=82=D1=80?= =?UTF-8?q?=D0=B5=20=D0=B3=D0=BE=D1=82=D0=BE=D0=B2=D1=8B=D0=BC=20=D0=BA?= =?UTF-8?q?=D1=83=D1=81=D0=BA=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В окне картинки не было вовсе, хотя в ролик она попадала. Плеер получал сам файл, а его декодер на этом webp спотыкался: «webp: invalid TIFF header in Exif data» — накладывать стало нечего. У неподвижного кадра есть и вторая беда: в списке кусков у него нет длины. Теперь наложению отдаётся заготовка — обычное видео нужной длины, сделанное тем же путём, что и остальные вставки, только цепочкой без полей: поля тут вредны, чёрное закрыло бы нижнее видео. Масштаб уже внутри куска, плееру остаётся поставить его на место. Заготовки наложения собираются отдельным списком: уплощение ниже заполняет список недостающего заново и затёрло бы их. Проверено на том самом файле: в окне картинка легла поверх видео, в ролике слева нижнее видео (253, 0, 0), справа картинка. Числовые проверки в flattest: кусок собирается слоями, у верхнего слоя полей нет, предпросмотру отдан кусок из кэша, и он заказан к созданию. --- README.md | 7 +++++ src/uFlatten.pas | 74 +++++++++++++++++++++++++++++++++++++++++++--- tests/nlvetool.lpr | 29 ++++++++++++++++++ 3 files changed, 106 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5029e4f..03471bb 100644 --- a/README.md +++ b/README.md @@ -730,6 +730,13 @@ Linux потом искала по нему `.dll`. что-то лежит, участок собирается наложением, и чёрным полям взяться неоткуда. Заготовка с полями остаётся для случая, когда класть не на что. + + В предпросмотре накладываемая картинка тоже показывается заготовкой, + только без полей. Своих декодеров плееру хватает не на всё: настоящий + webp из сети дал «webp: invalid TIFF header in Exif data», и картинка + пропадала из окна, хотя в ролик попадала. У неподвижного кадра есть и + вторая беда — в списке кусков у него нет длины. Заготовка же обычное + видео нужной длины. 3. Границы кусков кладутся на кадровую сетку проекта: между кадрами времени нет, а звук набирается с точностью до отсчёта, и доли кадра разводят их в стороны. diff --git a/src/uFlatten.pas b/src/uFlatten.pas index 264232a..254a527 100644 --- a/src/uFlatten.pas +++ b/src/uFlatten.pas @@ -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. diff --git a/tests/nlvetool.lpr b/tests/nlvetool.lpr index 2d0487b..a10b681 100644 --- a/tests/nlvetool.lpr +++ b/tests/nlvetool.lpr @@ -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;