// Copyright Epic Games, Inc. All Rights Reserved. #include "MovieSceneCapture.h" #include "AssetRegistry/AssetData.h" #include "Dom/JsonValue.h" #include "Dom/JsonObject.h" #include "HAL/PlatformFileManager.h" #include "HAL/FileManager.h" #include "Misc/CommandLine.h" #include "Misc/Paths.h" #include "Misc/ConfigCacheIni.h" #include "Misc/App.h" #include "Engine/World.h" #include "Slate/SceneViewport.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonSerializer.h" #include "ActiveMovieSceneCaptures.h" #include "JsonObjectConverter.h" #include "Misc/RemoteConfigIni.h" #include "MovieSceneCaptureModule.h" #include "Engine/GameViewportClient.h" #include "Protocols/VideoCaptureProtocol.h" #include "Slate/SceneViewport.h" #include "Engine/Engine.h" #include "AssetRegistry/AssetRegistryModule.h" #include "UObject/UObjectIterator.h" #include "Protocols/AudioCaptureProtocol.h" #include "Misc/DateTime.h" #include "RenderUtils.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(MovieSceneCapture) #define LOCTEXT_NAMESPACE "MovieSceneCapture" const FName UMovieSceneCapture::MovieSceneCaptureUIName = FName(TEXT("MovieSceneCaptureUIInstance")); TArray FindAllCaptureProtocolClasses() { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); // Retrieve all blueprint classes TArray BlueprintList; FARFilter Filter; Filter.ClassPaths.Add(UMovieSceneCaptureProtocolBase::StaticClass()->GetClassPathName()); // Include any Blueprint based objects as well, this includes things like Blutilities, UMG, and GameplayAbility objects Filter.bRecursiveClasses = true; AssetRegistryModule.Get().GetAssets(Filter, BlueprintList); TArray Classes; for (const FAssetData& Data : BlueprintList) { UClass* Class = Data.GetClass(); if (Class) { Classes.Add(Class); } } for (TObjectIterator ClassIterator; ClassIterator; ++ClassIterator) { if (ClassIterator->IsChildOf(UMovieSceneCaptureProtocolBase::StaticClass()) && !ClassIterator->HasAnyClassFlags(CLASS_Abstract | CLASS_Deprecated | CLASS_NewerVersionExists)) { Classes.Add(*ClassIterator); } } return Classes; } struct FUniqueMovieSceneCaptureHandle : FMovieSceneCaptureHandle { FUniqueMovieSceneCaptureHandle() { /// Start IDs at index 1 since 0 is deemed invalid static uint32 Unique = 1; ID = Unique++; } }; FMovieSceneCaptureSettings::FMovieSceneCaptureSettings() : Resolution(1280, 720) { OutputDirectory.Path = FPaths::VideoCaptureDir(); FPaths::MakePlatformFilename( OutputDirectory.Path ); bOverwriteExisting = false; bUseRelativeFrameNumbers = false; HandleFrames = 0; GameModeOverride = nullptr; OutputFormat = TEXT("{world}"); bUseCustomFrameRate = false; CustomFrameRate = FFrameRate(24, 1); FrameRate = FFrameRate(24, 1); ZeroPadFrameNumbers = 4; bEnableTextureStreaming = false; bCinematicEngineScalability = true; bCinematicMode = true; bAllowMovement = false; bAllowTurning = false; bShowPlayer = false; bShowHUD = false; bUsePathTracer = false; PathTracerSamplePerPixel = 16; #if PLATFORM_MAC MovieExtension = TEXT(".mov"); #elif PLATFORM_UNIX MovieExtension = TEXT(".unsupp"); #else MovieExtension = TEXT(".avi"); #endif } UMovieSceneCapture::UMovieSceneCapture(const FObjectInitializer& Initializer) : Super(Initializer) { TArray Tokens, Switches; FCommandLine::Parse( FCommandLine::Get(), Tokens, Switches ); for (auto& Switch : Switches) { InheritedCommandLineArguments.AppendChar('-'); InheritedCommandLineArguments.Append(Switch); InheritedCommandLineArguments.AppendChar(' '); } AdditionalCommandLineArguments += TEXT("-NOSCREENMESSAGES"); Handle = FUniqueMovieSceneCaptureHandle(); bCapturing = false; FrameNumberOffset = 0; ImageCaptureProtocolType = UVideoCaptureProtocol::StaticClass(); AudioCaptureProtocolType = UNullAudioCaptureProtocol::StaticClass(); } void UMovieSceneCapture::PostInitProperties() { if (!HasAnyFlags(RF_ClassDefaultObject)) { InitializeCaptureProtocols(); } Super::PostInitProperties(); } void UMovieSceneCapture::SetImageCaptureProtocolType(TSubclassOf ProtocolType) { ImageCaptureProtocolType = ProtocolType.Get(); InitializeCaptureProtocols(); } void UMovieSceneCapture::SetAudioCaptureProtocolType(TSubclassOf ProtocolType) { AudioCaptureProtocolType = ProtocolType.Get(); InitializeCaptureProtocols(); } void UMovieSceneCapture::ForciblyReinitializeCaptureProtocols() { UClass* ImageProtocolType = ImageCaptureProtocolType.TryLoadClass(); UClass* AudioProtocolType = AudioCaptureProtocolType.TryLoadClass(); if (ImageCaptureProtocol) { // Release the protocol since we know now that it's either not needed (the type is None), or it's the wrong type ImageCaptureProtocol->OnReleaseConfig(Settings); FName UniqueDeadName = MakeUniqueObjectName(GetTransientPackage(), UMovieSceneImageCaptureProtocolBase::StaticClass(), "ImageCaptureProtocol_DEAD"); ImageCaptureProtocol->Rename(*UniqueDeadName.ToString(), GetTransientPackage()); ImageCaptureProtocol = nullptr; } if (AudioCaptureProtocol) { // Release the protocol since we know now that it's either not needed (the type is None), or it's the wrong type AudioCaptureProtocol->OnReleaseConfig(Settings); FName UniqueDeadName = MakeUniqueObjectName(GetTransientPackage(), UMovieSceneAudioCaptureProtocolBase::StaticClass(), "AudioCaptureProtocol_DEAD"); AudioCaptureProtocol->Rename(*UniqueDeadName.ToString(), GetTransientPackage()); AudioCaptureProtocol = nullptr; } if (ImageProtocolType) { FString ProtocolName = GetName() + TEXT("_ImageProtocol"); ImageCaptureProtocol = NewObject(this, ImageProtocolType, *ProtocolName); if (ImageCaptureProtocol) { ImageCaptureProtocol->LoadConfig(); ImageCaptureProtocol->OnLoadConfig(Settings); } #if WITH_EDITOR FPropertyChangedEvent PropertyChangedEvent(GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UMovieSceneCapture, ImageCaptureProtocol)), EPropertyChangeType::ValueSet); PostEditChangeProperty(PropertyChangedEvent); #endif } if (AudioProtocolType) { FString ProtocolName = GetName() + TEXT("_AudioProtocol"); AudioCaptureProtocol = NewObject(this, AudioProtocolType, *ProtocolName); if (AudioCaptureProtocol) { AudioCaptureProtocol->LoadConfig(); AudioCaptureProtocol->OnLoadConfig(Settings); } #if WITH_EDITOR FPropertyChangedEvent PropertyChangedEvent(GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UMovieSceneCapture, AudioCaptureProtocol)), EPropertyChangeType::ValueSet); PostEditChangeProperty(PropertyChangedEvent); #endif } } void UMovieSceneCapture::InitializeCaptureProtocols() { UClass* ImageProtocolType = ImageCaptureProtocolType.TryLoadClass(); UClass* AudioProtocolType = AudioCaptureProtocolType.TryLoadClass(); // If there's no type and we've no protocol, do nothing if (!ImageProtocolType && !AudioProtocolType && !ImageCaptureProtocol && !AudioCaptureProtocol) { return; } // If we have a type and we've already got a protocol of that type, do nothing if (ImageProtocolType && ImageCaptureProtocol && ImageCaptureProtocol->GetClass() == ImageProtocolType && AudioCaptureProtocol && AudioCaptureProtocol->GetClass() == AudioProtocolType) { return; } ForciblyReinitializeCaptureProtocols(); } void UMovieSceneCapture::Initialize(TSharedPtr InSceneViewport, int32 PIEInstance) { ensure(!bCapturing); // Apply command-line overrides { FString OutputPathOverride; if( FParse::Value( FCommandLine::Get(), TEXT( "-MovieFolder=" ), OutputPathOverride ) ) { Settings.OutputDirectory.Path = OutputPathOverride; FPaths::NormalizeFilename(Settings.OutputDirectory.Path); // Only validate the directory if it doesn't contain any format specifiers if (!Settings.OutputDirectory.Path.Contains(TEXT("{"))) { if (!IFileManager::Get().DirectoryExists(*Settings.OutputDirectory.Path)) { if (!IFileManager::Get().MakeDirectory(*Settings.OutputDirectory.Path)) { UE_LOG(LogMovieSceneCapture, Error, TEXT("Invalid output directory: %s."), *Settings.OutputDirectory.Path); } } else if (IFileManager::Get().IsReadOnly(*Settings.OutputDirectory.Path)) { UE_LOG(LogMovieSceneCapture, Error, TEXT("Read only output directory: %s."), *Settings.OutputDirectory.Path); } } } FString OutputNameOverride; if( FParse::Value( FCommandLine::Get(), TEXT( "-MovieName=" ), OutputNameOverride ) ) { Settings.OutputFormat = OutputNameOverride; } bool bOverrideOverwriteExisting; if( FParse::Bool( FCommandLine::Get(), TEXT( "-MovieOverwriteExisting=" ), bOverrideOverwriteExisting ) ) { Settings.bOverwriteExisting = bOverrideOverwriteExisting; } bool bOverrideRelativeFrameNumbers; if( FParse::Bool( FCommandLine::Get(), TEXT( "-MovieRelativeFrames=" ), bOverrideRelativeFrameNumbers ) ) { Settings.bUseRelativeFrameNumbers = bOverrideRelativeFrameNumbers; } int32 HandleFramesOverride; if( FParse::Value( FCommandLine::Get(), TEXT( "-HandleFrames=" ), HandleFramesOverride ) ) { Settings.HandleFrames = HandleFramesOverride; } bool bOverrideCinematicEngineScalabilityMode; if( FParse::Bool( FCommandLine::Get(), TEXT( "-MovieEngineScalabilityMode=" ), bOverrideCinematicEngineScalabilityMode ) ) { Settings.bCinematicEngineScalability = bOverrideCinematicEngineScalabilityMode; } bool bOverrideCinematicMode; if( FParse::Bool( FCommandLine::Get(), TEXT( "-MovieCinematicMode=" ), bOverrideCinematicMode ) ) { Settings.bCinematicMode = bOverrideCinematicMode; } bool bOverridePathTracer; if (FParse::Bool(FCommandLine::Get(), TEXT("-PathTracer="), bOverridePathTracer)) { Settings.bUsePathTracer = bOverridePathTracer; if (bOverridePathTracer) { InSceneViewport->GetClient()->GetEngineShowFlags()->SetPathTracing(true); } } uint16 OverridePathTracerSamplePerPixel; if (FParse::Value(FCommandLine::Get(), TEXT("-PathTracerSamplePerPixel="), OverridePathTracerSamplePerPixel)) { Settings.PathTracerSamplePerPixel = OverridePathTracerSamplePerPixel; } bool bProtocolOverride = false; FString ImageProtocolOverrideString; if ( FParse::Value( FCommandLine::Get(), TEXT( "-MovieFormat=" ), ImageProtocolOverrideString ) || FParse::Value( FCommandLine::Get(), TEXT( "-ImageCaptureProtocol=" ), ImageProtocolOverrideString ) ) { static const TCHAR* const CommandLineIDString = TEXT("CommandLineID"); UClass* OverrideClass = nullptr; TArray AllProtocolTypes = FindAllCaptureProtocolClasses(); for (UClass* Class : AllProtocolTypes) { bool bMetaDataMatch = #if WITH_EDITOR Class->GetMetaData(CommandLineIDString) == ImageProtocolOverrideString; #else false; #endif if ( bMetaDataMatch || Class->GetName() == ImageProtocolOverrideString ) { OverrideClass = Class; break; } } if (!OverrideClass) { UE_LOG(LogMovieSceneCapture, Error, TEXT("Unrecognized image capture type (-MovieFormat or -ImageCaptureProtocol): %s."), *ImageProtocolOverrideString); } else { ImageCaptureProtocolType = OverrideClass; } } FString AudioProtocolOverrideString; if (FParse::Value( FCommandLine::Get(), TEXT( "-AudioCaptureProtocol=" ), AudioProtocolOverrideString ) ) { static const TCHAR* const CommandLineIDString = TEXT("CommandLineID"); UClass* OverrideClass = nullptr; TArray AllProtocolTypes = FindAllCaptureProtocolClasses(); for (UClass* Class : AllProtocolTypes) { bool bMetaDataMatch = #if WITH_EDITOR Class->GetMetaData(CommandLineIDString) == AudioProtocolOverrideString; #else false; #endif if ( bMetaDataMatch || Class->GetName() == AudioProtocolOverrideString ) { OverrideClass = Class; break; } } if (!OverrideClass) { UE_LOG(LogMovieSceneCapture, Error, TEXT("Unrecognized audio capture type (-AudioCaptureProtocol): %s."), *AudioProtocolOverrideString); } else { AudioCaptureProtocolType = OverrideClass; } } FString FrameRateOverrideString; if ( FParse::Value( FCommandLine::Get(), TEXT( "-MovieFrameRate=" ), FrameRateOverrideString ) ) { if (!TryParseString(Settings.CustomFrameRate, *FrameRateOverrideString)) { UE_LOG(LogMovieSceneCapture, Error, TEXT("Unrecognized capture frame rate: %s. Defaulting to sequence frame rate."), *FrameRateOverrideString); } else { Settings.bUseCustomFrameRate = true; } } } if (!IsRayTracingEnabled()) { Settings.bUsePathTracer = false; } bFinalizeWhenReady = false; bIsAudioCapturePass = false; InitSettings = FCaptureProtocolInitSettings::FromSlateViewport(InSceneViewport.ToSharedRef()); CachedMetrics = FCachedMetrics(); CachedMetrics.Width = InitSettings->DesiredSize.X; CachedMetrics.Height = InitSettings->DesiredSize.Y; double FrameRate = Settings.GetFrameRate().AsDecimal(); FormatMappings.Reserve(10); if (FrameRate == FMath::RoundToDouble(FrameRate)) { FormatMappings.Add(TEXT("fps"), FString::Printf(TEXT("%" INT64_FMT), FMath::RoundToInt(FrameRate))); } else { FormatMappings.Add(TEXT("fps"), FString::Printf(TEXT("%.2f"), FrameRate)); } FormatMappings.Add(TEXT("width"), FString::Printf(TEXT("%d"), CachedMetrics.Width)); FormatMappings.Add(TEXT("height"), FString::Printf(TEXT("%d"), CachedMetrics.Height)); FormatMappings.Add(TEXT("world"), InSceneViewport->GetClient()->GetWorld()->GetName()); FDateTime CurrentTime = FDateTime::Now(); FormatMappings.Add(TEXT("year"), CurrentTime.ToString(TEXT("%Y"))); FormatMappings.Add(TEXT("month"), CurrentTime.ToString(TEXT("%m"))); FormatMappings.Add(TEXT("day"), CurrentTime.ToString(TEXT("%d"))); FormatMappings.Add(TEXT("date"), CurrentTime.ToString(TEXT("%Y.%m.%d"))); FormatMappings.Add(TEXT("time"), CurrentTime.ToString(TEXT("%H.%M.%S"))); if( !CaptureStrategy.IsValid() ) { CaptureStrategy = MakeShareable( new FRealTimeCaptureStrategy( Settings.FrameRate ) ); CaptureStrategy->OnInitialize(); } InitializeCaptureProtocols(); if (ensure(ImageCaptureProtocol) && ensure(AudioCaptureProtocol)) { ImageCaptureProtocol->Setup(InitSettings.GetValue(), this); AudioCaptureProtocol->Setup(InitSettings.GetValue(), this); } if (Settings.bCinematicEngineScalability) { CachedQualityLevels = Scalability::GetQualityLevels(); Scalability::FQualityLevels QualityLevels = CachedQualityLevels; QualityLevels.SetFromSingleQualityLevelRelativeToMax(0); Scalability::SetQualityLevels(QualityLevels); } if (!HasAnyFlags(RF_ClassDefaultObject)) { FActiveMovieSceneCaptures::Get().Add(this); } } void UMovieSceneCapture::StartWarmup() { bCapturing = false; if (bIsAudioCapturePass) { if (ensure(AudioCaptureProtocol)) { AudioCaptureProtocol->WarmUp(); } } else { if (ensure(ImageCaptureProtocol)) { ImageCaptureProtocol->WarmUp(); } } } void UMovieSceneCapture::StartCapture() { bFinalizeWhenReady = false; bCapturing = true; // Audio Captures will always use Real Time capture strategies due to the Audio engine's need for real-time processing. if (!CaptureStrategy.IsValid() || bIsAudioCapturePass) { if (CaptureStrategy.IsValid()) { CaptureStrategy->OnStop(); } CaptureStrategy = MakeShareable(new FRealTimeCaptureStrategy(Settings.FrameRate)); } CaptureStrategy->OnInitialize(); // We only initialize the image capture protocol on the first pass and then stop ticking it (but don't finalize it) // until the audio capture pass has finished as well. StartCapture can get called up to two times, once for the image // pass, and again for the audio pass (if needed). if(!bIsAudioCapturePass) { ImageCaptureProtocol->StartCapture(); // Disable audio so when the image pass runs it doesn't play stuttering audio. // ToDo: This doesn't work very well in the editor due to some conflicting code in the engine tick loop // that also sets the volume each frame, overriding the effect of this. FApp::SetVolumeMultiplier(0.0f); FApp::SetUnfocusedVolumeMultiplier(0.0f); } else { // Unmute the audio FApp::SetVolumeMultiplier(1.0f); // Ensure non-focused apps still play audio as the audio has to be emitted for the recording to capture it. FApp::SetUnfocusedVolumeMultiplier(1.0f); AudioCaptureProtocol->StartCapture(); } } void UMovieSceneCapture::CaptureThisFrame(float DeltaSeconds) { if (!bCapturing || !CaptureStrategy.IsValid() || !ImageCaptureProtocol || !AudioCaptureProtocol || bFinalizeWhenReady) { return; } CachedMetrics.ElapsedSeconds += DeltaSeconds; if (CaptureStrategy->ShouldPresent(CachedMetrics.ElapsedSeconds, CachedMetrics.Frame)) { uint32 NumDroppedFrames = CaptureStrategy->GetDroppedFrames(CachedMetrics.ElapsedSeconds, CachedMetrics.Frame); CachedMetrics.Frame += NumDroppedFrames; const FFrameMetrics ThisFrameMetrics( CachedMetrics.ElapsedSeconds, DeltaSeconds, CachedMetrics.Frame, NumDroppedFrames ); if (!bIsAudioCapturePass) { ImageCaptureProtocol->CaptureFrame(ThisFrameMetrics); } else { AudioCaptureProtocol->CaptureFrame(ThisFrameMetrics); } UE_LOG(LogMovieSceneCapture, Verbose, TEXT("Captured frame: %d"), CachedMetrics.Frame); ++CachedMetrics.Frame; } } void UMovieSceneCapture::Tick(float DeltaSeconds) { if (ImageCaptureProtocol) { ImageCaptureProtocol->PreTick(); } if(AudioCaptureProtocol) { AudioCaptureProtocol->PreTick(); } OnTick(DeltaSeconds); if (ImageCaptureProtocol) { ImageCaptureProtocol->Tick(); } if(AudioCaptureProtocol) { AudioCaptureProtocol->Tick(); } } void UMovieSceneCapture::FinalizeWhenReady() { if (!bFinalizeWhenReady) { bFinalizeWhenReady = true; if (ImageCaptureProtocol) { ImageCaptureProtocol->BeginFinalize(); } if(AudioCaptureProtocol) { AudioCaptureProtocol->BeginFinalize(); } } } void UMovieSceneCapture::Finalize() { if (Settings.bCinematicEngineScalability) { Scalability::SetQualityLevels(CachedQualityLevels); } FActiveMovieSceneCaptures::Get().Remove(this); if (bCapturing) { bCapturing = false; if (CaptureStrategy.IsValid()) { CaptureStrategy->OnStop(); CaptureStrategy = nullptr; } if (ImageCaptureProtocol) { ImageCaptureProtocol->Finalize(); } if(AudioCaptureProtocol) { AudioCaptureProtocol->Finalize(); } // Reinitialize the object to ensure no transient state is carried over from one capture to the next ForciblyReinitializeCaptureProtocols(); OnCaptureFinishedDelegate.Broadcast(); } bFinalizeWhenReady = false; } FString UMovieSceneCapture::ResolveFileFormat(const FString& Format, const FFrameMetrics& FrameMetrics) const { TMap AllArgs = FormatMappings; AllArgs.Add(TEXT("frame"), FString::Printf(TEXT("%0*d"), Settings.ZeroPadFrameNumbers, Settings.bUseRelativeFrameNumbers ? FrameMetrics.FrameNumber : FrameMetrics.FrameNumber + FrameNumberOffset)); AddFormatMappings(AllArgs, FrameMetrics); // Allow the capture protocols to add their own format mappings if (ImageCaptureProtocol) { ImageCaptureProtocol->AddFormatMappings(AllArgs); } if (AudioCaptureProtocol) { AudioCaptureProtocol->AddFormatMappings(AllArgs); } return FString::Format(*Format, AllArgs); } void UMovieSceneCapture::LoadFromConfig() { LoadConfig(); InitializeCaptureProtocols(); if(ImageCaptureProtocol) { ImageCaptureProtocol->LoadConfig(); } if(AudioCaptureProtocol) { AudioCaptureProtocol->LoadConfig(); } FString JsonString; FString Section = GetClass()->GetPathName() + TEXT("_Json"); if (GConfig->GetString( *Section, TEXT("Data"), JsonString, GEditorSettingsIni)) { FString UnescapedString = FRemoteConfig::ReplaceIniSpecialCharWithChar(JsonString).ReplaceEscapedCharWithChar(); TSharedPtr RootObject; TSharedRef > JsonReader = TJsonReaderFactory<>::Create(UnescapedString); if (FJsonSerializer::Deserialize(JsonReader, RootObject) && RootObject.IsValid()) { DeserializeAdditionalJson(*RootObject); } } } void UMovieSceneCapture::SaveToConfig() { TSharedRef Json = MakeShareable(new FJsonObject); SerializeAdditionalJson(*Json); FString JsonString; TSharedRef > JsonWriter = TJsonWriterFactory<>::Create(&JsonString, 0); if (FJsonSerializer::Serialize( Json, JsonWriter )) { FString Section = GetClass()->GetPathName() + TEXT("_Json"); FString EscapedJson = FRemoteConfig::ReplaceIniCharWithSpecialChar(JsonString).ReplaceCharWithEscapedChar(); GConfig->SetString( *Section, TEXT("Data"), *EscapedJson, GEditorSettingsIni); GConfig->Flush(false, GEditorSettingsIni); } SaveConfig(); if(ImageCaptureProtocol) { ImageCaptureProtocol->SaveConfig(); } if(AudioCaptureProtocol) { AudioCaptureProtocol->SaveConfig(); } } void UMovieSceneCapture::SerializeJson(FJsonObject& Object) { if (ImageCaptureProtocol) { Object.SetField(TEXT("ImageProtocolType"), MakeShareable(new FJsonValueString(ImageCaptureProtocol->GetClass()->GetPathName()))); TSharedRef ProtocolDataObject = MakeShareable(new FJsonObject); if (FJsonObjectConverter::UStructToJsonObject(ImageCaptureProtocol->GetClass(), ImageCaptureProtocol, ProtocolDataObject, 0, 0)) { Object.SetField(TEXT("ImageProtocolData"), MakeShareable(new FJsonValueObject(ProtocolDataObject))); } } if (AudioCaptureProtocol) { Object.SetField(TEXT("AudioProtocolType"), MakeShareable(new FJsonValueString(AudioCaptureProtocol->GetClass()->GetPathName()))); TSharedRef ProtocolDataObject = MakeShareable(new FJsonObject); if (FJsonObjectConverter::UStructToJsonObject(AudioCaptureProtocol->GetClass(), AudioCaptureProtocol, ProtocolDataObject, 0, 0)) { Object.SetField(TEXT("AudioProtocolData"), MakeShareable(new FJsonValueObject(ProtocolDataObject))); } } SerializeAdditionalJson(Object); } void UMovieSceneCapture::DeserializeJson(const FJsonObject& Object) { TSharedPtr ImageProtocolTypeField = Object.TryGetField(TEXT("ImageProtocolType")); if (ImageProtocolTypeField.IsValid()) { UClass* ProtocolTypeClass = FindObject(nullptr, *ImageProtocolTypeField->AsString()); if (ProtocolTypeClass && ProtocolTypeClass->IsChildOf(UMovieSceneCaptureProtocolBase::StaticClass())) { SetImageCaptureProtocolType(ProtocolTypeClass); if (ImageCaptureProtocol) { TSharedPtr ProtocolDataField = Object.TryGetField(TEXT("ImageProtocolData")); if (ProtocolDataField.IsValid()) { FJsonObjectConverter::JsonAttributesToUStruct(ProtocolDataField->AsObject()->Values, ProtocolTypeClass, ImageCaptureProtocol, 0, 0); } } #if WITH_EDITOR FPropertyChangedEvent PropertyChangedEvent(GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UMovieSceneCapture, ImageCaptureProtocol)), EPropertyChangeType::ValueSet); PostEditChangeProperty(PropertyChangedEvent); #endif } } TSharedPtr AudioProtocolTypeField = Object.TryGetField(TEXT("AudioProtocolType")); if (AudioProtocolTypeField.IsValid()) { UClass* ProtocolTypeClass = FindObject(nullptr, *AudioProtocolTypeField->AsString()); if (ProtocolTypeClass && ProtocolTypeClass->IsChildOf(UMovieSceneCaptureProtocolBase::StaticClass())) { SetAudioCaptureProtocolType(ProtocolTypeClass); if (AudioCaptureProtocol) { TSharedPtr ProtocolDataField = Object.TryGetField(TEXT("AudioProtocolData")); if (ProtocolDataField.IsValid()) { FJsonObjectConverter::JsonAttributesToUStruct(ProtocolDataField->AsObject()->Values, ProtocolTypeClass, AudioCaptureProtocol, 0, 0); } } #if WITH_EDITOR FPropertyChangedEvent PropertyChangedEvent(GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UMovieSceneCapture, AudioCaptureProtocol)), EPropertyChangeType::ValueSet); PostEditChangeProperty(PropertyChangedEvent); #endif } } DeserializeAdditionalJson(Object); } bool UMovieSceneCapture::ShouldFinalize() const { return bFinalizeWhenReady && ImageCaptureProtocol->HasFinishedProcessing() && AudioCaptureProtocol->HasFinishedProcessing(); } bool UMovieSceneCapture::IsAudioPassIfNeeded() const { return AudioCaptureProtocolType == UNullAudioCaptureProtocol::StaticClass() || bIsAudioCapturePass; } #if WITH_EDITOR void UMovieSceneCapture::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { FName PropertyName = (PropertyChangedEvent.MemberProperty != NULL) ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None; if (PropertyName == GET_MEMBER_NAME_CHECKED(UMovieSceneCapture, ImageCaptureProtocolType) || PropertyName == GET_MEMBER_NAME_CHECKED(UMovieSceneCapture, AudioCaptureProtocolType)) { InitializeCaptureProtocols(); } // We only want to save changes to the UI instance. This makes it so that closing the Movie Scene Capture UI // saves your changes (without having to render a movie) but doesn't leak changes into the Python scripting // environment. if (GetFName() == MovieSceneCaptureUIName) { SaveToConfig(); } Super::PostEditChangeProperty(PropertyChangedEvent); } #endif FFixedTimeStepCaptureStrategy::FFixedTimeStepCaptureStrategy(FFrameRate InFrameRate) : FrameRate(InFrameRate) { } void FFixedTimeStepCaptureStrategy::OnInitialize() { FApp::SetFixedDeltaTime(FrameRate.AsInterval()); FApp::SetUseFixedTimeStep(true); } void FFixedTimeStepCaptureStrategy::OnStop() { FApp::SetUseFixedTimeStep(false); // This can cause an issue with negative delta times in the engine. Instead of changing // that rather sensitive code, we're going to just override the time that calculation is based off of, so it never comes up with a negative number. if(FApp::GetCurrentTime() > FPlatformTime::Seconds()) { UE_LOG(LogTemp, Warning, TEXT("FApp::CurrentTime() is ahead of platform time due to Fixed Timestep. Fixing. AppCurrentTime: %f PlatformCurrentTime: %f"), FApp::GetCurrentTime(), FPlatformTime::Seconds()); double CurrentRealTime = FPlatformTime::Seconds(); FApp::SetCurrentTime(CurrentRealTime); } } bool FFixedTimeStepCaptureStrategy::ShouldPresent(double CurrentTimeSeconds, uint32 FrameIndex) const { return true; } int32 FFixedTimeStepCaptureStrategy::GetDroppedFrames(double CurrentTimeSeconds, uint32 FrameIndex) const { return 0; } FRealTimeCaptureStrategy::FRealTimeCaptureStrategy(FFrameRate InFrameRate) : NextPresentTimeS(0), FrameLength(InFrameRate.AsInterval()) { } void FRealTimeCaptureStrategy::OnInitialize() { } void FRealTimeCaptureStrategy::OnStop() { } bool FRealTimeCaptureStrategy::ShouldPresent(double CurrentTimeSeconds, uint32 FrameIndex) const { return CurrentTimeSeconds >= FrameIndex * FrameLength; } int32 FRealTimeCaptureStrategy::GetDroppedFrames(double CurrentTimeSeconds, uint32 FrameIndex) const { uint32 ThisFrame = FMath::FloorToInt(CurrentTimeSeconds / FrameLength); if (ThisFrame > FrameIndex) { return ThisFrame - FrameIndex; } return 0; } #undef LOCTEXT_NAMESPACE