Текст вжигается и в фотографию; дорожки и полоса участка
Вжигание не доходило до сгенерированных вставок. Картинки и чёрное отбирались первой же веткой сборки — до проверки на пересборку — и копировались целиком вместе с отметкой о разметке. Галка вжигания стояла, кусок в журнале был помечен «с текстом», а в ролике надписи не было. Причину показал журнал сборки рядом с файлом: помечено «пересобирается», а команда — копирование. Теперь вставка с разметкой идёт общей веткой пересборки, и время у неё берётся своё: она начинается с нуля, а не с середины монтажа. У линейки появился верхний этаж в двенадцать точек — про отметки. Протяжка по нему отмечает участок, даже если отметок не было; щелчок без движения их снимает. Уголки по краям тянутся, как и раньше. Заголовок дорожки: правый щелчок открывает меню — поднять, опустить, удалить (с клипами — с подтверждением). Заголовок можно и потащить: порядок дорожек есть порядок перекрытия, и менять его перетаскиванием привычнее, чем через меню.
This commit is contained in:
parent
07aee86631
commit
59c778d2f6
4 changed files with 249 additions and 10 deletions
15
README.md
15
README.md
|
|
@ -153,14 +153,23 @@ xdg-mime query default application/x-nlve-project
|
|||
времени. На слабой машине это может быть заметнее, потому выключатель и
|
||||
есть.
|
||||
|
||||
**Отмеченный участок.** Клавиша `I` ставит начало по курсору, `O` —
|
||||
конец; оба пункта есть в меню «Правка», там же снятие отметок. Участок
|
||||
показан полосой под линейкой и стойками по краям. Пока он отмечен, сборка
|
||||
**Отмеченный участок.** У линейки два этажа. Верхний, в двенадцать точек,
|
||||
— про отметки: протяжка по нему отмечает участок, даже если отметок не
|
||||
было, а щелчок без движения их снимает. По краям участка стоят жёлтые
|
||||
уголки, за них тянут. Нижний этаж — про курсор, как и раньше.
|
||||
|
||||
Клавиши тоже остались: `I` ставит начало по курсору, `O` — конец; оба
|
||||
пункта есть в меню «Правка», там же снятие отметок. Пока он отмечен, сборка
|
||||
собирает только его — это единственный способ проверить правку на длинном
|
||||
монтаже, не тратя на каждую проверку столько же времени, сколько идёт сам
|
||||
ролик. Отметки живут в файле проекта: участок относится к монтажу, а не к
|
||||
привычкам человека.
|
||||
|
||||
**Дорожки.** Правый щелчок по заголовку открывает меню: поднять,
|
||||
опустить, удалить. Заголовок можно и просто потащить мышью — порядок
|
||||
дорожек и есть порядок перекрытия, и менять его перетаскиванием
|
||||
привычнее. Удаление дорожки с клипами спрашивает подтверждение.
|
||||
|
||||
**Схлопывание.** Кнопка внизу показывает и режим, и состояние: утоплена —
|
||||
автосхлопывание включено, и тогда при перетаскивании клипа всё, что
|
||||
начинается не раньше него, едет следом. Стрелка рядом открывает выбор
|
||||
|
|
|
|||
|
|
@ -533,7 +533,7 @@ begin
|
|||
нужного: так одна и та же чёрная вставка годится для дыр любой
|
||||
длины. Значит вырезать надо и из них, но притягивать к опорному
|
||||
кадру нечего — они начинаются с нуля, а ноль всегда опорный. }
|
||||
if FSegments[I].Kind <> skVideo then
|
||||
if (FSegments[I].Kind <> skVideo) and (FSegments[I].SubFile = '') then
|
||||
begin
|
||||
Dst := FCacheDir + Format('part_%.3d_a%s', [I, FIntermediateExt]);
|
||||
Rep.Index := I;
|
||||
|
|
@ -587,10 +587,17 @@ begin
|
|||
Continue;
|
||||
end;
|
||||
|
||||
{ Кусок от файла, не совпадающего с параметрами проекта. Скопировать
|
||||
его нельзя — пересобираем. Но только его: соседние куски, снятые тем
|
||||
же оборудованием, что и весь проект, по-прежнему копируются. }
|
||||
if FSegments[I].Transcode then
|
||||
{ Кусок от файла, не совпадающего с параметрами проекта, или с
|
||||
вжигаемым текстом. Скопировать его нельзя — пересобираем. Но только
|
||||
его: соседние куски, снятые тем же оборудованием, что и весь
|
||||
проект, по-прежнему копируются.
|
||||
|
||||
Сгенерированные вставки — картинки и чёрное — сюда попадают только
|
||||
с текстом: без него их незачем перекодировать, они и так в
|
||||
параметрах проекта. Раньше они уходили в ветку выше и копировались
|
||||
вместе с текстом мимо: галка вжигания стояла, а надписи на
|
||||
фотографии не появлялось. }
|
||||
if FSegments[I].Transcode or (FSegments[I].SubFile <> '') then
|
||||
begin
|
||||
SetStage(Format('Пересобираю кусок %d из %d (файл не совпадает с проектом)',
|
||||
[I + 1, Length(FSegments)]));
|
||||
|
|
@ -604,8 +611,16 @@ begin
|
|||
Rep.Encoded := True;
|
||||
|
||||
FStepWork := FSegments[I].Duration;
|
||||
{ У сгенерированной вставки время своё: она начинается с нуля, и
|
||||
просить у неё кусок с середины монтажа бессмысленно. }
|
||||
if FSegments[I].Kind <> skVideo then
|
||||
Rep.RequestedStart := 0
|
||||
else
|
||||
Rep.RequestedStart := FSegments[I].SrcStart;
|
||||
Rep.ActualStart := Rep.RequestedStart;
|
||||
|
||||
if not RunFfmpeg(ArgsTranscodeSegment(FSegments[I].FilePath, Dst,
|
||||
FSegments[I].SrcStart, FSegments[I].SrcStart + FSegments[I].Duration,
|
||||
Rep.RequestedStart, Rep.RequestedStart + FSegments[I].Duration,
|
||||
FMaster.Width, FMaster.Height, FMaster.FpsNum, FMaster.FpsDen,
|
||||
FMaster.VideoCodec, FMaster.PixFmt, FMaster.AudioCodec,
|
||||
FMaster.SampleRate, FMaster.Channels, FSegments[I].Volume,
|
||||
|
|
|
|||
|
|
@ -182,6 +182,10 @@ type
|
|||
FPoolMenu: TPopupMenu;
|
||||
FPoolAddMenu: TMenuItem;
|
||||
FRippleMenu: TPopupMenu;
|
||||
{ Меню дорожки по правому щелчку в её заголовке. }
|
||||
FTrackMenu: TPopupMenu;
|
||||
{ Дорожка, для которой открыто меню. }
|
||||
FTrackMenuIndex: Integer;
|
||||
{ Пункт «Недавние проекты» в меню «Файл». Его содержимое собирается
|
||||
заново при каждом изменении списка. }
|
||||
FRecentMenu: TMenuItem;
|
||||
|
|
@ -204,6 +208,13 @@ type
|
|||
procedure UpdateSubtitles;
|
||||
procedure UpdateRippleButton;
|
||||
procedure CheckMisplacedClips;
|
||||
procedure BuildTrackMenu;
|
||||
procedure TimelineTrackMenu(Sender: TObject; ATrackIndex: Integer;
|
||||
const AScreenPos: TPoint);
|
||||
procedure TimelineRegionChange(Sender: TObject);
|
||||
procedure TrackMenuDelete(Sender: TObject);
|
||||
procedure TrackMenuUp(Sender: TObject);
|
||||
procedure TrackMenuDown(Sender: TObject);
|
||||
procedure UpdateSnapButton;
|
||||
procedure UpdateGlueButton;
|
||||
procedure UpdateCenterButton;
|
||||
|
|
@ -437,6 +448,7 @@ begin
|
|||
lstPool.DragMode := dmAutomatic;
|
||||
|
||||
BuildPoolMenu;
|
||||
BuildTrackMenu;
|
||||
RefreshRecentMenu;
|
||||
AllowDropFiles := True;
|
||||
OnDropFiles := @FormFilesDropped;
|
||||
|
|
@ -577,6 +589,8 @@ begin
|
|||
FTimeline.OnMediaDrop := @TimelineMediaDrop;
|
||||
FTimeline.OnClipTransform := @TimelineClipTransform;
|
||||
FTimeline.OnTrackAdded := @TimelineTrackAdded;
|
||||
FTimeline.OnTrackMenu := @TimelineTrackMenu;
|
||||
FTimeline.OnRegionChange := @TimelineRegionChange;
|
||||
FTimeline.OnActiveTrackChange := @TimelineActiveTrackChange;
|
||||
end;
|
||||
|
||||
|
|
@ -2448,6 +2462,111 @@ begin
|
|||
FRippleMenu.PopUp(P.X, P.Y);
|
||||
end;
|
||||
|
||||
{ --- Меню дорожки --- }
|
||||
|
||||
{ Меню по правому щелчку в заголовке дорожки.
|
||||
|
||||
Раньше дорожку можно было только добавить и убрать последнюю, а убрать
|
||||
чаще нужно не последнюю. Порядок дорожек тоже правится здесь — тем, кому
|
||||
перетаскивание заголовка неудобно. }
|
||||
{ Участок подвинули мышью: показываем, что вышло, и помечаем проект
|
||||
изменённым — отметки в нём и хранятся. }
|
||||
procedure TMainForm.TimelineRegionChange(Sender: TObject);
|
||||
begin
|
||||
FProject.Modified := True;
|
||||
UpdateCaption;
|
||||
if FProject.HasRegion then
|
||||
SetStatus(Format('Участок: %s — %s',
|
||||
[SecondsToStr(FProject.RegionStart), SecondsToStr(FProject.RegionEnd)]))
|
||||
else
|
||||
SetStatus('Отметки участка сняты: собирается весь монтаж.');
|
||||
end;
|
||||
|
||||
procedure TMainForm.BuildTrackMenu;
|
||||
var
|
||||
Item: TMenuItem;
|
||||
begin
|
||||
FTrackMenu := TPopupMenu.Create(Self);
|
||||
|
||||
Item := TMenuItem.Create(FTrackMenu);
|
||||
Item.Caption := 'Поднять дорожку';
|
||||
Item.OnClick := @TrackMenuUp;
|
||||
FTrackMenu.Items.Add(Item);
|
||||
|
||||
Item := TMenuItem.Create(FTrackMenu);
|
||||
Item.Caption := 'Опустить дорожку';
|
||||
Item.OnClick := @TrackMenuDown;
|
||||
FTrackMenu.Items.Add(Item);
|
||||
|
||||
Item := TMenuItem.Create(FTrackMenu);
|
||||
Item.Caption := '-';
|
||||
FTrackMenu.Items.Add(Item);
|
||||
|
||||
Item := TMenuItem.Create(FTrackMenu);
|
||||
Item.Caption := 'Удалить дорожку';
|
||||
Item.OnClick := @TrackMenuDelete;
|
||||
FTrackMenu.Items.Add(Item);
|
||||
end;
|
||||
|
||||
procedure TMainForm.TimelineTrackMenu(Sender: TObject; ATrackIndex: Integer;
|
||||
const AScreenPos: TPoint);
|
||||
begin
|
||||
if (ATrackIndex < 0) or (ATrackIndex >= FProject.TrackCount) then
|
||||
Exit;
|
||||
FTrackMenuIndex := ATrackIndex;
|
||||
FTrackMenu.Items[0].Enabled := ATrackIndex > 0;
|
||||
FTrackMenu.Items[1].Enabled := ATrackIndex < FProject.TrackCount - 1;
|
||||
FTrackMenu.PopUp(AScreenPos.X, AScreenPos.Y);
|
||||
end;
|
||||
|
||||
procedure TMainForm.TrackMenuUp(Sender: TObject);
|
||||
begin
|
||||
if FTrackMenuIndex <= 0 then
|
||||
Exit;
|
||||
FProject.BeginChange;
|
||||
FProject.MoveTrack(FTrackMenuIndex, FTrackMenuIndex - 1);
|
||||
TimelineStructureChange(nil);
|
||||
SetStatus('Дорожка поднята.');
|
||||
end;
|
||||
|
||||
procedure TMainForm.TrackMenuDown(Sender: TObject);
|
||||
begin
|
||||
if (FTrackMenuIndex < 0) or (FTrackMenuIndex >= FProject.TrackCount - 1) then
|
||||
Exit;
|
||||
FProject.BeginChange;
|
||||
FProject.MoveTrack(FTrackMenuIndex, FTrackMenuIndex + 1);
|
||||
TimelineStructureChange(nil);
|
||||
SetStatus('Дорожка опущена.');
|
||||
end;
|
||||
|
||||
{ Удаление дорожки. С клипами — только с подтверждением: вместе с
|
||||
дорожкой уходит всё, что на ней лежит, а отменить можно только одним
|
||||
шагом назад. }
|
||||
procedure TMainForm.TrackMenuDelete(Sender: TObject);
|
||||
var
|
||||
Track: TTrack;
|
||||
{ Не Name: так называется свойство самой формы, и локальная переменная
|
||||
его перекроет — компилятор ловит, но сообщение неочевидное. }
|
||||
TrackName: string;
|
||||
begin
|
||||
if (FTrackMenuIndex < 0) or (FTrackMenuIndex >= FProject.TrackCount) then
|
||||
Exit;
|
||||
Track := FProject.Tracks[FTrackMenuIndex];
|
||||
TrackName := Track.Name;
|
||||
|
||||
if (Track.Count > 0) and (ThemedMessageDlg('nlve',
|
||||
Format('На дорожке %s %d %s. Удалить её вместе с ними?',
|
||||
[TrackName, Track.Count,
|
||||
Plural(Track.Count, 'клип', 'клипа', 'клипов')]),
|
||||
mtConfirmation, [mbYes, mbNo], 0) <> mrYes) then
|
||||
Exit;
|
||||
|
||||
FProject.BeginChange;
|
||||
FProject.RemoveTrack(FTrackMenuIndex);
|
||||
TimelineStructureChange(nil);
|
||||
SetStatus('Дорожка ' + TrackName + ' удалена.');
|
||||
end;
|
||||
|
||||
{ --- Меню библиотеки --- }
|
||||
|
||||
procedure TMainForm.BuildPoolMenu;
|
||||
|
|
|
|||
|
|
@ -40,12 +40,18 @@ type
|
|||
taFadeIn, // тянем появление за верхний левый угол
|
||||
taFadeOut, // тянем уход за верхний правый угол
|
||||
taRegionIn, // тянем начало отмеченного участка
|
||||
taRegionOut // тянем конец отмеченного участка
|
||||
taRegionOut, // тянем конец отмеченного участка
|
||||
taDragTrack // тянем дорожку за заголовок, меняя порядок
|
||||
);
|
||||
|
||||
{ Что находится под указателем в полосе заголовков дорожек. }
|
||||
THeaderPart = (hpNone, hpBody, hpMute, hpSolo);
|
||||
|
||||
{ Правый щелчок по заголовку дорожки. Само меню строит окно: компонент
|
||||
не знает, что с дорожкой можно делать. }
|
||||
TTrackMenuEvent = procedure(Sender: TObject; ATrackIndex: Integer;
|
||||
const AScreenPos: TPoint) of object;
|
||||
|
||||
{ Файл из списка проекта бросили на монтаж.
|
||||
|
||||
ATrackIndex = -1 означает, что бросили ниже всех дорожек и нужна новая.
|
||||
|
|
@ -125,6 +131,11 @@ type
|
|||
FOnSelectionChange: TNotifyEvent;
|
||||
{ Отмеченный участок подвинули мышью. }
|
||||
FOnRegionChange: TNotifyEvent;
|
||||
{ Правый щелчок по заголовку дорожки: окно покажет своё меню. }
|
||||
FOnTrackMenu: TTrackMenuEvent;
|
||||
{ Дорожка, которую тащат за заголовок. }
|
||||
FDragTrackIndex: Integer;
|
||||
FDragTrackFrom: Integer;
|
||||
FOnMediaDrop: TMediaDropEvent;
|
||||
FOnClipTransform: TClipEvent;
|
||||
FOnTrackAdded: TNotifyEvent;
|
||||
|
|
@ -294,6 +305,8 @@ type
|
|||
write FOnSelectionChange;
|
||||
property OnRegionChange: TNotifyEvent read FOnRegionChange
|
||||
write FOnRegionChange;
|
||||
property OnTrackMenu: TTrackMenuEvent read FOnTrackMenu
|
||||
write FOnTrackMenu;
|
||||
property OnMediaDrop: TMediaDropEvent read FOnMediaDrop write FOnMediaDrop;
|
||||
{ Нажата кнопка кадрирования в углу клипа. }
|
||||
property OnClipTransform: TClipEvent read FOnClipTransform
|
||||
|
|
@ -314,6 +327,11 @@ const
|
|||
SnapPixels = 8;
|
||||
{ Уголок отметки участка: сторона треугольника в точках. }
|
||||
RegionHandleWidth = 9;
|
||||
{ Полоса участка вверху линейки: в ней живут уголки, и протяжка по ней
|
||||
отмечает участок, а не двигает курсор. Так же устроено в монтажных
|
||||
программах: у линейки два этажа, верхний про отметки, нижний про
|
||||
курсор. }
|
||||
RegionBarHeight = 12;
|
||||
|
||||
{ Сдвиг мыши, до которого нажатие считается щелчком, а не перетаскиванием.
|
||||
Совсем без запаса нельзя: рука дрожит, и попасть кнопкой в один и тот же
|
||||
|
|
@ -405,6 +423,8 @@ begin
|
|||
FActiveTrack := 0;
|
||||
FSnapEnabled := True;
|
||||
FPlayheadSnap := True;
|
||||
FDragTrackIndex := -1;
|
||||
FDragTrackFrom := -1;
|
||||
end;
|
||||
|
||||
destructor TTimelineControl.Destroy;
|
||||
|
|
@ -1738,6 +1758,21 @@ begin
|
|||
Exit;
|
||||
end;
|
||||
|
||||
{ Правый щелчок по заголовку — меню дорожки. Удалять и переставлять
|
||||
дорожки больше негде: в главном меню есть только «добавить» и «убрать
|
||||
последнюю», а убрать нужно бывает не последнюю. }
|
||||
if (Button = mbRight) and (X < FHeaderWidth) and (Y >= FRulerHeight) then
|
||||
begin
|
||||
Idx := TrackIndexAt(Y);
|
||||
if (Idx >= 0) and Assigned(FOnTrackMenu) then
|
||||
begin
|
||||
FActiveTrack := Idx;
|
||||
Invalidate;
|
||||
FOnTrackMenu(Self, Idx, ClientToScreen(Point(X, Y)));
|
||||
end;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if Button <> mbLeft then
|
||||
Exit;
|
||||
|
||||
|
|
@ -1763,6 +1798,11 @@ begin
|
|||
hpBody:
|
||||
begin
|
||||
FActiveTrack := Idx;
|
||||
{ Заголовок можно потащить: порядок дорожек — это приоритет, и
|
||||
менять его перетаскиванием привычнее, чем через меню. }
|
||||
FAction := taDragTrack;
|
||||
FDragTrackIndex := Idx;
|
||||
FDragTrackFrom := Idx;
|
||||
Invalidate;
|
||||
if Assigned(FOnActiveTrackChange) then
|
||||
FOnActiveTrackChange(Self);
|
||||
|
|
@ -1781,6 +1821,18 @@ begin
|
|||
1: begin FAction := taRegionIn; Exit; end;
|
||||
2: begin FAction := taRegionOut; Exit; end;
|
||||
end;
|
||||
|
||||
{ Верхний этаж линейки — про участок: протяжка по нему отмечает его
|
||||
заново, даже если отметок не было. Нижний — про курсор. }
|
||||
if (Y < RegionBarHeight) and (FProject <> nil) then
|
||||
begin
|
||||
FAction := taRegionOut;
|
||||
FProject.RegionStart := Max(0, SnapPlayhead(XToTime(X)));
|
||||
FProject.RegionEnd := FProject.RegionStart;
|
||||
Invalidate;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
FAction := taScrub;
|
||||
Playhead := Max(0, SnapPlayhead(XToTime(X)));
|
||||
Exit;
|
||||
|
|
@ -1963,6 +2015,19 @@ begin
|
|||
на стык, соскакивал бы с него от малейшего движения мыши. }
|
||||
Playhead := Max(0, SnapPlayhead(XToTime(X)));
|
||||
|
||||
taDragTrack:
|
||||
if (FProject <> nil) and (FDragTrackIndex >= 0) then
|
||||
begin
|
||||
Idx := TrackIndexAt(Y);
|
||||
if (Idx >= 0) and (Idx <> FDragTrackIndex) then
|
||||
begin
|
||||
FProject.MoveTrack(FDragTrackIndex, Idx);
|
||||
FDragTrackIndex := Idx;
|
||||
FActiveTrack := Idx;
|
||||
Invalidate;
|
||||
end;
|
||||
end;
|
||||
|
||||
taRegionIn:
|
||||
DragRegionEdge(X, True);
|
||||
|
||||
|
|
@ -2247,6 +2312,37 @@ begin
|
|||
{ Shift читается и здесь: курсор по щелчку ставится при отпускании, и
|
||||
решение о прилипании должно быть по клавише, которую держат сейчас. }
|
||||
FSnapSuspended := ssShift in Shift;
|
||||
{ Дорожку переставили — предпросмотр надо пересобрать: порядок дорожек
|
||||
и есть порядок перекрытия. Сообщаем один раз, в конце: во время
|
||||
перетаскивания дорожка проходит через все промежуточные места. }
|
||||
if FAction = taDragTrack then
|
||||
begin
|
||||
FAction := taNone;
|
||||
if (FProject <> nil) and (FDragTrackIndex <> FDragTrackFrom) then
|
||||
begin
|
||||
FProject.Modified := True;
|
||||
StructureChanged;
|
||||
end;
|
||||
FDragTrackIndex := -1;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
{ Отметка участка протяжкой: щелчок без движения снимает отметки — это
|
||||
и есть привычный способ от них избавиться. }
|
||||
if (FAction in [taRegionIn, taRegionOut]) and (FProject <> nil) then
|
||||
begin
|
||||
FAction := taNone;
|
||||
if not FProject.HasRegion then
|
||||
begin
|
||||
FProject.RegionStart := 0;
|
||||
FProject.RegionEnd := 0;
|
||||
end;
|
||||
if Assigned(FOnRegionChange) then
|
||||
FOnRegionChange(Self);
|
||||
Invalidate;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
WasEditing := FAction in [taDragClip, taTrimIn, taTrimOut, taVolume,
|
||||
taFadeIn, taFadeOut];
|
||||
WasTrimIn := FAction = taTrimIn;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue