Вид звукового клипа сохраняется, вставки тишины заказываются
Вид клипа «звук» не умел записываться: в файле проекта он превращался в «видео». После открытия такой клип терял форму звука, получал ненужную кнопку кадрирования, а ползунок начинал править прозрачность вместо громкости. Найдено прямо в файле проекта пользователя. Уже записанные проекты чинятся при чтении: клип на звуковой дорожке обязан быть звуковым. Вторая правка: списки кусков звуковых дорожек ссылаются на вставки тишины, но о недостающих никто не сообщал, и они не создавались. Список тогда указывает на несуществующий файл, плеер его не открывает, и дорожка молчит целиком. Теперь недостающие собираются вместе с остальными. Третья: громкости дорожек и сами дорожки собирались разными проходами и могли разъехаться — тогда дорожка с нулевой громкостью заглушила бы чужую. Теперь громкости берутся ровно по тому списку, из которого построены списки кусков. Проверено на проекте пользователя: виды клипов после чтения верные, список звуковой дорожки A2 играет сам по себе (-16 dB), и вся связка с смешиванием тоже (-8.8 dB).
This commit is contained in:
parent
f9e50632aa
commit
e09e61103d
2 changed files with 76 additions and 40 deletions
|
|
@ -79,6 +79,9 @@ type
|
|||
FUpdatingFromPlayer: Boolean;
|
||||
FLastLoadedEdl: string;
|
||||
FLastLoadedAudioEdl: string;
|
||||
{ Списки кусков звуковых дорожек для плеера. }
|
||||
FPreviewAudioUrls: array of string;
|
||||
FPreviewAudioTracks: array of TTrack;
|
||||
FLastHwdec: string;
|
||||
FLastAudioNote: string;
|
||||
FLogFilePath: string;
|
||||
|
|
@ -1331,8 +1334,10 @@ end;
|
|||
|
||||
procedure TMainForm.RebuildPreview;
|
||||
var
|
||||
Segments, AudioSegs: TFlatSegmentArray;
|
||||
Missing: TGenRequests;
|
||||
Segments, AudioSegs, TrackSegs: TFlatSegmentArray;
|
||||
Missing, TrackMissing: TGenRequests;
|
||||
I, J: Integer;
|
||||
TotalLen: Double;
|
||||
begin
|
||||
if (FPlayer = nil) or (not FPlayer.Initialized) then
|
||||
Exit;
|
||||
|
|
@ -1353,6 +1358,39 @@ begin
|
|||
Exit;
|
||||
end;
|
||||
|
||||
{ Каждая звуковая дорожка идёт своим списком кусков: они смешиваются, а
|
||||
не побеждают друг друга. Дыры в каждой дорожке свои, и вставки тишины
|
||||
под них нужны свои — без этого список ссылается на несуществующий
|
||||
файл, плеер его не открывает, и дорожка молчит целиком. }
|
||||
FPreviewAudioUrls := nil;
|
||||
FPreviewAudioTracks := nil;
|
||||
TotalLen := Max(AudibleEnd(FProject, tkVideo), AudibleEnd(FProject, tkAudio));
|
||||
for I := 0 to FProject.TrackCount - 1 do
|
||||
begin
|
||||
if FProject.Tracks[I].Kind <> tkAudio then
|
||||
Continue;
|
||||
if not FProject.TrackAudible(FProject.Tracks[I]) then
|
||||
Continue;
|
||||
if FProject.Tracks[I].Count = 0 then
|
||||
Continue;
|
||||
if not FlattenAudioTrack(FProject, FProject.Tracks[I], TotalLen,
|
||||
TrackSegs, TrackMissing) then
|
||||
Continue;
|
||||
for J := 0 to High(TrackMissing) do
|
||||
begin
|
||||
SetLength(Missing, Length(Missing) + 1);
|
||||
Missing[High(Missing)] := TrackMissing[J];
|
||||
end;
|
||||
SetLength(FPreviewAudioUrls, Length(FPreviewAudioUrls) + 1);
|
||||
FPreviewAudioUrls[High(FPreviewAudioUrls)] := BuildEdlUri(TrackSegs);
|
||||
{ Запоминаем сами дорожки: громкости потом берутся ровно по этому
|
||||
списку. Пересчитывать его отдельным проходом нельзя — стоит одному
|
||||
проходу пропустить дорожку, и громкости съедут на соседние. Тогда
|
||||
дорожка с нулевой громкостью заглушила бы чужую. }
|
||||
SetLength(FPreviewAudioTracks, Length(FPreviewAudioTracks) + 1);
|
||||
FPreviewAudioTracks[High(FPreviewAudioTracks)] := FProject.Tracks[I];
|
||||
end;
|
||||
|
||||
if Length(Missing) > 0 then
|
||||
begin
|
||||
FGenerateBusy := True;
|
||||
|
|
@ -1371,11 +1409,8 @@ end;
|
|||
procedure TMainForm.LoadPreviewEdl(const ASegments, AAudio: TFlatSegmentArray);
|
||||
var
|
||||
Edl, AudioEdl: string;
|
||||
Pos, Total, TotalLen: Double;
|
||||
Pos, Total: Double;
|
||||
WasPaused: Boolean;
|
||||
Urls: array of string;
|
||||
TrackSegs: TFlatSegmentArray;
|
||||
TrackMissing: TGenRequests;
|
||||
I: Integer;
|
||||
begin
|
||||
{ Кадр проекта задаётся до загрузки: mpv должен показывать то, что
|
||||
|
|
@ -1385,32 +1420,11 @@ begin
|
|||
ApplyPreviewFrame;
|
||||
|
||||
Edl := BuildEdlUri(ASegments);
|
||||
{ Каждая звуковая дорожка идёт своим списком кусков.
|
||||
|
||||
Одним общим списком не обойтись: звуковые дорожки смешиваются, а не
|
||||
побеждают друг друга, иначе под голос не положить музыку. Плеер играет
|
||||
одну дорожку за раз, поэтому смесь он собирает своими средствами, а
|
||||
мы даём ему каждую по отдельности. }
|
||||
Urls := nil;
|
||||
TotalLen := Max(AudibleEnd(FProject, tkVideo), AudibleEnd(FProject, tkAudio));
|
||||
for I := 0 to FProject.TrackCount - 1 do
|
||||
begin
|
||||
if FProject.Tracks[I].Kind <> tkAudio then
|
||||
Continue;
|
||||
if not FProject.TrackAudible(FProject.Tracks[I]) then
|
||||
Continue;
|
||||
if FProject.Tracks[I].Count = 0 then
|
||||
Continue;
|
||||
if not FlattenAudioTrack(FProject, FProject.Tracks[I], TotalLen,
|
||||
TrackSegs, TrackMissing) then
|
||||
Continue;
|
||||
SetLength(Urls, Length(Urls) + 1);
|
||||
Urls[High(Urls)] := BuildEdlUri(TrackSegs);
|
||||
end;
|
||||
|
||||
{ Списки кусков звуковых дорожек уже построены при пересборке: там же
|
||||
проверено, что все вставки тишины существуют. }
|
||||
AudioEdl := '';
|
||||
for I := 0 to High(Urls) do
|
||||
AudioEdl := AudioEdl + Urls[I] + '|';
|
||||
for I := 0 to High(FPreviewAudioUrls) do
|
||||
AudioEdl := AudioEdl + FPreviewAudioUrls[I] + '|';
|
||||
|
||||
{ Список кусков запоминаем всегда, даже если строка не изменилась:
|
||||
громкость в неё не входит, а брать её потом неоткуда. }
|
||||
|
|
@ -1431,7 +1445,7 @@ begin
|
|||
перемоткой следом. Иначе mpv успевает начать с нуля, и на каждой
|
||||
правке монтажа слышен и виден кусок не оттуда. }
|
||||
{ Внешний звук назначается до загрузки: он часть того, что открывается. }
|
||||
FPlayer.SetAudioSources(Urls);
|
||||
FPlayer.SetAudioSources(FPreviewAudioUrls);
|
||||
FPlayer.LoadUrl(Edl, Format('start=%s,pause=%s',
|
||||
[SecondsToStr(Pos), BoolToStr(WasPaused, 'yes', 'no')]));
|
||||
FLastLoadedEdl := Edl;
|
||||
|
|
@ -1932,15 +1946,9 @@ begin
|
|||
|
||||
Vols := nil;
|
||||
Key := '';
|
||||
for I := 0 to FProject.TrackCount - 1 do
|
||||
for I := 0 to High(FPreviewAudioTracks) do
|
||||
begin
|
||||
if FProject.Tracks[I].Kind <> tkAudio then
|
||||
Continue;
|
||||
if not FProject.TrackAudible(FProject.Tracks[I]) then
|
||||
Continue;
|
||||
if FProject.Tracks[I].Count = 0 then
|
||||
Continue;
|
||||
Clip := FProject.Tracks[I].ClipAt(ATime);
|
||||
Clip := FPreviewAudioTracks[I].ClipAt(ATime);
|
||||
SetLength(Vols, Length(Vols) + 1);
|
||||
if Clip <> nil then
|
||||
Vols[High(Vols)] := Clip.Volume
|
||||
|
|
|
|||
|
|
@ -226,6 +226,11 @@ type
|
|||
{ Перенос проектов, записанных до разделения звука и картинки: там
|
||||
звук шёл вместе с клипом картинки, и без пары он бы просто пропал. }
|
||||
procedure MigrateAudio;
|
||||
{ Клип на звуковой дорожке обязан быть звуковым. В проектах, записанных
|
||||
до того, как вид клипа научился сохраняться, он остался «видео» — и
|
||||
тогда у клипа пропадает форма звука, появляется лишняя кнопка
|
||||
кадрирования, а ползунок правит прозрачность вместо громкости. }
|
||||
procedure FixClipKinds;
|
||||
{ Кладёт клип и, если в файле есть звук, парный звуковой клип на
|
||||
соответствующую звуковую дорожку. Возвращает клип картинки. }
|
||||
function AddClipPair(ATrack: TTrack; AMedia: TMediaItem;
|
||||
|
|
@ -735,6 +740,25 @@ begin
|
|||
TTrack(FTracks[T]).Sort;
|
||||
end;
|
||||
|
||||
procedure TProject.FixClipKinds;
|
||||
var
|
||||
T, C: Integer;
|
||||
Track: TTrack;
|
||||
begin
|
||||
for T := 0 to FTracks.Count - 1 do
|
||||
begin
|
||||
Track := TTrack(FTracks[T]);
|
||||
for C := 0 to Track.Count - 1 do
|
||||
if Track.Kind = tkAudio then
|
||||
begin
|
||||
if Track[C].Kind <> ckBlack then
|
||||
Track[C].Kind := ckAudio;
|
||||
end
|
||||
else if Track[C].Kind = ckAudio then
|
||||
Track[C].Kind := ckVideo;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TProject.EnsureAudioTracks;
|
||||
var
|
||||
Need, Have, I: Integer;
|
||||
|
|
@ -1050,6 +1074,7 @@ begin
|
|||
case AKind of
|
||||
ckImage: Result := 'image';
|
||||
ckBlack: Result := 'black';
|
||||
ckAudio: Result := 'audio';
|
||||
else
|
||||
Result := 'video';
|
||||
end;
|
||||
|
|
@ -1061,6 +1086,8 @@ begin
|
|||
Result := ckImage
|
||||
else if AValue = 'black' then
|
||||
Result := ckBlack
|
||||
else if AValue = 'audio' then
|
||||
Result := ckAudio
|
||||
else
|
||||
Result := ckVideo;
|
||||
end;
|
||||
|
|
@ -1332,6 +1359,7 @@ begin
|
|||
end;
|
||||
|
||||
MigrateAudio;
|
||||
FixClipKinds;
|
||||
|
||||
Settings := Obj.Find('settings', jtObject) as TJSONObject;
|
||||
if Settings <> nil then
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue