// Copyright Epic Games, Inc. All Rights Reserved. #include "Commandlets/SwapSoundForDialogueInCuesCommandlet.h" #include "Modules/ModuleManager.h" #include "SoundCueGraph/SoundCueGraph.h" #include "SoundCueGraph/SoundCueGraphNode.h" #include "AssetRegistry/AssetData.h" #include "Sound/SoundWave.h" #include "Sound/DialogueWave.h" #include "Sound/SoundCue.h" #include "AssetRegistry/ARFilter.h" #include "AssetRegistry/AssetRegistryModule.h" #include "Sound/SoundNodeDialoguePlayer.h" #include "Sound/SoundNodeWavePlayer.h" #include "AudioEditorModule.h" #include "LocalizedAssetUtil.h" #include "LocalizationSourceControlUtil.h" DEFINE_LOG_CATEGORY_STATIC(LogSwapSoundForDialogueInCuesCommandlet, Log, All); int32 USwapSoundForDialogueInCuesCommandlet::Main(const FString& Params) { // Prepare asset registry. FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); AssetRegistry.SearchAllAssets(true); // Parse command line. TArray Tokens; TArray Switches; TMap Parameters; UCommandlet::ParseCommandLine(*Params, Tokens, Switches, Parameters); TSharedPtr SourceControlInfo; const bool bEnableSourceControl = Switches.Contains(TEXT("EnableSCC")); if (bEnableSourceControl) { SourceControlInfo = MakeShareable(new FLocalizationSCC()); FText SCCErrorStr; if (!SourceControlInfo->IsReady(SCCErrorStr)) { UE_LOG(LogSwapSoundForDialogueInCuesCommandlet, Error, TEXT("Revision Control error: %s"), *SCCErrorStr.ToString()); return -1; } } // We only want dialogue wave assets that exist within the Game content directory. TArray AssetDataArrayForDialogueWaves; if (!FLocalizedAssetUtil::GetAssetsByPathAndClass(AssetRegistry, FName("/Game"), UDialogueWave::StaticClass()->GetClassPathName(), /*bIncludeLocalizedAssets*/false, AssetDataArrayForDialogueWaves)) { UE_LOG(LogSwapSoundForDialogueInCuesCommandlet, Error, TEXT("Unable to get dialogue wave asset data from asset registry.")); return -1; } for (const FAssetData& AssetData : AssetDataArrayForDialogueWaves) { // Verify that the found asset is a dialogue wave. if (AssetData.GetClass() != UDialogueWave::StaticClass()) { UE_LOG(LogSwapSoundForDialogueInCuesCommandlet, Error, TEXT("Asset registry found asset (%s), but the asset with this name is not actually a dialogue wave."), *AssetData.AssetName.ToString()); continue; } // Get the dialogue wave. UDialogueWave* const DialogueWave = Cast(AssetData.GetAsset()); // Verify that the dialogue wave was loaded. if (!DialogueWave) { UE_LOG(LogSwapSoundForDialogueInCuesCommandlet, Error, TEXT("Asset registry found asset (%s), but the dialogue wave could not be accessed."), *AssetData.AssetName.ToString()); continue; } // Iterate over each of the contexts and fix up the sound cue nodes referencing this sound wave. for (const FDialogueContextMapping& ContextMapping : DialogueWave->ContextMappings) { // Skip contexts without sound waves. const USoundWave* const SoundWave = ContextMapping.SoundWave; if (!SoundWave) { continue; } // Verify that the sound wave has a package. const UPackage* const SoundWavePackage = SoundWave->GetOutermost(); if (!SoundWavePackage) { UE_LOG(LogSwapSoundForDialogueInCuesCommandlet, Error, TEXT("Asset registry found dialogue wave (%s) with a context referencing sound wave (%s) but no package exists for this sound wave."), *AssetData.AssetName.ToString(), *SoundWave->GetName()); continue; } // Find referencers of the context's sound wave. TArray SoundWaveReferencerNames; if (!AssetRegistry.GetReferencers(SoundWavePackage->GetFName(), SoundWaveReferencerNames)) { UE_LOG(LogSwapSoundForDialogueInCuesCommandlet, Error, TEXT("Asset registry found dialogue wave (%s) with a context referencing sound wave (%s) but failed to search for referencers of the sound wave."), *AssetData.AssetName.ToString(), *SoundWave->GetName()); continue; } // Skip further searching if there are no sound wave referencers. if (SoundWaveReferencerNames.Num() == 0) { continue; } // Get sound cue assets that reference the context's sound wave. FARFilter Filter; Filter.ClassPaths.Add(USoundCue::StaticClass()->GetClassPathName()); Filter.bRecursiveClasses = true; Filter.PackageNames = SoundWaveReferencerNames; TArray ReferencingSoundCueAssetDataArray; if (!AssetRegistry.GetAssets(Filter, ReferencingSoundCueAssetDataArray)) { UE_LOG(LogSwapSoundForDialogueInCuesCommandlet, Error, TEXT("Asset registry found dialogue wave (%s) with a context referencing sound wave (%s) but failed to search for sound cues referencing the sound wave."), *AssetData.AssetName.ToString(), *SoundWave->GetName()); continue; } // Iterate through referencing sound cues, finding sound node wave players and replacing them for (const FAssetData& SoundCueAssetData : ReferencingSoundCueAssetDataArray) { USoundCue* const SoundCue = Cast(SoundCueAssetData.GetAsset()); // Verify that the sound cue exists. if (!SoundCue) { UE_LOG(LogSwapSoundForDialogueInCuesCommandlet, Error, TEXT("Asset registry found dialogue wave (%s) with a context referencing sound wave (%s) but failed to access the referencing sound cue (%s)."), *AssetData.AssetName.ToString(), *SoundWave->GetName(), *SoundCueAssetData.AssetName.ToString()); continue; } // Iterate through sound nodes in this sound cue and find those that need replacing. TArray NodesToReplace; for (USoundNode* const SoundNode : SoundCue->AllNodes) { USoundNodeWavePlayer* const SoundNodeWavePlayer = Cast(SoundNode); // Skip nodes that are not sound wave players or not referencing the sound wave in question. if (SoundNodeWavePlayer && SoundNodeWavePlayer->GetSoundWave() == SoundWave) { NodesToReplace.Add(SoundNode); } } if (NodesToReplace.Num() == 0) { continue; } IAudioEditorModule* AudioEditorModule = &FModuleManager::LoadModuleChecked("AudioEditor"); AudioEditorModule->ReplaceSoundNodesInGraph(SoundCue, DialogueWave, NodesToReplace, ContextMapping); // Execute save. if (!FLocalizedAssetSCCUtil::SaveAssetWithSCC(SourceControlInfo, SoundCue)) { continue; } } } } return 0; }