// Copyright Epic Games, Inc. All Rights Reserved. #include "MovieSceneTranslator.h" #include "MovieScene.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" #include "Modules/ModuleManager.h" #include "AssetRegistry/AssetData.h" #include "LevelSequence.h" #include "Tracks/MovieSceneAudioTrack.h" #include "Sections/MovieSceneAudioSection.h" #include "Tracks/MovieSceneCinematicShotTrack.h" #include "Sections/MovieSceneCinematicShotSection.h" #include "MovieSceneTimeHelpers.h" #include "AssetRegistry/AssetRegistryModule.h" #include "Sound/SoundWave.h" #include "Sound/SoundCue.h" #include "EditorFramework/AssetImportData.h" #define LOCTEXT_NAMESPACE "MovieSceneTranslator" namespace { bool SupportMultipleAudioTracks() { return true; } bool AudioSectionIsSoundWave(const UMovieSceneAudioSection* InAudioSection) { USoundBase* SoundBase = InAudioSection->GetSound(); if (SoundBase == nullptr) { return false; } return (SoundBase->IsA()); } } FMovieSceneExportData::FMovieSceneExportData(const UMovieScene* InMovieScene, FFrameRate InFrameRate, uint32 InResX, uint32 InResY, int32 InHandleFrames, FString InSaveFilename, TSharedPtr InContext, FString InMovieExtension) { if (InMovieScene == nullptr) { bExportDataIsValid = false; return; } ExportContext = InContext; FrameRate = InFrameRate; ResX = InResX; ResY = InResY; HandleFrames = InHandleFrames; SaveFilename = InSaveFilename; MovieExtension = InMovieExtension; // preferred sample rate in UE DefaultAudioSampleRate = 44100; // all audio in UE is has depth 16 DefaultAudioDepth = 16; bExportDataIsValid = ConstructData(InMovieScene); } FMovieSceneExportData::FMovieSceneExportData() { bExportDataIsValid = false; } FMovieSceneExportData::~FMovieSceneExportData() { } bool FMovieSceneExportData::IsExportDataValid() const { return bExportDataIsValid; } bool FMovieSceneExportData::ConstructData(const UMovieScene* InMovieScene) { if (InMovieScene == nullptr) { return false; } SaveFilenamePath = FPaths::GetPath(SaveFilename); if (FPaths::IsRelative(SaveFilenamePath)) { SaveFilenamePath = FPaths::ConvertRelativePathToFull(SaveFilenamePath); } return ConstructMovieSceneData(InMovieScene); } bool FMovieSceneExportData::ConstructMovieSceneData(const UMovieScene* InMovieScene) { if (InMovieScene == nullptr) { return false; } MovieSceneData = MakeShared(); FFrameRate TickResolution = InMovieScene->GetTickResolution(); TRange PlaybackRange = InMovieScene->GetPlaybackRange(); if (PlaybackRange.HasLowerBound()) { MovieSceneData->PlaybackRangeStartFrame = ConvertFrameTime(PlaybackRange.GetLowerBoundValue(), TickResolution, FrameRate).CeilToFrame(); } else { UE_LOG(LogMovieScene, Error, TEXT("Invalid condition: Movie scene playback range has infinite lower bound.")); return false; } if (PlaybackRange.HasUpperBound()) { MovieSceneData->PlaybackRangeEndFrame = ConvertFrameTime(PlaybackRange.GetUpperBoundValue(), TickResolution, FrameRate).CeilToFrame(); } else { UE_LOG(LogMovieScene, Error, TEXT("Invalid condition: Movie scene playback range has infinite upper bound.")); return false; } MovieSceneData->Name = InMovieScene->GetOuter()->GetName(); MovieSceneData->Path = InMovieScene->GetOuter()->GetPathName(); MovieSceneData->TickResolution = TickResolution; MovieSceneData->Duration = ConvertFrameTime(UE::MovieScene::DiscreteSize(PlaybackRange), TickResolution, FrameRate).FrameNumber.Value; bool bFoundCinematicTrack = false; // sort audio tracks TMap> AudioTrackMap; const TArray Tracks = InMovieScene->GetTracks(); for (UMovieSceneTrack* Track : Tracks) { if (!bFoundCinematicTrack && Track->IsA(UMovieSceneCinematicShotTrack::StaticClass())) { const UMovieSceneCinematicShotTrack* CinematicTrack = Cast(Track); if (CinematicTrack == nullptr || !ConstructCinematicData(InMovieScene, CinematicTrack)) { return false; } bFoundCinematicTrack = true; } else if (Track->IsA(UMovieSceneAudioTrack::StaticClass())) { const UMovieSceneAudioTrack* AudioTrack = Cast(Track); if (AudioTrack == nullptr || !ConstructAudioData(InMovieScene, AudioTrack, AudioTrackMap)) { return false; } } } // sort the audio tracks by their sorting index and add to the AudioTracks array if (AudioTrackMap.Num() > 0) { AudioTrackMap.KeySort([](int32 A, int32 B) { return A < B; // sort keys in order }); for (auto& Elem : AudioTrackMap) { if (Elem.Value.IsValid()) { MovieSceneData->AudioData.Add(Elem.Value); if (!SupportMultipleAudioTracks()) { break; } } } } return true; } bool FMovieSceneExportData::ConstructCinematicData(const UMovieScene* InMovieScene, const UMovieSceneCinematicShotTrack* InCinematicTrack) { if (InMovieScene == nullptr || !MovieSceneData.IsValid()) { return false; } TSharedPtr CinematicData = MakeShared(); CinematicData->MovieSceneTrack = InCinematicTrack; MovieSceneData->CinematicData = CinematicData; // Make a list of shotnames and how many duplicates for (UMovieSceneSection* Section : InCinematicTrack->GetAllSections()) { const UMovieSceneCinematicShotSection* CinematicSection = Cast(Section); if (CinematicSection != nullptr && CinematicSection->GetSequence() != nullptr) { ++DuplicateShotNameCounts.FindOrAdd(CinematicSection->GetShotDisplayName(), 0); } } // Remove entries for shotnames which do not have duplicates TArray ShotNames; DuplicateShotNameCounts.GetKeys(ShotNames); for (FString ShotName : ShotNames) { int32& Count = DuplicateShotNameCounts[ShotName]; if (Count > 1) { // Reset duplicate count to zero, so we can increment as we export Count = 0; } else { // No dupes, so remove it DuplicateShotNameCounts.Remove(ShotName); } } // Construct sections & create track row index array TArray CinematicTrackRowIndices; for (UMovieSceneSection* Section : InCinematicTrack->GetAllSections()) { const UMovieSceneCinematicShotSection* CinematicSection = Cast(Section); if (CinematicSection != nullptr && CinematicSection->GetSequence() != nullptr) { if (!ConstructCinematicSectionData(InMovieScene, CinematicData, CinematicSection)) { return false; } int32 RowIndex = CinematicSection->GetRowIndex(); if (RowIndex >= 0) { CinematicTrackRowIndices.AddUnique(RowIndex); } } } // Construct tracks and point to sections CinematicTrackRowIndices.Sort(); for (int32 CinematicTrackRowIndex : CinematicTrackRowIndices) { if (!ConstructCinematicTrackData(InMovieScene, CinematicData, CinematicTrackRowIndex)) { return false; } } return true; } bool FMovieSceneExportData::ConstructCinematicTrackData(const UMovieScene* InMovieScene, TSharedPtr InCinematicData, int32 InRowIndex) { if (InMovieScene == nullptr || !InCinematicData.IsValid() || !MovieSceneData.IsValid() || !MovieSceneData->CinematicData.IsValid()) { return false; } TSharedPtr TrackData = MakeShared(); TrackData->RowIndex = InRowIndex; MovieSceneData->CinematicData->CinematicTracks.Add(TrackData); for (TSharedPtr Section : InCinematicData->CinematicSections) { if (Section.IsValid() && Section->RowIndex == InRowIndex) { TrackData->CinematicSections.Add(Section); } } return true; } bool FMovieSceneExportData::ConstructAudioData(const UMovieScene* InMovieScene, const UMovieSceneAudioTrack* InAudioTrack, TMap>& InAudioTrackMap) { if (InMovieScene == nullptr || !MovieSceneData.IsValid()) { return false; } TSharedPtr TrackData = MakeShared(); TrackData->MovieSceneTrack = InAudioTrack; InAudioTrackMap.Add(InAudioTrack->GetSortingOrder(), TrackData); // Construct sections & create track row index array TArray AudioTrackRowIndices; TArray SectionPathNames; for (UMovieSceneSection* Section : InAudioTrack->GetAudioSections()) { const UMovieSceneAudioSection* AudioSection = Cast(Section); if (AudioSection == nullptr) { continue; } // skip duplicate sections if (SectionPathNames.Num() > 0 && SectionPathNames.Contains(AudioSection->GetPathName())) { continue; } if (AudioSectionIsSoundWave(AudioSection)) { if (!ConstructAudioSectionData(InMovieScene, TrackData, AudioSection)) { return false; } int32 RowIndex = AudioSection->GetRowIndex(); if (RowIndex >= 0) { AudioTrackRowIndices.AddUnique(RowIndex); } } SectionPathNames.Add(AudioSection->GetPathName()); } // Construct tracks and point to sections AudioTrackRowIndices.Sort(); for (int32 AudioTrackRowIndex : AudioTrackRowIndices) { if (!ConstructAudioData(InMovieScene, TrackData, AudioTrackRowIndex)) { return false; } } return true; } bool FMovieSceneExportData::ConstructAudioData(const UMovieScene* InMovieScene, TSharedPtr InAudioData, int32 InRowIndex) { if (InMovieScene == nullptr || !InAudioData.IsValid()) { return false; } TSharedPtr TrackData = MakeShared(); TrackData->SampleRate = DefaultAudioSampleRate; TrackData->RowIndex = InRowIndex; InAudioData->AudioTracks.Add(TrackData); for (TSharedPtr Section : InAudioData->AudioSections) { if (Section.IsValid() && Section->RowIndex == InRowIndex) { TrackData->AudioSections.Add(Section); } } return true; } bool FMovieSceneExportData::ConstructCinematicSectionData(const UMovieScene* InMovieScene, TSharedPtr InCinematicData, const UMovieSceneCinematicShotSection* InCinematicSection) { if (InMovieScene == nullptr || !InCinematicData.IsValid() || InCinematicSection == nullptr) { return false; } TSharedPtr SectionData = MakeShared(); InCinematicData->CinematicSections.Add(SectionData); SectionData->DisplayName = InCinematicSection->GetShotDisplayName(); if (DuplicateShotNameCounts.Contains(SectionData->DisplayName)) { // If this shotname is a duplicate, append a number to make it unique int32 Count = ++DuplicateShotNameCounts[SectionData->DisplayName]; SectionData->DisplayName.Append(FString::Format(TEXT("({0})"), { Count })); } SectionData->SourceFilename = SectionData->DisplayName + MovieExtension; SectionData->SourceFilePath = TEXT(""); ConstructSectionData(InMovieScene, SectionData, InCinematicSection, EMovieSceneTranslatorSectionType::Cinematic, SectionData->DisplayName); return true; } bool FMovieSceneExportData::ConstructAudioSectionData(const UMovieScene* InMovieScene, TSharedPtr InAudioData, const UMovieSceneAudioSection* InAudioSection) { if (InMovieScene == nullptr || !InAudioData.IsValid() || InAudioSection == nullptr) { return false; } USoundBase* SoundBase = InAudioSection->GetSound(); if (SoundBase == nullptr || !SoundBase->IsA()) { return false; } USoundWave* SoundWave = nullptr; SoundWave = Cast(SoundBase); if (SoundWave == nullptr || SoundWave->AssetImportData == nullptr) { return false; } TSharedPtr SectionData = MakeShared(); InAudioData->AudioSections.Add(SectionData); TArray Filenames = SoundWave->AssetImportData->ExtractFilenames(); if (Filenames.Num() < 1) { return false; } int32 SampleRate = SoundWave->GetSampleRateForCurrentPlatform(); if (SampleRate != 48000 && SampleRate != 44100 && SampleRate != 32000) { // @todo - warning about invalid sample rate SampleRate = 44100; } SectionData->DisplayName = SoundWave->GetName(); SectionData->SourceFilename = FPaths::GetCleanFilename(Filenames[0]); SectionData->SourceFilePath = FPaths::GetPath(Filenames[0]); SectionData->Depth = GetDefaultAudioDepth(); SectionData->SampleRate = SampleRate; SectionData->NumChannels = SoundWave->NumChannels; ConstructSectionData(InMovieScene, SectionData, InAudioSection, EMovieSceneTranslatorSectionType::Audio, TEXT("")); return true; } bool FMovieSceneExportData::ConstructSectionData(const UMovieScene* InMovieScene, TSharedPtr InSectionData, const UMovieSceneSection* InSection, EMovieSceneTranslatorSectionType InSectionType, const FString& InSectionDisplayName) { if (InMovieScene == nullptr || !MovieSceneData.IsValid() || InSection == nullptr || !InSectionData.IsValid()) { return false; } InSectionData->MovieSceneSection = InSection; InSectionData->RowIndex = InSection->GetRowIndex(); if (InSection->HasStartFrame()) { FFrameTime InclusiveStartFrame = InSection->GetInclusiveStartFrame(); FFrameTime ConvertedStartFrame = ConvertFrameTime(InclusiveStartFrame, MovieSceneData->TickResolution, FrameRate); InSectionData->StartFrame = ConvertedStartFrame.CeilToFrame(); if (ExportContext.IsValid() && InSectionType == EMovieSceneTranslatorSectionType::Cinematic && ConvertedStartFrame.GetSubFrame() > 0.0f) { ExportContext->AddMessage(EMessageSeverity::Warning, FText::Format(LOCTEXT("SectionStartNotDivisByDisplayRate", "Section '{0}' starts on tick {1} which is not evenly divisible by the display rate {2}. Enable snapping and adjust the start or edit the section properties to ensure it lands evenly on a whole frame."), FText::FromString(InSectionDisplayName), FText::AsNumber(InclusiveStartFrame.CeilToFrame().Value), FText::FromString(FString::SanitizeFloat(FrameRate.AsDecimal())))); } } else { InSectionData->StartFrame = FFrameNumber(0); if (ExportContext.IsValid() && InSectionType == EMovieSceneTranslatorSectionType::Cinematic) { ExportContext->AddMessage(EMessageSeverity::Warning, FText::Format(LOCTEXT("SectionHasNoStartFrame", "Section '{0}' has no start frame. Start frame will default to 0."), FText::FromString(InSectionDisplayName))); } } if (InSection->HasEndFrame()) { FFrameTime ExclusiveEndFrame = InSection->GetExclusiveEndFrame(); FFrameTime ConvertedEndFrame = ConvertFrameTime(ExclusiveEndFrame, MovieSceneData->TickResolution, FrameRate); InSectionData->EndFrame = ConvertedEndFrame.CeilToFrame(); if (ExportContext.IsValid() && InSectionType == EMovieSceneTranslatorSectionType::Cinematic && ConvertedEndFrame.GetSubFrame() > 0.0f) { ExportContext->AddMessage(EMessageSeverity::Warning, FText::Format(LOCTEXT("SectionEndNotDivisByDisplayRate", "Section '{0}' ends on tick {1} which is not evenly divisible by the display rate {2}. Enable snapping and adjust the end or edit the section properties to ensure it lands evenly on a whole frame."), FText::FromString(InSectionDisplayName), FText::FromString(FString::FromInt(ExclusiveEndFrame.CeilToFrame().Value)), FText::FromString(FString::SanitizeFloat(FrameRate.AsDecimal())))); } } else { InSectionData->EndFrame = MovieSceneData->PlaybackRangeEndFrame; if (ExportContext.IsValid() && InSectionType == EMovieSceneTranslatorSectionType::Cinematic) { ExportContext->AddMessage(EMessageSeverity::Warning, FText::Format(LOCTEXT("SectionHasNoEndFrame", "Section '{0}' has no end frame. End frame will default to playback range end."), FText::FromString(InSectionDisplayName))); } } // @todo handle intersection with playback range? TRange PlaybackRange = InMovieScene->GetPlaybackRange(); TRange EditRange = InSection->GetRange(); TRange Intersection = TRange::Intersection(PlaybackRange, EditRange); InSectionData->bWithinPlaybackRange = EditRange.Overlaps(PlaybackRange); InSectionData->bEnabled = true; return true; } bool FMovieSceneExportData::FindAudioSections(const FString& InSoundPathName, TArray>& OutFoundSections) const { if (!MovieSceneData.IsValid()) { return false; } for (TSharedPtr AudioData : MovieSceneData->AudioData) { for (TSharedPtr AudioSectionData : AudioData->AudioSections) { if (!AudioSectionData.IsValid() || AudioSectionData->MovieSceneSection == nullptr) { continue; } const UMovieSceneAudioSection* AudioSection = Cast(AudioSectionData->MovieSceneSection); if (AudioSection == nullptr) { continue; } USoundBase* Sound = AudioSection->GetSound(); if (Sound == nullptr || !Sound->IsA()) { continue; } FString SoundPathName = Sound->GetPathName(); if (SoundPathName == InSoundPathName) { OutFoundSections.Add(AudioSectionData); } } } return true; } FString FMovieSceneExportData::GetFilename() const { return SaveFilename; } FString FMovieSceneExportData::GetFilenamePath() const { return SaveFilenamePath; } FString FMovieSceneExportData::GetMovieExtension() const { return MovieExtension; } FFrameRate FMovieSceneExportData::GetFrameRate() const { return FrameRate; } uint32 FMovieSceneExportData::GetResX() const { return ResX; } uint32 FMovieSceneExportData::GetResY() const { return ResY; } uint32 FMovieSceneExportData::GetNearestWholeFrameRate() const { if (GetFrameRateIsNTSC()) { double Rate = FrameRate.AsDecimal(); return static_cast(FMath::FloorToDouble(Rate + 0.5)); } return static_cast(FrameRate.AsDecimal()); } bool FMovieSceneExportData::GetFrameRateIsNTSC() const { float FractionalPart = FMath::Frac(FrameRate.AsDecimal()); return (!FMath::IsNearlyZero(FractionalPart)); } int32 FMovieSceneExportData::GetHandleFrames() const { return HandleFrames; } int32 FMovieSceneExportData::GetDefaultAudioSampleRate() const { return DefaultAudioSampleRate; } int32 FMovieSceneExportData::GetDefaultAudioDepth() const { return DefaultAudioDepth; } FMovieSceneImportData::FMovieSceneImportData(UMovieScene* InMovieScene, TSharedPtr InContext) { if (InMovieScene == nullptr) { return; } ImportContext = InContext; MovieSceneData = ConstructMovieSceneData(InMovieScene); } FMovieSceneImportData::FMovieSceneImportData() : MovieSceneData(nullptr) { } FMovieSceneImportData::~FMovieSceneImportData() { } bool FMovieSceneImportData::IsImportDataValid() const { return MovieSceneData.IsValid(); } TSharedPtr FMovieSceneImportData::FindCinematicSection(const FString& InSectionPathName) { TSharedPtr CinematicData = GetCinematicData(false); if (!CinematicData.IsValid()) { return nullptr; } for (TSharedPtr CinematicSection : CinematicData->CinematicSections) { UMovieSceneCinematicShotSection* CinematicShotSection = CinematicSection->CinematicSection; if (CinematicShotSection != nullptr) { UMovieSceneSequence* ShotSequence = CinematicShotSection->GetSequence(); FString ShotSectionPathName = CinematicShotSection->GetPathName(); if (ShotSequence != nullptr && ShotSectionPathName == InSectionPathName) { return CinematicSection; } } } return nullptr; } /** Create cinematic section */ TSharedPtr FMovieSceneImportData::CreateCinematicSection(FString InName, int32 InRow, FFrameRate InFrameRate, FFrameNumber InStartFrame, FFrameNumber InEndFrame, FFrameNumber InStartOffsetFrame) { if (!MovieSceneData.IsValid() || MovieSceneData->MovieScene == nullptr) { return nullptr; } TSharedPtr CinematicData = GetCinematicData(true); if (!CinematicData.IsValid() || CinematicData->MovieSceneTrack == nullptr) { return nullptr; } UMovieSceneCinematicShotTrack* CinematicTrack = Cast(CinematicData->MovieSceneTrack); if (CinematicTrack == nullptr) { return nullptr; } UMovieSceneSequence* SequenceToAdd = nullptr; FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); // Collect a full list of assets with the specified class TArray AssetDataArray; AssetRegistryModule.Get().GetAssetsByClass(ULevelSequence::StaticClass()->GetClassPathName(), AssetDataArray); for (FAssetData AssetData : AssetDataArray) { if (AssetData.AssetName == *InName) { SequenceToAdd = Cast(AssetData.GetAsset()); break; } } // If exact match wasn't found, look for the base filename (in case the clip name comes with an extension) if (SequenceToAdd == nullptr) { for (FAssetData AssetData : AssetDataArray) { if (AssetData.AssetName == *FPaths::GetBaseFilename(InName)) { SequenceToAdd = Cast(AssetData.GetAsset()); break; } } } if (SequenceToAdd == nullptr) { UE_LOG(LogMovieScene, Error, TEXT("Can't find valid level sequence asset to map clip to: (%s)"), *InName); } // both FCP XML and Sequencer have inclusive start frame, exclusive end frame FFrameRate TickResolution = MovieSceneData->MovieScene->GetTickResolution(); FFrameNumber StartFrame = ConvertFrameTime(InStartFrame, InFrameRate, TickResolution).RoundToFrame(); FFrameNumber StartOffsetFrame = ConvertFrameTime(InStartOffsetFrame, InFrameRate, TickResolution).RoundToFrame(); FFrameNumber EndFrame = ConvertFrameTime(InEndFrame, InFrameRate, TickResolution).RoundToFrame(); int32 Duration = (EndFrame - StartFrame).Value; CinematicTrack->Modify(); UMovieSceneCinematicShotSection* Section = Cast(CinematicTrack->AddSequence(SequenceToAdd, StartFrame, Duration)); if (Section == nullptr) { return nullptr; } Section->Modify(); Section->SetRowIndex(InRow); Section->Parameters.StartFrameOffset = StartOffsetFrame.Value; Section->SetRange(TRange(StartFrame, EndFrame)); TSharedPtr SectionData = ConstructCinematicSectionData(Section); CinematicData->CinematicSections.Add(SectionData); for (TSharedPtr TrackData : CinematicData->CinematicTracks) { if (InRow == TrackData->RowIndex) { TrackData->CinematicSections.Add(SectionData); } } return SectionData; } bool FMovieSceneImportData::SetCinematicSection(TSharedPtr InSection, int32 InRow, FFrameRate InFrameRate, FFrameNumber InStartFrame, FFrameNumber InEndFrame, TOptional InStartOffsetFrame) { if (!InSection.IsValid() || InSection->CinematicSection == nullptr) { return false; } FFrameRate TickResolution = MovieSceneData->MovieScene->GetTickResolution(); FFrameNumber StartFrame = ConvertFrameTime(InStartFrame, InFrameRate, TickResolution).GetFrame(); FFrameNumber EndFrame = ConvertFrameTime(InEndFrame, InFrameRate, TickResolution).GetFrame(); InSection->CinematicSection->Modify(); if (InStartOffsetFrame.IsSet()) { FFrameNumber StartOffsetFrame = ConvertFrameTime(InStartOffsetFrame.GetValue(), InFrameRate, TickResolution).GetFrame(); InSection->CinematicSection->Parameters.StartFrameOffset = StartOffsetFrame; } InSection->CinematicSection->SetRange(TRange(StartFrame, EndFrame)); if (InRow != InSection->CinematicSection->GetRowIndex()) { InSection->CinematicSection->SetRowIndex(InRow); } return true; } TSharedPtr FMovieSceneImportData::FindAudioSection(const FString& InSectionPathName, TSharedPtr& OutAudioData) { if (!MovieSceneData.IsValid()) { OutAudioData = nullptr; return nullptr; } for (TSharedPtr AudioData : MovieSceneData->AudioData) { if (!AudioData.IsValid()) { continue; } for (TSharedPtr AudioSectionData : AudioData->AudioSections) { if (AudioSectionData.IsValid() && AudioSectionData->AudioSection != nullptr) { FString SectionName = AudioSectionData->AudioSection->GetPathName(); if (SectionName == InSectionPathName) { OutAudioData = AudioData; return AudioSectionData; } } } } OutAudioData = nullptr; return nullptr; } /** Create audio section */ TSharedPtr FMovieSceneImportData::CreateAudioSection(FString InFilenameOrAssetPathName, bool bIsPathName, TSharedPtr InData, int32 InRow, FFrameRate InFrameRate, FFrameNumber InStartFrame, FFrameNumber InEndFrame, FFrameNumber InStartOffsetFrame) { if (!MovieSceneData.IsValid() || MovieSceneData->MovieScene == nullptr || !InData.IsValid() || InData->MovieSceneTrack == nullptr) { return nullptr; } UMovieSceneAudioTrack* AudioTrack = Cast(InData->MovieSceneTrack); if (AudioTrack == nullptr) { return nullptr; } USoundWave* SoundToAdd = nullptr; FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); // Collect a full list of assets with the specified class TArray AssetDataArray; AssetRegistryModule.Get().GetAssetsByClass(USoundWave::StaticClass()->GetClassPathName(), AssetDataArray); for (FAssetData AssetData : AssetDataArray) { USoundWave* SoundWaveAsset = Cast(AssetData.GetAsset()); if (SoundWaveAsset == nullptr) { continue; } if (bIsPathName) { if (InFilenameOrAssetPathName == SoundWaveAsset->GetPathName()) { SoundToAdd = SoundWaveAsset; break; } } else { if (SoundWaveAsset->AssetImportData == nullptr) { continue; } TArray Filenames = SoundWaveAsset->AssetImportData->ExtractFilenames(); if (Filenames.Num() < 1) { continue; } FString Filename = FPaths::GetCleanFilename(Filenames[0]); if (Filename == InFilenameOrAssetPathName) { SoundToAdd = SoundWaveAsset; break; } } } if (SoundToAdd == nullptr) { UE_LOG(LogMovieScene, Error, TEXT("Can't find valid asset asset to map clip to: (%s)"), *InFilenameOrAssetPathName); return nullptr; } // both FCP XML and Sequencer have inclusive start frame, exclusive end frame FFrameRate TickResolution = MovieSceneData->MovieScene->GetTickResolution(); FFrameNumber StartFrame = ConvertFrameTime(InStartFrame, InFrameRate, TickResolution).RoundToFrame(); FFrameNumber StartOffsetFrame = ConvertFrameTime(InStartOffsetFrame, InFrameRate, TickResolution).RoundToFrame(); FFrameNumber EndFrame = ConvertFrameTime(InEndFrame, InFrameRate, TickResolution).RoundToFrame(); int32 Duration = (EndFrame - StartFrame).Value; AudioTrack->Modify(); UMovieSceneAudioSection* AudioSection = Cast(AudioTrack->AddNewSoundOnRow(SoundToAdd, StartFrame, InRow)); if (AudioSection == nullptr) { return nullptr; } AudioSection->Modify(); AudioSection->SetRowIndex(InRow); AudioSection->SetStartOffset(StartOffsetFrame.Value); AudioSection->SetRange(TRange(StartFrame, EndFrame)); TSharedPtr AudioSectionData = ConstructAudioSectionData(AudioSection); InData->AudioSections.Add(AudioSectionData); for (TSharedPtr TrackData : InData->AudioTracks) { if (InRow == TrackData->RowIndex) { TrackData->AudioSections.Add(AudioSectionData); } } return AudioSectionData; } bool FMovieSceneImportData::SetAudioSection(TSharedPtr InSection, int32 InRow, FFrameRate InFrameRate, FFrameNumber InStartFrame, FFrameNumber InEndFrame, FFrameNumber InStartOffsetFrame) { if (!MovieSceneData.IsValid() || MovieSceneData->MovieScene == nullptr || !InSection.IsValid() || InSection->AudioSection == nullptr) { return false; } FFrameRate TickResolution = MovieSceneData->MovieScene->GetTickResolution(); FFrameNumber StartFrame = ConvertFrameTime(InStartFrame, InFrameRate, TickResolution).GetFrame(); FFrameNumber StartOffsetFrame = ConvertFrameTime(InStartOffsetFrame, InFrameRate, TickResolution).GetFrame(); FFrameNumber EndFrame = ConvertFrameTime(InEndFrame, InFrameRate, TickResolution).GetFrame(); InSection->AudioSection->Modify(); InSection->AudioSection->SetStartOffset(StartOffsetFrame.Value); InSection->AudioSection->SetRange(TRange(StartFrame, EndFrame)); if (InRow != InSection->AudioSection->GetRowIndex()) { InSection->AudioSection->SetRowIndex(InRow); } return true; } bool FMovieSceneImportData::MoveAudioSection(TSharedPtr InAudioSectionData, TSharedPtr InFromData, TSharedPtr InToData, int32 InToRowIndex) { if (!MovieSceneData.IsValid() || !InAudioSectionData.IsValid() || !InFromData.IsValid() || !InToData.IsValid()) { return false; } UMovieSceneAudioTrack* FromTrack = Cast(InFromData->MovieSceneTrack); if (FromTrack == nullptr) { return false; } UMovieSceneAudioTrack* ToTrack = Cast(InToData->MovieSceneTrack); if (ToTrack == nullptr) { return false; } UMovieSceneAudioSection* AudioSection = InAudioSectionData->AudioSection; if (AudioSection == nullptr) { return false; } FromTrack->Modify(); FromTrack->RemoveSection(*AudioSection); ToTrack->Modify(); ToTrack->AddSection(*AudioSection); InFromData->AudioSections.Remove(InAudioSectionData); for (TSharedPtr AudioTrackData : InFromData->AudioTracks) { if (AudioTrackData->AudioSections.Contains(InAudioSectionData)) { AudioTrackData->AudioSections.Remove(InAudioSectionData); } } bool bFoundTrack = false; InToData->AudioSections.Add(InAudioSectionData); for (TSharedPtr AudioTrackData : InToData->AudioTracks) { if (AudioTrackData->RowIndex == InToRowIndex) { AudioTrackData->AudioSections.Add(InAudioSectionData); bFoundTrack = true; break; } } if (!bFoundTrack) { TSharedPtr TrackData = MakeShared(); TrackData->RowIndex = InToRowIndex; TrackData->AudioSections.Add(InAudioSectionData); InToData->AudioTracks.Add(TrackData); } return true; } TSharedPtr FMovieSceneImportData::GetCinematicData(bool CreateTrackIfNull) { if (!MovieSceneData.IsValid()) { return nullptr; } if (!MovieSceneData->CinematicData.IsValid() && CreateTrackIfNull) { UMovieSceneCinematicShotTrack* CinematicTrack = MovieSceneData->MovieScene->AddTrack(); MovieSceneData->CinematicData = ConstructCinematicData(CinematicTrack); } return MovieSceneData->CinematicData; } TSharedPtr FMovieSceneImportData::GetAudioData() { if (!MovieSceneData.IsValid()) { return nullptr; } for (TSharedPtr AudioData : MovieSceneData->AudioData) { if (AudioData.IsValid()) { return AudioData; } } return nullptr; } TSharedPtr FMovieSceneImportData::ConstructMovieSceneData(UMovieScene* InMovieScene) { if (InMovieScene == nullptr) { return nullptr; } MovieSceneData = MakeShared(); MovieSceneData->MovieScene = InMovieScene; // Get cinematic track UMovieSceneCinematicShotTrack* CinematicTrack = InMovieScene->FindTrack(); if (CinematicTrack != nullptr) { MovieSceneData->CinematicData = ConstructCinematicData(CinematicTrack); if (!MovieSceneData->CinematicData.IsValid()) { return nullptr; } } // Get audio tracks TMap> AudioDataMap; const TArray Tracks = InMovieScene->GetTracks(); for (UMovieSceneTrack* Track : Tracks) { if (Track->IsA(UMovieSceneAudioTrack::StaticClass())) { UMovieSceneAudioTrack* AudioTrack = Cast(Track); if (AudioTrack == nullptr) { continue; } TSharedPtr AudioData = ConstructAudioData(AudioTrack); if (!AudioData.IsValid()) { continue; } AudioDataMap.Add(AudioTrack->GetSortingOrder(), AudioData); } } // sort the audio tracks by their sorting index and add to the AudioTracks array if (AudioDataMap.Num() > 0) { AudioDataMap.KeySort([](int32 A, int32 B) { return A < B; // sort keys in order }); for (auto& Elem : AudioDataMap) { if (Elem.Value.IsValid()) { MovieSceneData->AudioData.Add(Elem.Value); if (!SupportMultipleAudioTracks()) { break; } } } } return MovieSceneData; } TSharedPtr FMovieSceneImportData::ConstructCinematicData(UMovieSceneCinematicShotTrack* InCinematicTrack) { if (!MovieSceneData.IsValid() || InCinematicTrack == nullptr) { return nullptr; } TSharedPtr CinematicData = MakeShared(); CinematicData->MovieSceneTrack = InCinematicTrack; // Construct sections & create track row index array TArray CinematicTrackRowIndices; for (UMovieSceneSection* ShotSection : InCinematicTrack->GetAllSections()) { UMovieSceneCinematicShotSection* CinematicSection = Cast(ShotSection); if (CinematicSection != nullptr && CinematicSection->GetSequence() != nullptr) { int32 RowIndex = CinematicSection->GetRowIndex(); if (RowIndex >= 0) { CinematicTrackRowIndices.AddUnique(RowIndex); } } } // Construct tracks and point to sections CinematicTrackRowIndices.Sort(); for (int32 CinematicTrackRowIndex : CinematicTrackRowIndices) { TSharedPtr TrackData = ConstructCinematicTrackData(InCinematicTrack, CinematicTrackRowIndex); if (TrackData.IsValid()) { CinematicData->CinematicTracks.Add(TrackData); for (TSharedPtr SectionData : TrackData->CinematicSections) { if (SectionData.IsValid()) { CinematicData->CinematicSections.Add(SectionData); } } } } return CinematicData; } TSharedPtr FMovieSceneImportData::ConstructCinematicTrackData(UMovieSceneCinematicShotTrack* InCinematicTrack, int32 InRowIndex) { if (!MovieSceneData.IsValid() || InCinematicTrack == nullptr) { return nullptr; } TSharedPtr TrackData = MakeShared(); TrackData->RowIndex = InRowIndex; for (UMovieSceneSection* ShotSection : InCinematicTrack->GetAllSections()) { UMovieSceneCinematicShotSection* CinematicSection = Cast(ShotSection); if (CinematicSection != nullptr && CinematicSection->GetSequence() != nullptr && CinematicSection->GetRowIndex() == InRowIndex) { TSharedPtr CinematicSectionData = ConstructCinematicSectionData(CinematicSection); if (!CinematicSectionData.IsValid()) { return nullptr; } TrackData->CinematicSections.Add(CinematicSectionData); } } return TrackData; } TSharedPtr FMovieSceneImportData::ConstructAudioData(UMovieSceneAudioTrack* InAudioTrack) { if (!MovieSceneData.IsValid() || InAudioTrack == nullptr) { return nullptr; } TSharedPtr AudioData = MakeShared(); AudioData->MovieSceneTrack = InAudioTrack; AudioData->MaxRowIndex = 0; // Construct sections & create track row index array TArray AudioTrackRowIndices; for (UMovieSceneSection* ShotSection : InAudioTrack->GetAllSections()) { UMovieSceneAudioSection* AudioSection = Cast(ShotSection); if (AudioSection != nullptr) { int32 RowIndex = AudioSection->GetRowIndex(); if (RowIndex >= 0) { AudioTrackRowIndices.AddUnique(RowIndex); if (RowIndex > AudioData->MaxRowIndex) { AudioData->MaxRowIndex = RowIndex; } } } } // Construct tracks and point to sections AudioTrackRowIndices.Sort(); for (int32 AudioTrackRowIndex : AudioTrackRowIndices) { TSharedPtr TrackData = ConstructAudioTrackData(InAudioTrack, AudioTrackRowIndex); if (TrackData.IsValid()) { AudioData->AudioTracks.Add(TrackData); for (TSharedPtr SectionData : TrackData->AudioSections) { if (SectionData.IsValid()) { AudioData->AudioSections.Add(SectionData); } } } } return AudioData; } TSharedPtr FMovieSceneImportData::ConstructAudioTrackData(UMovieSceneAudioTrack* InAudioTrack, int32 InRowIndex) { if (!MovieSceneData.IsValid() || InAudioTrack == nullptr) { return nullptr; } TSharedPtr TrackData = MakeShared(); TrackData->RowIndex = InRowIndex; for (UMovieSceneSection* Section : InAudioTrack->GetAllSections()) { UMovieSceneAudioSection* AudioSection = Cast(Section); if (AudioSection != nullptr && AudioSectionIsSoundWave(AudioSection) && AudioSection->GetRowIndex() == InRowIndex) { TSharedPtr AudioSectionData = ConstructAudioSectionData(AudioSection); if (!AudioSectionData.IsValid()) { continue; } TrackData->AudioSections.Add(AudioSectionData); } } return TrackData; } TSharedPtr FMovieSceneImportData::ConstructCinematicSectionData(UMovieSceneCinematicShotSection* InCinematicSection) { if (!MovieSceneData.IsValid() || MovieSceneData->MovieScene == nullptr || InCinematicSection == nullptr) { return nullptr; } TSharedPtr SectionData = MakeShared(); SectionData->CinematicSection = InCinematicSection; return SectionData; } TSharedPtr FMovieSceneImportData::ConstructAudioSectionData(UMovieSceneAudioSection* InAudioSection) { if (InAudioSection == nullptr) { return nullptr; } USoundBase* SoundBase = InAudioSection->GetSound(); if (SoundBase == nullptr || !SoundBase->IsA()) { return nullptr; } USoundWave* SoundWave = nullptr; SoundWave = Cast(SoundBase); if (SoundWave == nullptr || SoundWave->AssetImportData == nullptr) { return nullptr; } TSharedPtr SectionData = MakeShared(); SectionData->AudioSection = InAudioSection; TArray Filenames = SoundWave->AssetImportData->ExtractFilenames(); if (Filenames.Num() < 1) { return nullptr; } SectionData->SourceFilename = FPaths::GetCleanFilename(Filenames[0]); SectionData->SourceFilePath = FPaths::GetPath(Filenames[0]); return SectionData; } void FMovieSceneTranslatorContext::Init() { ClearMessages(); } void FMovieSceneTranslatorContext::AddMessage(EMessageSeverity::Type InMessageSeverity, FText InMessage) { Messages.Add(FTokenizedMessage::Create(InMessageSeverity, InMessage)); } void FMovieSceneTranslatorContext::ClearMessages() { Messages.Empty(); } bool FMovieSceneTranslatorContext::ContainsMessageType(EMessageSeverity::Type InMessageSeverity) const { for (const TSharedRef& Message : Messages) { if (Message->GetSeverity() == InMessageSeverity) { return true; } } return false; } const TArray>& FMovieSceneTranslatorContext::GetMessages() const { return Messages; } #undef LOCTEXT_NAMESPACE