Клип не попадает на дорожку чужого вида
В живом проекте фотография оказалась на текстовой дорожке, а надпись — на дорожке картинки. Уплощение обходит дорожки по видам, и такой клип не видит ни сборка, ни предпросмотр: на монтаже он нарисован, в готовом ролике его нет, и ни одной жалобы по дороге. Именно это и было причиной пропавших фотографии и текста, а не ошибка со стоп-кадром. Теперь перетаскивание на дорожку чужого вида не проходит, а файл из списка, брошенный на такую дорожку, ложится на подходящую — с записью в журнал, куда именно. Новая дорожка, заведённая перетаскиванием вниз, заводится того вида, какой нужен клипу. При открытии проекта такие клипы пересчитываются, и программа предлагает перенести их на подходящие дорожки, сохранив время. Молча не переносим: клип мог оказаться там осознанно, и внезапный переезд — сюрприз хуже пропажи. Проверка 10 в nlvetool flattest: картинка на текстовой дорожке в уплощение не попадает, после переноса попадает и остаётся на своём месте.
This commit is contained in:
parent
ed889e5d03
commit
3add5fcbf6
5 changed files with 261 additions and 6 deletions
|
|
@ -121,6 +121,12 @@ NVENC, если он есть). Это разрозненно и не даёт
|
|||
|
||||
## Мелочи, до которых не дошли руки
|
||||
|
||||
**Вид дорожки не виден в её заголовке.** Дорожки картинки, звука и текста
|
||||
подписаны V1, A1, Т1, и по букве вид угадывается, но не более того.
|
||||
Клип, попавший на дорожку чужого вида, теперь не принимается и не
|
||||
теряется, но само различие стоило бы показать явно — значком или цветом
|
||||
полосы заголовка.
|
||||
|
||||
**Значок файла программы под Windows.** Значок окна задаётся из
|
||||
`uIconData`, а значок самого `.exe` в проводнике берётся из ресурса при
|
||||
сборке — там он до сих пор стандартный.
|
||||
|
|
|
|||
|
|
@ -101,6 +101,9 @@ type
|
|||
{ Наложение для плеера: список кусков оверлея и окна показа. }
|
||||
{ Журнал текущей сборки: файл рядом с готовым роликом. }
|
||||
FExportLogPath: string;
|
||||
{ Проект открыт из командной строки: спросить про клипы на чужих
|
||||
дорожках можно только когда окно уже показано. }
|
||||
FCheckMisplacedOnShow: Boolean;
|
||||
FPreviewOverlayUrl: string;
|
||||
FPreviewOverlayWindows: TOverlayWindows;
|
||||
{ Списки кусков звуковых дорожек для плеера. }
|
||||
|
|
@ -200,6 +203,7 @@ type
|
|||
{ Переписывает файл субтитров и отдаёт его плееру. }
|
||||
procedure UpdateSubtitles;
|
||||
procedure UpdateRippleButton;
|
||||
procedure CheckMisplacedClips;
|
||||
procedure UpdateSnapButton;
|
||||
procedure UpdateGlueButton;
|
||||
procedure UpdateCenterButton;
|
||||
|
|
@ -1159,6 +1163,7 @@ begin
|
|||
UpdateRippleButton;
|
||||
UpdateCaption;
|
||||
SchedulePreview;
|
||||
FCheckMisplacedOnShow := True;
|
||||
end
|
||||
else
|
||||
Log('Не удалось открыть проект: ' + Err);
|
||||
|
|
@ -1175,6 +1180,14 @@ begin
|
|||
if lstPool.Items.Count > 0 then
|
||||
lstPool.ItemIndex := 0;
|
||||
end;
|
||||
|
||||
{ Спрашивать про клипы на чужих дорожках можно только теперь: окно
|
||||
показано, и вопрос не окажется за кадром. }
|
||||
if FCheckMisplacedOnShow then
|
||||
begin
|
||||
FCheckMisplacedOnShow := False;
|
||||
CheckMisplacedClips;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ Проверка не по одному месту, а разом: человеку нужен ответ «чего не
|
||||
|
|
@ -1666,7 +1679,9 @@ var
|
|||
Track: TTrack;
|
||||
Mismatches: TMismatches;
|
||||
Duration, PlaceAt: Double;
|
||||
NormPath: string;
|
||||
NormPath, Wanted: string;
|
||||
NeedKind: TTrackKind;
|
||||
I: Integer;
|
||||
begin
|
||||
Result := False;
|
||||
if (APoolIndex < 0) or (APoolIndex >= FProject.PoolCount) then
|
||||
|
|
@ -1687,6 +1702,38 @@ begin
|
|||
ATrackIndex := FProject.TrackCount - 1;
|
||||
Track := FProject.Tracks[ATrackIndex];
|
||||
|
||||
{ Дорожка чужого вида не годится: клип на ней не увидит ни сборка, ни
|
||||
предпросмотр — уплощение обходит дорожки по видам. Поэтому не кладём
|
||||
туда молча, а находим подходящую и говорим, куда положили. }
|
||||
{ Файл без картинки — только звук; всё остальное идёт на дорожку
|
||||
картинки, а парный звук программа положит сама. }
|
||||
if Item.Info.HasVideo or (Item.Info.Kind = mkImage) then
|
||||
NeedKind := tkVideo
|
||||
else
|
||||
NeedKind := tkAudio;
|
||||
if Track.Kind <> NeedKind then
|
||||
begin
|
||||
Wanted := Track.Name;
|
||||
Track := nil;
|
||||
for I := 0 to FProject.TrackCount - 1 do
|
||||
if FProject.Tracks[I].Kind = NeedKind then
|
||||
begin
|
||||
Track := FProject.Tracks[I];
|
||||
Break;
|
||||
end;
|
||||
if Track = nil then
|
||||
begin
|
||||
if NeedKind = tkAudio then
|
||||
Track := FProject.AddTrackOfKind('A' +
|
||||
IntToStr(FProject.TrackCountOf(tkAudio) + 1), tkAudio)
|
||||
else
|
||||
Track := FProject.AddTrack('V' +
|
||||
IntToStr(FProject.TrackCountOf(tkVideo) + 1));
|
||||
end;
|
||||
Log(Format('Дорожка %s не для этого вида клипов — положил на %s.',
|
||||
[Wanted, Track.Name]));
|
||||
end;
|
||||
|
||||
{ Предупреждаем именно здесь, при попадании на монтаж: пока файл просто
|
||||
лежит в списке, он ни с чем не склеивается. }
|
||||
Mismatches := CompareToMaster(FProject.Master, Item.Info);
|
||||
|
|
@ -2202,12 +2249,59 @@ begin
|
|||
FSettings.NoteRecentProject(FProject.FileName);
|
||||
RefreshRecentMenu;
|
||||
Log('Открыт проект: ' + FProject.FileName);
|
||||
CheckMisplacedClips;
|
||||
end;
|
||||
|
||||
{ --- Схлопывание --- }
|
||||
|
||||
{ Кнопка показывает и состояние, и режим: утоплена — автосхлопывание
|
||||
включено, надпись говорит, на какие дорожки оно действует. }
|
||||
{ Клипы на дорожках чужого вида: находим и предлагаем перенести.
|
||||
|
||||
Такой клип нарисован на монтаже, но в ролик не попадает: уплощение
|
||||
обходит дорожки по видам и клип с чужой дорожки не видит. Пропажа тихая
|
||||
— сборка проходит без единой жалобы, а в готовом файле фотографии или
|
||||
надписи нет. Проверено на живом проекте, где фотография оказалась на
|
||||
текстовой дорожке, а надпись — на дорожке картинки.
|
||||
|
||||
Молча переносить нельзя: человек мог положить клип туда осознанно, и
|
||||
внезапно переехавший клип — сюрприз хуже пропажи. Поэтому спрашиваем. }
|
||||
procedure TMainForm.CheckMisplacedClips;
|
||||
var
|
||||
N: Integer;
|
||||
begin
|
||||
N := FProject.MisplacedClipCount;
|
||||
if N = 0 then
|
||||
Exit;
|
||||
|
||||
Log(Format('ВНИМАНИЕ: %d %s на дорожках чужого вида — в сборку они не попадут.',
|
||||
[N, Plural(N, 'клип', 'клипа', 'клипов')]));
|
||||
|
||||
if ThemedMessageDlg('nlve',
|
||||
Format('В проекте %d %s лежит на дорожках не своего вида: ' +
|
||||
'картинка на звуковой или текстовой, надпись на дорожке картинки.' +
|
||||
LineEnding + LineEnding +
|
||||
'Такой клип виден на монтаже, но в готовый ролик не попадает — ' +
|
||||
'ни в сборку, ни в предпросмотр.' + LineEnding + LineEnding +
|
||||
'Перенести их на подходящие дорожки? Время клипов не изменится.',
|
||||
[N, Plural(N, 'клип', 'клипа', 'клипов')]),
|
||||
mtWarning, [mbYes, mbNo], 0) <> mrYes then
|
||||
begin
|
||||
SetStatus(Format('%d %s не попадёт в сборку: они на дорожках чужого вида.',
|
||||
[N, Plural(N, 'клип', 'клипа', 'клипов')]));
|
||||
Exit;
|
||||
end;
|
||||
|
||||
FProject.BeginChange;
|
||||
N := FProject.FixMisplacedClips;
|
||||
FTimeline.Invalidate;
|
||||
FLastLoadedEdl := '';
|
||||
SchedulePreview;
|
||||
UpdateCaption;
|
||||
Log(Format('Перенесено на подходящие дорожки: %d.', [N]));
|
||||
SetStatus(Format('Перенесено клипов: %d.', [N]));
|
||||
end;
|
||||
|
||||
procedure TMainForm.UpdateRippleButton;
|
||||
var
|
||||
ModeText: string;
|
||||
|
|
|
|||
112
src/uProject.pas
112
src/uProject.pas
|
|
@ -380,6 +380,22 @@ type
|
|||
если он окажется хуже их, разница бросится в глаза именно на нём.
|
||||
Лучше потратить место, чем показать шакала посреди чистой съёмки. }
|
||||
function MaxVideoBitrate: Int64;
|
||||
{ Какая дорожка нужна клипу этого вида. Правило одно на всю
|
||||
программу: картинке — дорожка картинки, звуку — звуковая, надписи —
|
||||
текстовая. }
|
||||
class function TrackKindFor(AClipKind: TClipKind): TTrackKind;
|
||||
{ Сколько клипов лежит на дорожках не своего вида.
|
||||
|
||||
Такой клип не попадает никуда: уплощение обходит дорожки по видам,
|
||||
и клип с чужой дорожки не видит ни сборка, ни предпросмотр. Пропажа
|
||||
при этом тихая — на монтаже он нарисован, а в ролике его нет. }
|
||||
function MisplacedClipCount: Integer;
|
||||
{ Переносит их на подходящие дорожки. Возвращает, сколько перенесено.
|
||||
|
||||
Ищется первая дорожка нужного вида, где это время свободно; если
|
||||
такой нет — заводится новая. Время клипа не меняется: человек ставил
|
||||
его именно туда. }
|
||||
function FixMisplacedClips: Integer;
|
||||
{ Автоматическое схлопывание при перетаскивании клипов. }
|
||||
property RippleEnabled: Boolean read FRippleEnabled write FRippleEnabled;
|
||||
property FileName: string read FFileName write FFileName;
|
||||
|
|
@ -990,6 +1006,102 @@ begin
|
|||
Result := FRegionEnd > FRegionStart + 0.001;
|
||||
end;
|
||||
|
||||
class function TProject.TrackKindFor(AClipKind: TClipKind): TTrackKind;
|
||||
begin
|
||||
case AClipKind of
|
||||
ckAudio: Result := tkAudio;
|
||||
ckText: Result := tkText;
|
||||
else
|
||||
Result := tkVideo;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TProject.MisplacedClipCount: Integer;
|
||||
var
|
||||
T, C: Integer;
|
||||
Track: TTrack;
|
||||
begin
|
||||
Result := 0;
|
||||
for T := 0 to TrackCount - 1 do
|
||||
begin
|
||||
Track := Tracks[T];
|
||||
for C := 0 to Track.Count - 1 do
|
||||
if TrackKindFor(Track[C].Kind) <> Track.Kind then
|
||||
Inc(Result);
|
||||
end;
|
||||
end;
|
||||
|
||||
function TProject.FixMisplacedClips: Integer;
|
||||
var
|
||||
T, C, K: Integer;
|
||||
Track, Target: TTrack;
|
||||
Clip: TClip;
|
||||
Need: TTrackKind;
|
||||
Moved: TFPList;
|
||||
|
||||
{ Свободна ли дорожка в это время. }
|
||||
function Fits(ATrack: TTrack; AClip: TClip): Boolean;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
Result := True;
|
||||
for I := 0 to ATrack.Count - 1 do
|
||||
if (AClip.TlStart < ATrack[I].TlEnd - 0.001) and
|
||||
(AClip.TlEnd > ATrack[I].TlStart + 0.001) then
|
||||
Exit(False);
|
||||
end;
|
||||
|
||||
begin
|
||||
Result := 0;
|
||||
Moved := TFPList.Create;
|
||||
try
|
||||
{ Сперва собираем список: переносить прямо во время обхода нельзя —
|
||||
состав дорожек меняется под ногами. }
|
||||
for T := 0 to TrackCount - 1 do
|
||||
begin
|
||||
Track := Tracks[T];
|
||||
for C := 0 to Track.Count - 1 do
|
||||
if TrackKindFor(Track[C].Kind) <> Track.Kind then
|
||||
Moved.Add(Track[C]);
|
||||
end;
|
||||
|
||||
for C := 0 to Moved.Count - 1 do
|
||||
begin
|
||||
Clip := TClip(Moved[C]);
|
||||
Track := FindClipTrack(Clip);
|
||||
if Track = nil then
|
||||
Continue;
|
||||
Need := TrackKindFor(Clip.Kind);
|
||||
|
||||
Target := nil;
|
||||
for K := 0 to TrackCount - 1 do
|
||||
if (Tracks[K].Kind = Need) and Fits(Tracks[K], Clip) then
|
||||
begin
|
||||
Target := Tracks[K];
|
||||
Break;
|
||||
end;
|
||||
|
||||
if Target = nil then
|
||||
case Need of
|
||||
tkAudio: Target := AddTrackOfKind('A' +
|
||||
IntToStr(TrackCountOf(tkAudio) + 1), tkAudio);
|
||||
tkText: Target := AddTrackOfKind('Т' +
|
||||
IntToStr(TrackCountOf(tkText) + 1), tkText);
|
||||
else
|
||||
Target := AddTrackOfKind('V' +
|
||||
IntToStr(TrackCountOf(tkVideo) + 1), tkVideo);
|
||||
end;
|
||||
|
||||
Track.Extract(Clip);
|
||||
Target.Add(Clip);
|
||||
Target.Sort;
|
||||
Inc(Result);
|
||||
end;
|
||||
finally
|
||||
Moved.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TProject.MaxVideoBitrate: Int64;
|
||||
var
|
||||
I: Integer;
|
||||
|
|
|
|||
|
|
@ -2013,12 +2013,20 @@ begin
|
|||
|
||||
{ Утащили ниже последней дорожки — заводим новую, как в Vegas.
|
||||
За одно перетаскивание не больше одной: иначе движение мыши
|
||||
вниз наплодило бы их десяток. }
|
||||
вниз наплодило бы их десяток. Вид новой дорожки — по клипу:
|
||||
картинку на звуковую дорожку класть незачем. }
|
||||
if (Idx < 0) and (FProject <> nil) and (Y > TracksBottom) and
|
||||
(not FTrackAddedDuringDrag) then
|
||||
begin
|
||||
FProject.AddTrack('V' +
|
||||
IntToStr(FProject.TrackCountOf(tkVideo) + 1));
|
||||
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);
|
||||
else
|
||||
FProject.AddTrack('V' +
|
||||
IntToStr(FProject.TrackCountOf(tkVideo) + 1));
|
||||
end;
|
||||
FTrackAddedDuringDrag := True;
|
||||
Idx := FProject.TrackCount - 1;
|
||||
if Assigned(FOnTrackAdded) then
|
||||
|
|
@ -2026,11 +2034,18 @@ begin
|
|||
end;
|
||||
|
||||
{ Перенос на другую дорожку: клип следует за указателем по
|
||||
вертикали. Именно перенос, а не копирование. }
|
||||
вертикали. Именно перенос, а не копирование.
|
||||
|
||||
На дорожку чужого вида клип не пускаем. Уплощение обходит
|
||||
дорожки по видам, и клип, попавший не на свою, пропадает и из
|
||||
сборки, и из предпросмотра — молча: на монтаже он нарисован, а
|
||||
в ролике его нет. Проверено на живом проекте, где фотография
|
||||
оказалась на текстовой дорожке. }
|
||||
if (Idx >= 0) and (FProject <> nil) then
|
||||
begin
|
||||
TargetTrack := FProject.Tracks[Idx];
|
||||
if TargetTrack <> FDragTrack then
|
||||
if (TargetTrack <> FDragTrack) and
|
||||
(TargetTrack.Kind = TProject.TrackKindFor(FDragClip.Kind)) then
|
||||
begin
|
||||
FDragTrack.Extract(FDragClip);
|
||||
TargetTrack.Add(FDragClip);
|
||||
|
|
|
|||
|
|
@ -826,6 +826,34 @@ begin
|
|||
end;
|
||||
WriteLn;
|
||||
|
||||
{ --- 10. Клипы на дорожках чужого вида --- }
|
||||
WriteLn(' 10. Клип на дорожке не своего вида');
|
||||
Proj := MakeProject(1, Media);
|
||||
try
|
||||
Proj.EnsureAudioTracks;
|
||||
Proj.AddTrackOfKind('Т1', tkText);
|
||||
{ Картинка на текстовой дорожке — ровно то, что нашлось в живом
|
||||
проекте: клип виден на монтаже, а в ролик не попадает. }
|
||||
Clip := Proj.AddClip(Proj.Tracks[Proj.TrackCount - 1], Media, 0, 3, 10);
|
||||
Clip.Kind := ckImage;
|
||||
|
||||
Check(Proj.MisplacedClipCount = 1, 'нашёлся один такой клип');
|
||||
FlattenStream(Proj, tkVideo, 0, Segs, Missing);
|
||||
Check(Length(Segs) = 0, 'в уплощении его нет');
|
||||
|
||||
Check(Proj.FixMisplacedClips = 1, 'перенесён один клип');
|
||||
Check(Proj.MisplacedClipCount = 0, 'чужих клипов не осталось');
|
||||
FlattenStream(Proj, tkVideo, 0, Segs, Missing);
|
||||
DumpSegments(Segs);
|
||||
Check(Length(Segs) > 0, 'после переноса он попадает в уплощение');
|
||||
Clip := Proj.Tracks[0].ClipAt(11);
|
||||
Check((Clip <> nil) and (Abs(Clip.TlStart - 10) < 0.001),
|
||||
'и стоит на прежнем месте');
|
||||
finally
|
||||
Proj.Free;
|
||||
end;
|
||||
WriteLn;
|
||||
|
||||
WriteLn;
|
||||
if FailCount = 0 then
|
||||
begin
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue