Оверлей по порядку дорожек, текст в окне, имена дорожек
Миникарта была видна в окне, но не в ролике. Предпросмотр рисовал оверлей поверх всего, не глядя на порядок дорожек, а сборка живёт по правилу «верхний клип побеждает целиком»: фотография лежала выше оверлея и в ролике его закрывала. Теперь окно считает так же — оверлей показывается только там, где его не закрывает сплошной клип с дорожки выше, а закрытый посередине разбивается на несколько окон показа. Текст не появлялся в окне. Разметка лежит в файле с одним именем на весь проект, а плеер сравнивал имена: имя прежнее — перечитывать нечего. Правка доходила до окна только вместе с перезагрузкой списка кусков. Теперь сравнивается сама разметка, и при изменении файл перечитывается. Отдельно проверено, что цепочка lavfi-complex субтитрам не мешает. Две дорожки Т2. Имя новой дорожке давалось по счёту дорожек своего вида плюс единица, и после удаления средней счёт совпадал с занятым именем. Имя подбирается свободное, а повторы из старых проектов разводятся при чтении. Проверки 11 и 12 в nlvetool flattest.
This commit is contained in:
parent
59c778d2f6
commit
0bbff345ba
6 changed files with 252 additions and 50 deletions
134
src/uFlatten.pas
134
src/uFlatten.pas
|
|
@ -38,6 +38,9 @@ uses
|
|||
Classes, SysUtils, uAppTypes, uProject, uMediaProbe, uTransform;
|
||||
|
||||
type
|
||||
{ Набор отрезков времени: им описывают, где оверлей виден. }
|
||||
TDoubleArray = array of Double;
|
||||
|
||||
{ Клип, который в предпросмотре кладётся поверх основы.
|
||||
|
||||
Место в кадре — то же самое, по чему сборка обкладывает картинку
|
||||
|
|
@ -1086,6 +1089,52 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
{ Вырезает из набора отрезков указанный. Отрезок посередине делит тот, в
|
||||
который попал, надвое: оверлей, закрытый в середине, показывается двумя
|
||||
окнами. }
|
||||
procedure CutRange(var AFrom, ATo: TDoubleArray; var ACount: Integer;
|
||||
const ACutFrom, ACutTo: Double);
|
||||
var
|
||||
I, N: Integer;
|
||||
NewFrom, NewTo: array of Double;
|
||||
|
||||
procedure Push(const A, B: Double);
|
||||
begin
|
||||
if B <= A + 0.001 then
|
||||
Exit;
|
||||
NewFrom[N] := A;
|
||||
NewTo[N] := B;
|
||||
Inc(N);
|
||||
end;
|
||||
|
||||
begin
|
||||
if ACutTo <= ACutFrom then
|
||||
Exit;
|
||||
SetLength(NewFrom, ACount * 2 + 2);
|
||||
SetLength(NewTo, ACount * 2 + 2);
|
||||
N := 0;
|
||||
for I := 0 to ACount - 1 do
|
||||
begin
|
||||
{ Не пересекаются — отрезок остаётся как был. }
|
||||
if (ACutTo <= AFrom[I] + 0.001) or (ACutFrom >= ATo[I] - 0.001) then
|
||||
Push(AFrom[I], ATo[I])
|
||||
else
|
||||
begin
|
||||
Push(AFrom[I], ACutFrom);
|
||||
Push(ACutTo, ATo[I]);
|
||||
end;
|
||||
end;
|
||||
|
||||
ACount := N;
|
||||
SetLength(AFrom, N);
|
||||
SetLength(ATo, N);
|
||||
for I := 0 to N - 1 do
|
||||
begin
|
||||
AFrom[I] := NewFrom[I];
|
||||
ATo[I] := NewTo[I];
|
||||
end;
|
||||
end;
|
||||
|
||||
{ Клип кладётся поверх, а не побеждает целиком, если он не сплошной:
|
||||
с выбитым цветом или полупрозрачный. }
|
||||
function IsOverlayClip(AClip: TClip): Boolean;
|
||||
|
|
@ -1102,11 +1151,13 @@ function FlattenPreview(AProject: TProject;
|
|||
var
|
||||
Hidden: TFPList;
|
||||
MissA: TGenRequests;
|
||||
T, C, I, N: Integer;
|
||||
Track: TTrack;
|
||||
T, C, I, J, K, N: Integer;
|
||||
Track, Above: TTrack;
|
||||
Clip: TClip;
|
||||
Item: TMediaItem;
|
||||
Total: Double;
|
||||
RangeA, RangeB: TDoubleArray;
|
||||
RangeCount: Integer;
|
||||
Ov: TPreviewOverlay;
|
||||
|
||||
{ Занято ли это время другим оверлеем. }
|
||||
|
|
@ -1163,29 +1214,66 @@ begin
|
|||
|
||||
Hidden.Add(Clip);
|
||||
|
||||
Ov := Default(TPreviewOverlay);
|
||||
Ov.TlStart := Clip.TlStart;
|
||||
Ov.TlEnd := Clip.TlEnd;
|
||||
Ov.FilePath := Item.EffectivePath;
|
||||
Ov.SrcStart := Clip.SrcIn;
|
||||
Ov.Filter := OverlayFilter(Clip.Transform,
|
||||
Item.Info.Width, Item.Info.Height,
|
||||
AProject.Master.Width, AProject.Master.Height,
|
||||
Ov.X, Ov.Y, Ov.Width, Ov.Height);
|
||||
{ Порядок тот же, что при сборке: сперва вписали в кадр, потом
|
||||
выбили цвет, потом приглушили. Иначе смягчение края разошлось бы
|
||||
с тем, что даст готовый ролик. }
|
||||
if AWithKey then
|
||||
{ Оверлей виден не всюду, где лежит: сверху его может закрывать
|
||||
сплошной клип, и в готовом ролике его там не будет — побеждает
|
||||
верхняя дорожка целиком. Показывать в окне то, чего не будет в
|
||||
ролике, нельзя: именно из-за такого расхождения человек собирает
|
||||
ролик и не находит в нём миникарты.
|
||||
|
||||
Закрывать могут и посередине — тогда от оверлея остаётся
|
||||
несколько отрезков, и каждый становится своим окном показа. }
|
||||
RangeCount := 1;
|
||||
SetLength(RangeA, 1);
|
||||
SetLength(RangeB, 1);
|
||||
RangeA[0] := Clip.TlStart;
|
||||
RangeB[0] := Clip.TlEnd;
|
||||
|
||||
for K := 0 to T - 1 do
|
||||
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);
|
||||
Above := AProject.Tracks[K];
|
||||
if Above.Kind <> tkVideo then
|
||||
Continue;
|
||||
if not AProject.TrackAudible(Above) then
|
||||
Continue;
|
||||
for J := 0 to Above.Count - 1 do
|
||||
{ Клип с ключом или прозрачностью сам ничего не закрывает. }
|
||||
if not IsOverlayClip(Above[J]) then
|
||||
CutRange(RangeA, RangeB, RangeCount,
|
||||
Above[J].TlStart, Above[J].TlEnd);
|
||||
end;
|
||||
|
||||
for J := 0 to RangeCount - 1 do
|
||||
begin
|
||||
if RangeB[J] <= RangeA[J] + 0.001 then
|
||||
Continue;
|
||||
if OverlapsTaken(RangeA[J], RangeB[J]) then
|
||||
Continue;
|
||||
|
||||
Ov := Default(TPreviewOverlay);
|
||||
Ov.TlStart := RangeA[J];
|
||||
Ov.TlEnd := RangeB[J];
|
||||
Ov.FilePath := Item.EffectivePath;
|
||||
Ov.SrcStart := Clip.SrcIn + (RangeA[J] - Clip.TlStart);
|
||||
Ov.Filter := OverlayFilter(Clip.Transform,
|
||||
Item.Info.Width, Item.Info.Height,
|
||||
AProject.Master.Width, AProject.Master.Height,
|
||||
Ov.X, Ov.Y, Ov.Width, Ov.Height);
|
||||
{ Порядок тот же, что при сборке: сперва вписали в кадр, потом
|
||||
выбили цвет, потом приглушили. Иначе смягчение края разошлось
|
||||
бы с тем, что даст готовый ролик. }
|
||||
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);
|
||||
end;
|
||||
|
||||
SetLength(AOverlays, N + 1);
|
||||
AOverlays[N] := Ov;
|
||||
Inc(N);
|
||||
end;
|
||||
SetLength(AOverlays, N + 1);
|
||||
AOverlays[N] := Ov;
|
||||
Inc(N);
|
||||
end;
|
||||
end;
|
||||
|
||||
|
|
|
|||
|
|
@ -101,6 +101,8 @@ type
|
|||
{ Наложение для плеера: список кусков оверлея и окна показа. }
|
||||
{ Журнал текущей сборки: файл рядом с готовым роликом. }
|
||||
FExportLogPath: string;
|
||||
{ Разметка текста, отданная плееру в прошлый раз. }
|
||||
FLastAss: string;
|
||||
{ Проект открыт из командной строки: спросить про клипы на чужих
|
||||
дорожках можно только когда окно уже показано. }
|
||||
FCheckMisplacedOnShow: Boolean;
|
||||
|
|
@ -903,7 +905,7 @@ begin
|
|||
FProject.BeginChange;
|
||||
|
||||
if Track = nil then
|
||||
Track := FProject.AddTrackOfKind('Т1', tkText);
|
||||
Track := FProject.AddTrackOfKind(FProject.UniqueTrackName('Т'), tkText);
|
||||
|
||||
Clip := TClip.Create;
|
||||
Clip.Id := FProject.NewId('c');
|
||||
|
|
@ -1707,8 +1709,7 @@ begin
|
|||
|
||||
if ATrackIndex < 0 then
|
||||
begin
|
||||
FProject.AddTrack('V' +
|
||||
IntToStr(FProject.TrackCountOf(tkVideo) + 1));
|
||||
FProject.AddTrack(FProject.UniqueTrackName('V'));
|
||||
ATrackIndex := FProject.TrackCount - 1;
|
||||
Log('Заведена дорожка ' + FProject.Tracks[ATrackIndex].Name);
|
||||
end;
|
||||
|
|
@ -1738,11 +1739,9 @@ begin
|
|||
if Track = nil then
|
||||
begin
|
||||
if NeedKind = tkAudio then
|
||||
Track := FProject.AddTrackOfKind('A' +
|
||||
IntToStr(FProject.TrackCountOf(tkAudio) + 1), tkAudio)
|
||||
Track := FProject.AddTrackOfKind(FProject.UniqueTrackName('A'), tkAudio)
|
||||
else
|
||||
Track := FProject.AddTrack('V' +
|
||||
IntToStr(FProject.TrackCountOf(tkVideo) + 1));
|
||||
Track := FProject.AddTrack(FProject.UniqueTrackName('V'));
|
||||
end;
|
||||
Log(Format('Дорожка %s не для этого вида клипов — положил на %s.',
|
||||
[Wanted, Track.Name]));
|
||||
|
|
@ -2894,18 +2893,30 @@ end;
|
|||
|
||||
Пересобирать цепочку на каждый кадр нельзя — она создаётся заново и
|
||||
звук щёлкает. Поэтому только при изменении. }
|
||||
{ Разметка текста для предпросмотра.
|
||||
|
||||
Сравнивается сама разметка, а не имя файла: имя у неё одно на весь
|
||||
проект, и правка текста его не меняет. Плеер же, увидев прежнее имя,
|
||||
файл не перечитывал — и новая надпись в окне не появлялась вовсе, пока
|
||||
не перезагрузится весь список кусков. }
|
||||
procedure TMainForm.UpdateSubtitles;
|
||||
var
|
||||
Path: string;
|
||||
{ Не Text: так называется свойство формы. }
|
||||
Path, Markup: string;
|
||||
begin
|
||||
if (FProject = nil) or (FPlayer = nil) then
|
||||
Exit;
|
||||
if FProject.CacheDir = '' then
|
||||
Exit;
|
||||
|
||||
Markup := BuildAss(FProject);
|
||||
if Markup = FLastAss then
|
||||
Exit;
|
||||
FLastAss := Markup;
|
||||
|
||||
Path := IncludeTrailingPathDelimiter(FProject.CacheDir) + 'subs.ass';
|
||||
if WriteAssFile(FProject, Path) then
|
||||
FPlayer.SetSubtitleFile(Path)
|
||||
if (Markup <> '') and WriteAssFile(FProject, Path) then
|
||||
FPlayer.SetSubtitleFile(Path, True)
|
||||
else
|
||||
FPlayer.SetSubtitleFile('');
|
||||
end;
|
||||
|
|
@ -3529,8 +3540,7 @@ end;
|
|||
procedure TMainForm.MenuAddTrack(Sender: TObject);
|
||||
begin
|
||||
FProject.BeginChange;
|
||||
FProject.AddTrack('V' +
|
||||
IntToStr(FProject.TrackCountOf(tkVideo) + 1));
|
||||
FProject.AddTrack(FProject.UniqueTrackName('V'));
|
||||
FTimeline.Invalidate;
|
||||
UpdateCaption;
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -218,7 +218,10 @@ type
|
|||
|
||||
Держится отдельно от списка кусков: текст правится чаще, чем
|
||||
монтаж, и перезагружать из-за него картинку незачем. }
|
||||
procedure SetSubtitleFile(const APath: string);
|
||||
{ AForce — перечитать файл, даже если имя прежнее. Нужно всегда, когда
|
||||
разметка переписана: имя у неё одно на весь проект, и по имени
|
||||
изменение не видно. }
|
||||
procedure SetSubtitleFile(const APath: string; AForce: Boolean = False);
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -1096,9 +1099,9 @@ end;
|
|||
подключаем новые, не трогая воспроизведение. При загрузке нового списка
|
||||
кусков mpv забывает все внешние дорожки, поэтому там подключение
|
||||
повторяется — см. SelectExternalAudio. }
|
||||
procedure TMpvPlayer.SetSubtitleFile(const APath: string);
|
||||
procedure TMpvPlayer.SetSubtitleFile(const APath: string; AForce: Boolean);
|
||||
begin
|
||||
if FSubtitlePath = APath then
|
||||
if (FSubtitlePath = APath) and (not AForce) then
|
||||
Exit;
|
||||
FSubtitlePath := APath;
|
||||
if FHandle = nil then
|
||||
|
|
|
|||
|
|
@ -282,6 +282,14 @@ type
|
|||
{ Номер новой связки. Отдельным именем, чтобы не пришлось помнить
|
||||
префикс в каждом месте, где связка заводится. }
|
||||
function NewGroupId: string;
|
||||
{ Имя, которого ещё нет ни у одной дорожки: V1, V2, ... Считать
|
||||
дорожки нужного вида и прибавлять единицу нельзя — после удаления
|
||||
средней счёт совпадёт с уже занятым именем, и в окне окажутся две
|
||||
дорожки Т2. }
|
||||
function UniqueTrackName(const APrefix: string): string;
|
||||
{ Разводит одинаковые имена дорожек, доставшиеся от старых проектов.
|
||||
Имя — только подпись, но две дорожки Т2 в окне сбивают с толку. }
|
||||
procedure EnsureUniqueTrackNames;
|
||||
function AddTrack(const AName: string): TTrack;
|
||||
function AddTrackOfKind(const AName: string; AKind: TTrackKind): TTrack;
|
||||
function FindTrack(const AId: string): TTrack;
|
||||
|
|
@ -717,6 +725,45 @@ begin
|
|||
FModified := True;
|
||||
end;
|
||||
|
||||
function TProject.UniqueTrackName(const APrefix: string): string;
|
||||
var
|
||||
N, I: Integer;
|
||||
Taken: Boolean;
|
||||
begin
|
||||
N := 1;
|
||||
repeat
|
||||
Result := APrefix + IntToStr(N);
|
||||
Taken := False;
|
||||
for I := 0 to FTracks.Count - 1 do
|
||||
if TTrack(FTracks[I]).Name = Result then
|
||||
begin
|
||||
Taken := True;
|
||||
Break;
|
||||
end;
|
||||
Inc(N);
|
||||
until not Taken;
|
||||
end;
|
||||
|
||||
procedure TProject.EnsureUniqueTrackNames;
|
||||
var
|
||||
I, J: Integer;
|
||||
Prefix: string;
|
||||
begin
|
||||
for I := 0 to FTracks.Count - 1 do
|
||||
for J := 0 to I - 1 do
|
||||
if TTrack(FTracks[J]).Name = TTrack(FTracks[I]).Name then
|
||||
begin
|
||||
case TTrack(FTracks[I]).Kind of
|
||||
tkAudio: Prefix := 'A';
|
||||
tkText: Prefix := 'Т';
|
||||
else
|
||||
Prefix := 'V';
|
||||
end;
|
||||
TTrack(FTracks[I]).Name := UniqueTrackName(Prefix);
|
||||
Break;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TProject.AddTrack(const AName: string): TTrack;
|
||||
begin
|
||||
Result := AddTrackOfKind(AName, tkVideo);
|
||||
|
|
@ -1083,13 +1130,10 @@ begin
|
|||
|
||||
if Target = nil then
|
||||
case Need of
|
||||
tkAudio: Target := AddTrackOfKind('A' +
|
||||
IntToStr(TrackCountOf(tkAudio) + 1), tkAudio);
|
||||
tkText: Target := AddTrackOfKind('Т' +
|
||||
IntToStr(TrackCountOf(tkText) + 1), tkText);
|
||||
tkAudio: Target := AddTrackOfKind(UniqueTrackName('A'), tkAudio);
|
||||
tkText: Target := AddTrackOfKind(UniqueTrackName('Т'), tkText);
|
||||
else
|
||||
Target := AddTrackOfKind('V' +
|
||||
IntToStr(TrackCountOf(tkVideo) + 1), tkVideo);
|
||||
Target := AddTrackOfKind(UniqueTrackName('V'), tkVideo);
|
||||
end;
|
||||
|
||||
Track.Extract(Clip);
|
||||
|
|
@ -1737,6 +1781,7 @@ begin
|
|||
|
||||
MigrateAudio;
|
||||
FixClipKinds;
|
||||
EnsureUniqueTrackNames;
|
||||
|
||||
Settings := Obj.Find('settings', jtObject) as TJSONObject;
|
||||
if Settings <> nil then
|
||||
|
|
|
|||
|
|
@ -2084,13 +2084,10 @@ begin
|
|||
(not FTrackAddedDuringDrag) then
|
||||
begin
|
||||
case TProject.TrackKindFor(FDragClip.Kind) of
|
||||
tkAudio: FProject.AddTrackOfKind('A' +
|
||||
IntToStr(FProject.TrackCountOf(tkAudio) + 1), tkAudio);
|
||||
tkText: FProject.AddTrackOfKind('Т' +
|
||||
IntToStr(FProject.TrackCountOf(tkText) + 1), tkText);
|
||||
tkAudio: FProject.AddTrackOfKind(FProject.UniqueTrackName('A'), tkAudio);
|
||||
tkText: FProject.AddTrackOfKind(FProject.UniqueTrackName('Т'), tkText);
|
||||
else
|
||||
FProject.AddTrack('V' +
|
||||
IntToStr(FProject.TrackCountOf(tkVideo) + 1));
|
||||
FProject.AddTrack(FProject.UniqueTrackName('V'));
|
||||
end;
|
||||
FTrackAddedDuringDrag := True;
|
||||
Idx := FProject.TrackCount - 1;
|
||||
|
|
|
|||
|
|
@ -854,6 +854,65 @@ begin
|
|||
end;
|
||||
WriteLn;
|
||||
|
||||
{ --- 11. Оверлей под сплошным клипом в окне не показывается --- }
|
||||
WriteLn(' 11. Оверлей закрыт сверху сплошным клипом');
|
||||
Proj := MakeProject(3, Media);
|
||||
try
|
||||
Proj.Master := Media.Info;
|
||||
{ Снизу вверх: V3 — съёмка, V2 — оверлей с ключом на весь ролик,
|
||||
V1 — фотография поверх куска. Так и было в живом проекте. }
|
||||
Proj.AddClip(Proj.Tracks[2], Media, 0, 20, 0);
|
||||
Clip := Proj.AddClip(Proj.Tracks[1], Media, 0, 20, 0);
|
||||
Key := DefaultColorKey;
|
||||
Key.Enabled := True;
|
||||
Clip.ColorKey := Key;
|
||||
Clip := Proj.AddClip(Proj.Tracks[0], Media, 0, 5, 5);
|
||||
Clip.Kind := ckImage;
|
||||
|
||||
Check(FlattenPreview(Proj, Segs, AudioSegs, Overlays, Missing),
|
||||
'уплощение для предпросмотра прошло');
|
||||
{ Фотография лежит с 5 по 10 секунду и закрывает оверлей: показывать
|
||||
его там нельзя — в ролике его там не будет. Значит, от оверлея
|
||||
остаются два окна: до фотографии и после. }
|
||||
Check(Length(Overlays) = 2, 'оверлей показывается двумя окнами: ' +
|
||||
IntToStr(Length(Overlays)));
|
||||
if Length(Overlays) = 2 then
|
||||
begin
|
||||
Check(Abs(Overlays[0].TlStart) < 0.001, 'первое окно с начала');
|
||||
Check(Abs(Overlays[0].TlEnd - 5) < 0.001, 'и до фотографии');
|
||||
Check(Abs(Overlays[1].TlStart - 10) < 0.001, 'второе после неё');
|
||||
Check(Abs(Overlays[1].TlEnd - 20) < 0.001, 'и до конца');
|
||||
Check(Abs(Overlays[1].SrcStart - 10) < 0.001,
|
||||
'и берётся с того же места исходника');
|
||||
end;
|
||||
finally
|
||||
Proj.Free;
|
||||
end;
|
||||
WriteLn;
|
||||
|
||||
{ --- 12. Имена дорожек не повторяются --- }
|
||||
WriteLn(' 12. Имена дорожек');
|
||||
Proj := MakeProject(3, Media);
|
||||
try
|
||||
Check(Proj.UniqueTrackName('V') = 'V4', 'следующее имя V4: ' +
|
||||
Proj.UniqueTrackName('V'));
|
||||
{ Убираем среднюю: счёт дорожек снова три, но имя V3 уже занято, и
|
||||
считать их для имени нельзя. }
|
||||
Proj.RemoveTrack(1);
|
||||
Check(Proj.UniqueTrackName('V') = 'V2', 'после удаления средней — V2: ' +
|
||||
Proj.UniqueTrackName('V'));
|
||||
Proj.AddTrack(Proj.UniqueTrackName('V'));
|
||||
Check(Proj.Tracks[2].Name = 'V2', 'новая дорожка названа V2');
|
||||
|
||||
{ Одинаковые имена, доставшиеся от старого проекта, разводятся. }
|
||||
Proj.Tracks[2].Name := 'V1';
|
||||
Proj.EnsureUniqueTrackNames;
|
||||
Check(Proj.Tracks[2].Name <> 'V1', 'повтор разведён: ' + Proj.Tracks[2].Name);
|
||||
finally
|
||||
Proj.Free;
|
||||
end;
|
||||
WriteLn;
|
||||
|
||||
WriteLn;
|
||||
if FailCount = 0 then
|
||||
begin
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue