internal static async Task ProcessJobAssignmentAsync(AzureAiServiceWorkerRequest @event) { var resourceManager = @event.GetAwsV4ResourceManager(); var table = new DynamoDbTable(@event.StageVariables["TableName"]); var jobAssignmentId = @event.JobAssignmentId; var azure = new AzureConfig(@event); try { // 1. Setting job assignment status to RUNNING await UpdateJobAssignmentStatusAsync(resourceManager, table, jobAssignmentId, "RUNNING", null); // 2. Retrieving WorkflowJob var job = await RetrieveJobAsync(resourceManager, table, jobAssignmentId); // 3. Retrieve JobProfile var jobProfile = await RetrieveJobProfileAsync(resourceManager, job); // 4. Retrieve job inputParameters var jobInput = job.JobInput; // 5. Check if we support jobProfile and if we have required parameters in jobInput ValidateJobProfile(jobProfile, jobInput); S3Locator inputFile; if (!jobInput.TryGet <S3Locator>(nameof(inputFile), out inputFile)) { throw new Exception("Invalid or missing input file."); } string mediaFileUrl; if (!string.IsNullOrWhiteSpace(inputFile.HttpEndpoint)) { mediaFileUrl = inputFile.HttpEndpoint; } else { var bucketLocation = await inputFile.GetBucketLocationAsync(); var s3SubDomain = !string.IsNullOrWhiteSpace(bucketLocation) ? $"s3-{bucketLocation}" : "s3"; mediaFileUrl = $"https://{s3SubDomain}.amazonaws.com/{inputFile.AwsS3Bucket}/{inputFile.AwsS3Key}"; } switch (jobProfile.Name) { case JOB_PROFILE_TRANSCRIBE_AUDIO: case JOB_PROFILE_TRANSLATE_TEXT: throw new NotImplementedException($"{jobProfile.Name} profile has not yet been implemented for Azure."); case JOB_PROFILE_EXTRACT_ALL_AI_METADATA: var authTokenUrl = azure.ApiUrl + "/auth/" + azure.Location + "/Accounts/" + azure.AccountID + "/AccessToken?allowEdit=true"; var customHeaders = new Dictionary <string, string> { ["Ocp-Apim-Subscription-Key"] = azure.SubscriptionKey }; Logger.Debug($"Generate Azure Video Indexer Token: Doing a GET on {authTokenUrl}"); var mcmaHttp = new McmaHttpClient(); var response = await mcmaHttp.GetAsync(authTokenUrl, headers : customHeaders).WithErrorHandling(); var apiToken = await response.Content.ReadAsJsonAsync(); Logger.Debug($"Azure API Token: {apiToken}"); // call the Azure API to process the video // in this scenario the video is located in a public link // so no need to upload the file to Azure /* Sample URL Structure * https://api.videoindexer.ai/{location}/Accounts/{accountId}/Videos? * accessToken={accessToken}& * name={name}?description={string}& * partition={string}& * externalId={string}& * callbackUrl={string}& * metadata={string}& * language={string}& * videoUrl={string}& * fileName={string}& * indexingPreset={string}& * streamingPreset=Default& * linguisticModelId={string}& * privacy={string}& * externalUrl={string}" */ var secureHost = new Uri(jobAssignmentId, UriKind.Absolute).Host; var nonSecureHost = new Uri(@event.StageVariables["PublicUrlNonSecure"], UriKind.Absolute).Host; var callbackUrl = Uri.EscapeDataString(jobAssignmentId.Replace(secureHost, nonSecureHost) + "/notifications"); var postVideoUrl = azure.ApiUrl + "/" + azure.Location + "/Accounts/" + azure.AccountID + "/Videos?accessToken=" + apiToken + "&name=" + inputFile.AwsS3Key + "&callbackUrl=" + callbackUrl + "&videoUrl=" + mediaFileUrl + "&fileName=" + inputFile.AwsS3Key; Logger.Debug($"Call Azure Video Indexer API: Doing a POST on {postVideoUrl}"); var postVideoResponse = await mcmaHttp.PostAsync(postVideoUrl, null, customHeaders).WithErrorHandling(); var azureAssetInfo = await postVideoResponse.Content.ReadAsJsonAsync(); Logger.Debug("azureAssetInfo: ", azureAssetInfo); try { var jobOutput = new JobParameterBag(); jobOutput["jobInfo"] = azureAssetInfo; await UpdateJobAssignmentWithOutputAsync(table, jobAssignmentId, jobOutput); } catch (Exception error) { Logger.Error("Error updating the job", error); } break; } } catch (Exception ex) { Logger.Exception(ex); try { await UpdateJobAssignmentStatusAsync(resourceManager, table, jobAssignmentId, "FAILED", ex.ToString()); } catch (Exception innerEx) { Logger.Exception(innerEx); } } }
public static async Task ProcessNotificationAsync(AzureAiServiceWorkerRequest @event) { var jobAssignmentId = @event.JobAssignmentId; var resourceManager = @event.GetAwsV4ResourceManager(); var table = new DynamoDbTable(@event.StageVariables["TableName"]); var azure = new AzureConfig(@event); var azureVideoId = @event.Notification?.Id; var azureState = @event.Notification?.State; if (azureVideoId == null || azureState == null) { Logger.Warn("POST is not coming from Azure Video Indexer. Expected notification to have id and state properties."); return; } try { var authTokenUrl = azure.ApiUrl + "/auth/" + azure.Location + "/Accounts/" + azure.AccountID + "/AccessToken?allowEdit=true"; var customHeaders = new Dictionary <string, string> { ["Ocp-Apim-Subscription-Key"] = azure.SubscriptionKey }; Logger.Debug($"Generate Azure Video Indexer Token: Doing a GET on {authTokenUrl}"); var mcmaHttp = new McmaHttpClient(); var response = await mcmaHttp.GetAsync(authTokenUrl, headers : customHeaders).WithErrorHandling(); var apiToken = await response.Content.ReadAsJsonAsync(); Logger.Debug($"Azure API Token: {apiToken}"); var metadataFromAzureVideoIndexer = $"{azure.ApiUrl}/{azure.Location}/Accounts/{azure.AccountID}/Videos/{azureVideoId}/Index?accessToken={apiToken}&language=English"; Logger.Debug($"Getting Azure video metadata from: {metadataFromAzureVideoIndexer}"); var indexedVideoMetadataResponse = await mcmaHttp.GetAsync(metadataFromAzureVideoIndexer, headers : customHeaders).WithErrorHandling(); var videoMetadata = await indexedVideoMetadataResponse.Content.ReadAsJsonAsync(); Logger.Debug($"Azure AI video metadata: {videoMetadata}"); //Need to hydrate the destination bucket from the job input var workflowJob = await RetrieveJobAsync(resourceManager, table, jobAssignmentId); //Retrieve job inputParameters var jobInput = workflowJob.JobInput; S3Locator outputLocation; if (!jobInput.TryGet <S3Locator>(nameof(outputLocation), out outputLocation)) { throw new Exception("Invalid or missing output location."); } var jobOutputBucket = outputLocation.AwsS3Bucket; var jobOutputKeyPrefix = outputLocation.AwsS3KeyPrefix != null ? outputLocation.AwsS3KeyPrefix : ""; // get the info about the destination bucket to store the result of the job var s3Params = new PutObjectRequest { BucketName = jobOutputBucket, Key = jobOutputKeyPrefix + azureVideoId + "-" + Guid.NewGuid() + ".json", ContentBody = videoMetadata.ToString(), ContentType = "application/json" }; var destS3 = await outputLocation.GetClientAsync(); await destS3.PutObjectAsync(s3Params); //updating JobAssignment with jobOutput var jobOutput = workflowJob.JobOutput ?? new JobParameterBag(); jobOutput["outputFile"] = new S3Locator { AwsS3Bucket = s3Params.BucketName, AwsS3Key = s3Params.Key }; await UpdateJobAssignmentWithOutputAsync(table, jobAssignmentId, jobOutput); // Setting job assignment status to COMPLETED await UpdateJobAssignmentStatusAsync(resourceManager, table, jobAssignmentId, "COMPLETED"); } catch (Exception error) { Logger.Exception(error); try { await UpdateJobAssignmentStatusAsync(resourceManager, table, jobAssignmentId, "FAILED", error.ToString()); } catch (Exception innerEx) { Logger.Exception(innerEx); } } }