diff --git a/src/uMainForm.pas b/src/uMainForm.pas index 6d7d9e8..e483f55 100644 --- a/src/uMainForm.pas +++ b/src/uMainForm.pas @@ -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 diff --git a/src/uProject.pas b/src/uProject.pas index f9316d9..bb8b969 100644 --- a/src/uProject.pas +++ b/src/uProject.pas @@ -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