private static void ValidateJobProfile(JobProfile jobProfile, JobParameterBag jobInput) { if (jobProfile.Name != JOB_PROFILE_CONFORM_WORKFLOW && jobProfile.Name != JOB_PROFILE_AI_WORKFLOW) { throw new Exception("JobProfile '" + jobProfile.Name + "' is not supported"); } if (jobProfile.InputParameters != null) { foreach (var parameter in jobProfile.InputParameters) { if (!jobInput.HasProperty(parameter.ParameterName)) { throw new Exception("jobInput misses required input parameter '" + parameter.ParameterName + "'"); } } } }
private static void ValidateJobProfile(JobProfile jobProfile, JobParameterBag jobInput) { if (jobProfile.Name != JOB_PROFILE_TRANSCRIBE_AUDIO && jobProfile.Name != JOB_PROFILE_TRANSLATE_TEXT && jobProfile.Name != JOB_PROFILE_DETECT_CELEBRITIES) { throw new Exception("JobProfile '" + jobProfile.Name + "' is not supported"); } if (jobProfile.InputParameters != null) { foreach (var parameter in jobProfile.InputParameters) { if (!jobInput.HasProperty(parameter.ParameterName)) { throw new Exception("jobInput misses required input parameter '" + parameter.ParameterName + "'"); } } } }
private static async Task UpdateJobAssignmentWithOutputAsync(DynamoDbTable table, string jobAssignmentId, JobParameterBag jobOutput) { var jobAssignment = await GetJobAssignmentAsync(table, jobAssignmentId); jobAssignment.JobOutput = jobOutput; await PutJobAssignmentAsync(null, table, jobAssignmentId, jobAssignment); }
internal static async Task ProcessJobAssignmentAsync(AwsAiServiceWorkerRequest @event) { var resourceManager = @event.GetAwsV4ResourceManager(); var table = new DynamoDbTable(@event.StageVariables["TableName"]); var jobAssignmentId = @event.JobAssignmentId; 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."); } S3Locator outputLocation; if (!jobInput.TryGet <S3Locator>(nameof(outputLocation), out outputLocation)) { throw new Exception("Invalid or missing output location."); } switch (jobProfile.Name) { case JOB_PROFILE_TRANSCRIBE_AUDIO: 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}"; } string mediaFormat; if (mediaFileUrl.EndsWith("mp3", StringComparison.OrdinalIgnoreCase)) { mediaFormat = "mp3"; } else if (mediaFileUrl.EndsWith("mp4", StringComparison.OrdinalIgnoreCase)) { mediaFormat = "mp4"; } else if (mediaFileUrl.EndsWith("wav", StringComparison.OrdinalIgnoreCase)) { mediaFormat = "wav"; } else if (mediaFileUrl.EndsWith("flac", StringComparison.OrdinalIgnoreCase)) { mediaFormat = "flac"; } else { throw new Exception($"Unable to determine media format from input file '{mediaFileUrl}'"); } var transcribeParameters = new StartTranscriptionJobRequest { TranscriptionJobName = "TranscriptionJob-" + jobAssignmentId.Substring(jobAssignmentId.LastIndexOf("/") + 1), LanguageCode = "en-US", Media = new Media { MediaFileUri = mediaFileUrl }, MediaFormat = mediaFormat, OutputBucketName = @event.StageVariables["ServiceOutputBucket"] }; var transcribeService = new AmazonTranscribeServiceClient(); var startJobResponse = await transcribeService.StartTranscriptionJobAsync(transcribeParameters); Logger.Debug(startJobResponse.ToMcmaJson().ToString()); break; case JOB_PROFILE_TRANSLATE_TEXT: var s3Bucket = inputFile.AwsS3Bucket; var s3Key = inputFile.AwsS3Key; GetObjectResponse s3Object; try { s3Object = await inputFile.GetAsync(); } catch (Exception error) { throw new Exception($"Unable to read file in bucket '{s3Bucket}' with key '{s3Key}'.", error); } var inputText = await new StreamReader(s3Object.ResponseStream).ReadToEndAsync(); var translateParameters = new TranslateTextRequest { SourceLanguageCode = jobInput.TryGet("sourceLanguageCode", out string srcLanguageCode) ? srcLanguageCode : "auto", TargetLanguageCode = jobInput.Get <string>("targetLanguageCode"), Text = inputText }; var translateService = new AmazonTranslateClient(); var translateResponse = await translateService.TranslateTextAsync(translateParameters); var s3Params = new PutObjectRequest { BucketName = outputLocation.AwsS3Bucket, Key = (!string.IsNullOrWhiteSpace(outputLocation.AwsS3KeyPrefix) ? outputLocation.AwsS3Key : string.Empty) + Guid.NewGuid() + ".txt", ContentBody = translateResponse.TranslatedText }; var outputS3 = await outputLocation.GetClientAsync(); await outputS3.PutObjectAsync(s3Params); var jobOutput = new JobParameterBag(); jobOutput["outputFile"] = new S3Locator { AwsS3Bucket = s3Params.BucketName, AwsS3Key = s3Params.Key }; Logger.Debug("Updating job assignment"); await UpdateJobAssignmentWithOutputAsync(table, jobAssignmentId, jobOutput); await UpdateJobAssignmentStatusAsync(resourceManager, table, jobAssignmentId, "COMPLETED"); break; case JOB_PROFILE_DETECT_CELEBRITIES: var randomBytes = new byte[16]; new Random().NextBytes(randomBytes); var clientToken = randomBytes.HexEncode(); var base64JobId = Encoding.UTF8.GetBytes(jobAssignmentId).HexEncode(); var rekoParams = new StartCelebrityRecognitionRequest { Video = new Video { S3Object = new Amazon.Rekognition.Model.S3Object { Bucket = inputFile.AwsS3Bucket, Name = inputFile.AwsS3Key } }, ClientRequestToken = clientToken, JobTag = base64JobId, NotificationChannel = new NotificationChannel { RoleArn = REKO_SNS_ROLE_ARN, SNSTopicArn = SNS_TOPIC_ARN } }; var rekognitionClient = new AmazonRekognitionClient(); var startCelebRecognitionResponse = await rekognitionClient.StartCelebrityRecognitionAsync(rekoParams); Logger.Debug(startCelebRecognitionResponse.ToMcmaJson().ToString()); 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 ProcessRekognitionResultAsync(AwsAiServiceWorkerRequest @event) { var resourceManager = @event.GetAwsV4ResourceManager(); var table = new DynamoDbTable(@event.StageVariables["TableName"]); var jobAssignmentId = @event.JobAssignmentId; var job = await RetrieveJobAsync(resourceManager, table, jobAssignmentId); try { var jobInput = job.JobInput; S3Locator outputLocation; if (!jobInput.TryGet <S3Locator>(nameof(outputLocation), out outputLocation)) { throw new Exception("Invalid or missing output location."); } var s3Bucket = outputLocation.AwsS3Bucket; var rekoJobId = @event.JobExternalInfo.RekoJobId; var rekoJobType = @event.JobExternalInfo.RekoJobType; var status = @event.JobExternalInfo.Status; if (status != "SUCCEEDED") { throw new Exception($"AI Rekognition failed job info: rekognition status:" + status); } object data = null; switch (rekoJobType) { case "StartCelebrityRecognition": var getCelebRecognitionParams = new GetCelebrityRecognitionRequest { JobId = rekoJobId, /* required */ MaxResults = 1000000, SortBy = "TIMESTAMP" }; var rekognitionClient = new AmazonRekognitionClient(); data = await rekognitionClient.GetCelebrityRecognitionAsync(getCelebRecognitionParams); break; case "StartLabelDetection": throw new NotImplementedException("StartLabelDetection has not yet been implemented"); case "StartContentModeration": throw new NotImplementedException("StartContentModeration has not yet been implemented"); case "StartPersonTracking": throw new NotImplementedException("StartPersonTracking has not yet been implemented"); case "StartFaceDetection": throw new NotImplementedException("StartLabelDetection has not yet been implemented"); case "StartFaceSearch": throw new NotImplementedException("StartLabelDetection has not yet been implemented"); default: throw new Exception($"Unknown job type '{rekoJobType}'"); } if (data == null) { throw new Exception($"No data was returned by AWS Rekognition"); } var newS3Key = $"reko_{Guid.NewGuid()}.json"; var s3Params = new PutObjectRequest { BucketName = outputLocation.AwsS3Bucket, Key = newS3Key, ContentBody = data.ToMcmaJson().ToString(), ContentType = "application/json" }; try { var destS3 = await outputLocation.GetClientAsync(); await destS3.PutObjectAsync(s3Params); } catch (Exception error) { Logger.Error("Unable to write output file to bucket '" + s3Bucket + "' with key '" + newS3Key + "'"); Logger.Exception(error); } var jobOutput = new JobParameterBag(); jobOutput["outputFile"] = new S3Locator { AwsS3Bucket = s3Bucket, AwsS3Key = newS3Key }; await UpdateJobAssignmentWithOutputAsync(table, jobAssignmentId, jobOutput); await UpdateJobAssignmentStatusAsync(resourceManager, table, jobAssignmentId, "COMPLETED"); } catch (Exception error) { Logger.Exception(error); try { await UpdateJobAssignmentStatusAsync(resourceManager, table, jobAssignmentId, "FAILED", error.ToString()); } catch (Exception innerError) { Logger.Exception(innerError); } } }
public static async Task ProcessTranscribeJobResultAsync(AwsAiServiceWorkerRequest @event) { var resourceManager = @event.GetAwsV4ResourceManager(); var table = new DynamoDbTable(@event.StageVariables["TableName"]); var jobAssignmentId = @event.JobAssignmentId; var job = await RetrieveJobAsync(resourceManager, table, jobAssignmentId); try { var jobInput = job.JobInput; S3Locator outputLocation; if (!jobInput.TryGet <S3Locator>(nameof(outputLocation), out outputLocation)) { throw new Exception("Invalid or missing output location."); } var copySource = Uri.EscapeDataString(@event.OutputFile.AwsS3Bucket + "/" + @event.OutputFile.AwsS3Key); var s3Bucket = outputLocation.AwsS3Bucket; var s3Key = (!string.IsNullOrWhiteSpace(outputLocation.AwsS3KeyPrefix) ? outputLocation.AwsS3KeyPrefix : string.Empty) + @event.OutputFile.AwsS3Key; try { var destS3 = await outputLocation.GetClientAsync(); await destS3.CopyObjectAsync(new CopyObjectRequest { SourceBucket = @event.OutputFile.AwsS3Bucket, SourceKey = @event.OutputFile.AwsS3Key, DestinationBucket = s3Bucket, DestinationKey = s3Key }); } catch (Exception error) { throw new Exception("Unable to copy output file to bucket '" + s3Bucket + "' with key'" + s3Key + "'", error); } var jobOutput = new JobParameterBag(); jobOutput["outputFile"] = new S3Locator { AwsS3Bucket = s3Bucket, AwsS3Key = s3Key }; await UpdateJobAssignmentWithOutputAsync(table, jobAssignmentId, jobOutput); await UpdateJobAssignmentStatusAsync(resourceManager, table, jobAssignmentId, "COMPLETED"); } catch (Exception error) { Logger.Exception(error); try { await UpdateJobAssignmentStatusAsync(resourceManager, table, jobAssignmentId, "FAILED", error.ToString()); } catch (Exception innerError) { Logger.Exception(innerError); } } // Cleanup: Deleting original output file try { var sourceS3 = await @event.OutputFile.GetClientAsync(); await sourceS3.DeleteObjectAsync(new DeleteObjectRequest { BucketName = @event.OutputFile.AwsS3Bucket, Key = @event.OutputFile.AwsS3Key, }); } catch (Exception error) { Logger.Error("Failed to cleanup transcribe output file."); Logger.Exception(error); } }
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); } } }
internal static async Task ProcessJobAssignmentAsync(TransformServiceWorkerRequest @event) { var resourceManager = @event.Request.GetAwsV4ResourceManager(); var table = new DynamoDbTable(@event.Request.StageVariables["TableName"]); var jobAssignmentId = @event.JobAssignmentId; try { // 1. Setting job assignment status to RUNNING await UpdateJobAssignmentStatusAsync(resourceManager, table, jobAssignmentId, "RUNNING", null); // 2. Retrieving WorkflowJob var transformJob = await RetrieveTransformJobAsync(resourceManager, table, jobAssignmentId); // 3. Retrieve JobProfile var jobProfile = await RetrieveJobProfileAsync(resourceManager, transformJob); // 4. Retrieve job inputParameters var jobInput = transformJob.JobInput; // 5. Check if we support jobProfile and if we have required parameters in jobInput ValidateJobProfile(jobProfile, jobInput); switch (jobProfile.Name) { case JOB_PROFILE_CREATE_PROXY_LAMBDA: S3Locator inputFile; if (!jobInput.TryGet <S3Locator>(nameof(inputFile), out inputFile)) { throw new Exception("Invalid or missing input file."); } S3Locator outputLocation; if (!jobInput.TryGet <S3Locator>(nameof(outputLocation), out outputLocation)) { throw new Exception("Invalid or missing output location."); } if (string.IsNullOrWhiteSpace(inputFile.AwsS3Bucket) || string.IsNullOrWhiteSpace(inputFile.AwsS3Key)) { throw new Exception("Not able to obtain input file"); } var data = await inputFile.GetAsync(); var localFileName = "/tmp/" + Guid.NewGuid(); await data.WriteResponseStreamToFileAsync(localFileName, true, CancellationToken.None); var tempFileName = "/tmp/" + Guid.NewGuid() + ".mp4"; var ffmpegParams = new[] { "-y", "-i", localFileName, "-preset", "ultrafast", "-vf", "scale=-1:360", "-c:v", "libx264", "-pix_fmt", "yuv420p", tempFileName }; var ffmpegProcess = await FFmpegProcess.RunAsync(ffmpegParams); File.Delete(localFileName); var s3Params = new PutObjectRequest { BucketName = outputLocation.AwsS3Bucket, Key = (outputLocation.AwsS3KeyPrefix ?? string.Empty) + Guid.NewGuid().ToString() + ".mp4", FilePath = tempFileName, ContentType = "video/mp4" }; var outputS3 = await outputLocation.GetClientAsync(); var putResp = await outputS3.PutObjectAsync(s3Params); var jobOutput = new JobParameterBag(); jobOutput["outputFile"] = new S3Locator { AwsS3Bucket = s3Params.BucketName, AwsS3Key = s3Params.Key }; await UpdateJobAssignmentWithOutputAsync(table, jobAssignmentId, jobOutput); await UpdateJobAssignmentStatusAsync(resourceManager, table, jobAssignmentId, "COMPLETED"); break; case JOB_PROFILE_CREATE_PROXY_EC2: var ec2hostname = @event.Request.StageVariables["HostnameInstanceEC2"]; var ec2Url = "http://" + ec2hostname + "/new-transform-job"; var message = new { input = jobInput, notificationEndpoint = new NotificationEndpoint { HttpEndpoint = jobAssignmentId + "/notifications" } }; Logger.Debug("Sending to", ec2Url, "message", message); var mcmaHttp = new McmaHttpClient(); await mcmaHttp.PostAsJsonAsync(ec2Url, message); Logger.Debug("Done"); break; } } catch (Exception ex) { Logger.Exception(ex); try { await UpdateJobAssignmentStatusAsync(resourceManager, table, jobAssignmentId, "FAILED", ex.ToString()); } catch (Exception innerEx) { Logger.Exception(innerEx); } } finally { Logger.Debug("Exiting TransformServiceWorker.ProcessJobAssignmentAsync"); } }