diff --git a/src/uFlatten.pas b/src/uFlatten.pas index 4eea397..c8ebe1d 100644 --- a/src/uFlatten.pas +++ b/src/uFlatten.pas @@ -63,9 +63,17 @@ type Дорожки другого вида в проходе не участвуют вовсе — ни как источники, ни как границы кусков. } +{ AMinLength — до какой длины дотянуть поток. Нужно, чтобы картинка и звук + всегда были одной длины: если выключить все дорожки картинки, поток + картинки опустеет, и без этого предпросмотр погас бы вместе со звуком. + Недостающее заполняется чёрным и тишиной. } function FlattenStream(AProject: TProject; AKind: TTrackKind; + const AMinLength: Double; out ASegments: TFlatSegmentArray; out AMissing: TGenRequests): Boolean; +{ Длина слышимого материала одного вида дорожек. } +function AudibleEnd(AProject: TProject; AKind: TTrackKind): Double; + { Оба потока разом. Список того, что надо создать, общий: чёрные вставки годятся и картинке, и звуку — в них есть и то, и другое. } function FlattenProject(AProject: TProject; @@ -203,7 +211,28 @@ begin end; end; +function AudibleEnd(AProject: TProject; AKind: TTrackKind): Double; +var + T: Integer; + Track: TTrack; +begin + Result := 0; + if AProject = nil then + Exit; + for T := 0 to AProject.TrackCount - 1 do + begin + Track := AProject.Tracks[T]; + if Track.Kind <> AKind then + Continue; + if not AProject.TrackAudible(Track) then + Continue; + if Track.EndTime > Result then + Result := Track.EndTime; + end; +end; + function FlattenStream(AProject: TProject; AKind: TTrackKind; + const AMinLength: Double; out ASegments: TFlatSegmentArray; out AMissing: TGenRequests): Boolean; var Bounds: array of Double; @@ -334,10 +363,13 @@ begin N := 0; for T := 0 to AProject.TrackCount - 1 do Inc(N, AProject.Tracks[T].Count * 2); - SetLength(Bounds, N + 2); + SetLength(Bounds, N + 3); BoundCount := 0; AddBoundary(Bounds, BoundCount, 0); + { Конец задан снаружи: оба потока должны быть одной длины. } + if AMinLength > 0 then + AddBoundary(Bounds, BoundCount, AMinLength); for T := 0 to AProject.TrackCount - 1 do begin Track := AProject.Tracks[T]; @@ -600,15 +632,20 @@ function FlattenProject(AProject: TProject; var MissA: TGenRequests; I: Integer; + Total: Double; begin AAudio := nil; MissA := nil; - Result := FlattenStream(AProject, tkVideo, AVideo, AMissing); + { Длина берётся по обоим видам дорожек сразу: иначе выключенная + картинка укоротила бы ролик, хотя звук ещё идёт. } + Total := Max(AudibleEnd(AProject, tkVideo), AudibleEnd(AProject, tkAudio)); + if Total <= 0 then + Exit; + + Result := FlattenStream(AProject, tkVideo, Total, AVideo, AMissing); if not Result then Exit; - { Звук уплощается всегда, даже если звуковых дорожек нет: тогда выйдет - один кусок тишины на всю длину, и это правильный ответ. } - FlattenStream(AProject, tkAudio, AAudio, MissA); + FlattenStream(AProject, tkAudio, Total, AAudio, MissA); for I := 0 to High(MissA) do begin diff --git a/src/uMpvPlayer.pas b/src/uMpvPlayer.pas index 2545eea..3ab76e5 100644 --- a/src/uMpvPlayer.pas +++ b/src/uMpvPlayer.pas @@ -81,6 +81,13 @@ type procedure PumpEvents(Sender: TObject); procedure HandlePropertyChange(AProp: Pmpv_event_property); procedure HandleLogMessage(AMsg: Pmpv_event_log_message); + { Выбирает внешнюю звуковую дорожку, если она есть. + + Без этого mpv играет звук, встроенный в саму картинку: внешний файл + он лишь добавляет ещё одной дорожкой, а выбирает по своему + усмотрению. Получалось, что звук слышен там, где на монтаже его нет + вовсе — он приходил из видеофайла, а не с звуковой дорожки. } + procedure SelectExternalAudio; function SetOption(const AName, AValue: string): Integer; public constructor Create(AVideoParent: TWinControl); @@ -737,6 +744,38 @@ begin end; end; +procedure TMpvPlayer.SelectExternalAudio; +var + Count, I: Integer; + Kind, Ext, Id: string; +begin + if FHandle = nil then + Exit; + + { Своего звука нет — пусть играет как есть. } + if FAudioSource = '' then + Exit; + + Count := StrToIntDef(GetPropString('track-list/count'), 0); + for I := 0 to Count - 1 do + begin + Kind := GetPropString(Format('track-list/%d/type', [I])); + if Kind <> 'audio' then + Continue; + Ext := GetPropString(Format('track-list/%d/external', [I])); + if (Ext <> 'yes') and (Ext <> 'true') then + Continue; + Id := GetPropString(Format('track-list/%d/id', [I])); + if Id <> '' then + mpv_set_property_string(FHandle, 'aid', PAnsiChar(Id)); + Exit; + end; + + { Внешней дорожки не нашлось — значит наш список кусков не открылся. + Играть вместо него звук из картинки нельзя: на монтаже его там нет. } + mpv_set_property_string(FHandle, 'aid', 'no'); +end; + procedure TMpvPlayer.HandleLogMessage(AMsg: Pmpv_event_log_message); begin if Assigned(FOnLog) then @@ -766,6 +805,8 @@ begin HandlePropertyChange(Pmpv_event_property(Ev^.data)); MPV_EVENT_LOG_MESSAGE: HandleLogMessage(Pmpv_event_log_message(Ev^.data)); + MPV_EVENT_FILE_LOADED: + SelectExternalAudio; MPV_EVENT_SHUTDOWN: begin FPump.Enabled := False; diff --git a/tests/nlvetool.lpr b/tests/nlvetool.lpr index 03ea9c4..c9dc794 100644 --- a/tests/nlvetool.lpr +++ b/tests/nlvetool.lpr @@ -564,7 +564,7 @@ begin try Proj.AddClip(Proj.Tracks[0], Media, 10, 5, 0); Proj.AddClip(Proj.Tracks[0], Media, 15, 5, 5); - FlattenStream(Proj, tkVideo, Segs, Missing); + FlattenStream(Proj, tkVideo, 0, Segs, Missing); DumpSegments(Segs); Check(Length(Segs) = 1, 'склеились в один кусок'); if Length(Segs) = 1 then @@ -583,7 +583,7 @@ begin try Proj.AddClip(Proj.Tracks[0], Media, 100, 5, 0); Proj.AddClip(Proj.Tracks[0], Media, 200, 5, 5); - FlattenStream(Proj, tkVideo, Segs, Missing); + FlattenStream(Proj, tkVideo, 0, Segs, Missing); DumpSegments(Segs); Check(Length(Segs) = 2, 'остались двумя кусками'); finally @@ -598,7 +598,7 @@ begin { Дорожка 0 — верхняя, она побеждает. } Proj.AddClip(Proj.Tracks[1], Media, 0, 30, 0); Proj.AddClip(Proj.Tracks[0], Media, 100, 10, 10); - FlattenStream(Proj, tkVideo, Segs, Missing); + FlattenStream(Proj, tkVideo, 0, Segs, Missing); DumpSegments(Segs); Check(Length(Segs) = 3, 'получилось три куска'); if Length(Segs) = 3 then @@ -622,7 +622,7 @@ begin try Proj.AddClip(Proj.Tracks[0], Media, 0, 5, 3); Proj.AddClip(Proj.Tracks[0], Media, 50, 5, 12); - FlattenStream(Proj, tkVideo, Segs, Missing); + FlattenStream(Proj, tkVideo, 0, Segs, Missing); DumpSegments(Segs); Check(Length(Segs) = 4, 'четыре куска: чёрное, видео, чёрное, видео'); if Length(Segs) = 4 then @@ -652,7 +652,7 @@ begin try Proj.AddClip(Proj.Tracks[1], Media, 0, 10, 0); Proj.AddClip(Proj.Tracks[0], Media, 200, 10, 0); - FlattenStream(Proj, tkVideo, Segs, Missing); + FlattenStream(Proj, tkVideo, 0, Segs, Missing); DumpSegments(Segs); Check(Length(Segs) = 1, 'остался один кусок'); if Length(Segs) = 1 then @@ -669,7 +669,7 @@ begin Proj.AddClip(Proj.Tracks[1], Media, 0, 10, 0); Proj.AddClip(Proj.Tracks[0], Media, 200, 10, 0); Proj.Tracks[0].Muted := True; - FlattenStream(Proj, tkVideo, Segs, Missing); + FlattenStream(Proj, tkVideo, 0, Segs, Missing); DumpSegments(Segs); Check(Length(Segs) = 1, 'остался один кусок'); if Length(Segs) = 1 then @@ -689,7 +689,7 @@ begin Proj.Tracks[0][0].EdgeIn := edPrecise; Proj.RippleMode := rmAllTracks; Proj.RippleEnabled := True; - FlattenStream(Proj, tkVideo, Segs, Missing); + FlattenStream(Proj, tkVideo, 0, Segs, Missing); Check(Length(Segs) = 3, 'до записи три куска'); Check(Proj.SaveToFile(TempFile, Err), 'записан: ' + Err); finally @@ -709,7 +709,7 @@ begin Check(Proj.RippleEnabled, 'состояние схлопывания сохранилось'); if Proj.TrackCount = 2 then Check(Proj.Tracks[0][0].EdgeIn = edPrecise, 'признак точного реза сохранился'); - FlattenStream(Proj, tkVideo, Segs, Missing); + FlattenStream(Proj, tkVideo, 0, Segs, Missing); DumpSegments(Segs); Check(Length(Segs) = 3, 'после чтения снова три куска'); if Length(Segs) = 3 then