// Copyright Epic Games, Inc. All Rights Reserved. #include "MetaHumanCaptureIngester.h" #include "MetaHumanCaptureSourceLog.h" #include "MetaHumanCaptureEvents.h" #include "FootageIngest/Utils/CommandHandler.h" #include "FootageIngest/LiveLinkFaceFootageIngest.h" #include "FootageIngest/LiveLinkFaceConnectionIngest.h" #include "FootageIngest/HMCArchiveIngest.h" PRAGMA_DISABLE_DEPRECATION_WARNINGS namespace UE::MetaHuman { FIngesterParams::FIngesterParams( EMetaHumanCaptureSourceType InCaptureSourceType, FDirectoryPath InStoragePath, FDeviceAddress InDeviceAddress, uint16 InDeviceControlPort, bool InShouldCompressDepthFiles, bool InCopyImagesToProject, float InMinDistance, float InMaxDistance, EMetaHumanCaptureDepthPrecisionType InDepthPrecision, EMetaHumanCaptureDepthResolutionType InDepthResolution ) : CaptureSourceType(InCaptureSourceType), StoragePath(InStoragePath), DeviceAddress(InDeviceAddress), DeviceControlPort(InDeviceControlPort), ShouldCompressDepthFiles(InShouldCompressDepthFiles), CopyImagesToProject(InCopyImagesToProject), MinDistance(InMinDistance), MaxDistance(InMaxDistance), DepthPrecision(InDepthPrecision), DepthResolution(InDepthResolution) { } FIngester::FIngester(FIngesterParams InIngesterParams) : Params(MoveTemp(InIngesterParams)) { RegisterEvent(FTakeListResetEvent::Name); RegisterEvent(FNewTakesAddedEvent::Name); RegisterEvent(FThumbnailChangedEvent::Name); RegisterEvent(FConnectionChangedEvent::Name); RegisterEvent(FRecordingStatusChangedEvent::Name); RegisterEvent(FTakesRemovedEvent::Name); } void FIngester::SetParams(FIngesterParams InIngesterParams) { Params = MoveTemp(InIngesterParams); } FIngester::~FIngester() { Shutdown(); } EMetaHumanCaptureSourceType FIngester::GetCaptureSourceType() const { return Params.CaptureSourceType; } bool FIngester::CanStartup() const { if (IsProcessing()) { return false; } switch (Params.CaptureSourceType) { case EMetaHumanCaptureSourceType::LiveLinkFaceArchives: case EMetaHumanCaptureSourceType::HMCArchives: return !Params.StoragePath.Path.IsEmpty() && FPaths::DirectoryExists(Params.StoragePath.Path); case EMetaHumanCaptureSourceType::LiveLinkFaceConnection: return !Params.DeviceAddress.IpAddress.IsEmpty(); case EMetaHumanCaptureSourceType::Undefined: default: break; } return false; } bool FIngester::CanIngestTakes() const { return !IsProcessing() && GetNumTakes() > 0; } bool FIngester::CanCancel() const { return IsProcessing() && !IsCancelling(); } void FIngester::Startup(ETakeIngestMode InMode) { // Shutdown the API in case its already valid Shutdown(); // Assert if startup is called with invalid data if (!CanStartup()) { return; } switch (Params.CaptureSourceType) { case EMetaHumanCaptureSourceType::LiveLinkFaceArchives: FootageIngestAPI = MakeUnique(Params.StoragePath.Path, Params.ShouldCompressDepthFiles); break; case EMetaHumanCaptureSourceType::LiveLinkFaceConnection: FootageIngestAPI = MakeUnique( Params.DeviceAddress.IpAddress, Params.DeviceControlPort, Params.ShouldCompressDepthFiles ); break; case EMetaHumanCaptureSourceType::HMCArchives: FootageIngestAPI = MakeUnique( Params.StoragePath.Path, Params.ShouldCompressDepthFiles, Params.CopyImagesToProject, TRange(Params.MinDistance, Params.MaxDistance), Params.DepthPrecision, Params.DepthResolution ); break; case EMetaHumanCaptureSourceType::Undefined: default: // TODO: Log error FootageIngestAPI.Reset(); break; } if (FootageIngestAPI.IsValid()) { FCaptureEventHandler CaptureEventHandler = FCaptureEventHandler( FCaptureEventHandler::Type::CreateRaw(this, &FIngester::ProxyEvent), EDelegateExecutionThread::InternalThread ); // Subscribe to all supported events so we can pass them on FootageIngestAPI->SubscribeToEvent(FTakeListResetEvent::Name, CaptureEventHandler); FootageIngestAPI->SubscribeToEvent(FNewTakesAddedEvent::Name, CaptureEventHandler); FootageIngestAPI->SubscribeToEvent(FThumbnailChangedEvent::Name, CaptureEventHandler); FootageIngestAPI->SubscribeToEvent(FConnectionChangedEvent::Name, CaptureEventHandler); FootageIngestAPI->SubscribeToEvent(FRecordingStatusChangedEvent::Name, CaptureEventHandler); FootageIngestAPI->SubscribeToEvent(FTakesRemovedEvent::Name, CaptureEventHandler); FootageIngestAPI->OnGetTakesFinished().BindLambda([this](const TArray& InTakes) { // Broadcast the newly ingested takes for interested parties OnGetTakesFinishedDelegate.Broadcast(InTakes); }); FootageIngestAPI->Startup(InMode); } } void FIngester::Refresh(FRefreshCallback InCallback) { if (FootageIngestAPI.IsValid()) { IFootageIngestAPI::TCallback Callback = IFootageIngestAPI::TCallback([UserCallback = MoveTemp(InCallback)](TResult InResult) { FMetaHumanCaptureVoidResult Result; Result.SetResult(MoveTemp(InResult)); UserCallback(MoveTemp(Result)); }, EDelegateExecutionThread::InternalThread); FootageIngestAPI->RefreshTakeListAsync(MoveTemp(Callback)); } } void FIngester::SetTargetPath(const FString& InTargetIngestDirectory, const FString& InTargetFolderAssetPath) { if (FootageIngestAPI.IsValid()) { FootageIngestAPI->SetTargetPath(InTargetIngestDirectory, InTargetFolderAssetPath); } } void FIngester::Shutdown() { if (FootageIngestAPI.IsValid()) { FootageIngestAPI->UnsubscribeAll(); FootageIngestAPI->Shutdown(); } } bool FIngester::IsProcessing() const { if (FootageIngestAPI.IsValid()) { return FootageIngestAPI->IsProcessing(); } return false; } bool FIngester::IsCancelling() const { if (FootageIngestAPI.IsValid()) { return FootageIngestAPI->IsCancelling(); } return false; } void FIngester::CancelProcessing(const TArray& InTakeIdList) { if (FootageIngestAPI.IsValid()) { FootageIngestAPI->CancelProcessing(InTakeIdList); } } int32 FIngester::GetNumTakes() const { if (FootageIngestAPI.IsValid()) { return FootageIngestAPI->GetNumTakes(); } return 0; } TArray FIngester::GetTakeIds() const { if (FootageIngestAPI.IsValid()) { return FootageIngestAPI->GetTakeIds(); } return {}; } bool FIngester::GetTakeInfo(TakeId InTakeId, FMetaHumanTakeInfo& OutTakeInfo) const { if (FootageIngestAPI.IsValid()) { OutTakeInfo = FootageIngestAPI->GetTakeInfo(InTakeId); return true; } return false; } bool FIngester::GetTakes(const TArray& InTakeIdList, FGetTakesCallbackPerTake InCallback) { if (FootageIngestAPI.IsValid() && !IsProcessing()) { IFootageIngestAPI::TPerTakeCallback PerTakeCallback = IFootageIngestAPI::TPerTakeCallback([UserCallback = MoveTemp(InCallback)](IFootageIngestAPI::TPerTakeResult InResult) { FMetaHumanCapturePerTakeVoidResult PerTakeResult; PerTakeResult.TakeId = InResult.Key; PerTakeResult.Result.SetResult(MoveTemp(InResult.Value)); UserCallback(MoveTemp(PerTakeResult)); }, EDelegateExecutionThread::InternalThread); FootageIngestAPI->GetTakes(InTakeIdList, MoveTemp(PerTakeCallback)); return true; } return false; } TOptional FIngester::GetProcessingProgress(TakeId InTakeId) const { if (FootageIngestAPI.IsValid()) { return FootageIngestAPI->GetTaskProgress(InTakeId); } return 0.0f; } FText FIngester::GetProcessName(TakeId InTakeId) const { if (FootageIngestAPI.IsValid()) { return FootageIngestAPI->GetTaskName(InTakeId); } return FText(); } bool FIngester::ExecuteCommand(TSharedPtr InCommand) { if (FootageIngestAPI.IsValid()) { return FootageIngestAPI->Execute(MoveTemp(InCommand)); } return false; } void FIngester::ProxyEvent(TSharedPtr Event) { PublishEventPtr(MoveTemp(Event)); } PRAGMA_ENABLE_DEPRECATION_WARNINGS }