示例#1
0
        /// <inheritdoc/>
        public string GetSasUrlForBlob(Uri blobUri, TimeSpan ttl, StorageClientProviderContext context)
        {
            _ = blobUri ?? throw new ArgumentNullException(nameof(blobUri));
            _ = context ?? throw new ArgumentNullException(nameof(context));

            try
            {
                var blobUriBuilder = new BlobUriBuilder(blobUri);

                // Create a SAS token that's valid for the TimeSpan, plus a back-off start for clock skew.
                var            timeRange  = StorageHelpers.CreateTimeRangeForUrl(ttl);
                BlobSasBuilder sasBuilder = new BlobSasBuilder
                {
                    BlobContainerName = blobUriBuilder.BlobContainerName,
                    BlobName          = blobUriBuilder.BlobName,
                    Resource          = "b", // "b" is for blob
                    StartsOn          = timeRange.StartTime,
                    ExpiresOn         = timeRange.EndTime,
                }.UnescapeTargetPath();                             // Important adjustment(s) for SAS computation

                sasBuilder.SetPermissions(BlobSasPermissions.Read); // read permissions only for the SAS.

                var sleeve         = _blobBaseClientProvider.GetBlobClientSleeveForUri(blobUri, context);
                var userDelegation = sleeve.Service.GetUserDelegationKey(sasBuilder.StartsOn, sasBuilder.ExpiresOn)?.Value;

                if (userDelegation == null)
                {
                    var msg = $@"Unable to get a user delegation key from the Storage service for blob {blobUri}";
                    _log.LogEvent(LogEventIds.StorageServiceMissingConnectionString, msg);
                    throw new GridwichStorageServiceException(blobUri, msg, LogEventIds.StorageServiceMissingConnectionString, context.ClientRequestIdAsJObject);
                }

                var sasToken = sasBuilder.ToSasQueryParameters(userDelegation, blobUriBuilder.AccountName);
                blobUriBuilder.Sas = sasToken;

                // Construct the full URI, including the SAS token. AbsoluteUri (vs. ToString) is to ensure the %HH escaping is used.
                return(blobUriBuilder.ToUri().AbsoluteUri);
            }
            catch (RequestFailedException e)
            {
                var msg = $@"Unable to get a user delegation key from the Storage service for blob {blobUri}";
                _log.LogEvent(LogEventIds.StorageServiceMissingConnectionString, msg);
                throw new GridwichStorageServiceException(blobUri, msg, LogEventIds.StorageServiceMissingConnectionString, context.ClientRequestIdAsJObject, e);
            }
            catch (Exception e)
            {
                _log.LogExceptionObject(LogEventIds.FailedToCreateBlobSasUriInStorageService, e, blobUri);
                throw new GridwichStorageServiceException(blobUri, "Failed to generate the SAS url.",
                                                          LogEventIds.FailedToCreateBlobSasUriInStorageService, context.ClientRequestIdAsJObject, e);
            }
        }
        private void InitializeSDKs()
        {
            var apiKey = _settingsProvider.GetAppSettingsValue(EnvKey);

            if (string.IsNullOrWhiteSpace(apiKey))
            {
                var message = $"{EnvKey}: is null or empty";
                _log.LogEvent(LogEventIds.CloudPortApiError, message);
                throw new ArgumentNullException(LogEventIds.CloudPortApiError.Name, message);
            }
            Telestream.Cloud.Stores.Client.Configuration storesConfiguration = new Telestream.Cloud.Stores.Client.Configuration();
            storesConfiguration.ApiKey.Add("X-Api-Key", apiKey);
            CloudPortStoresApi = new StoresApi(storesConfiguration);

            // Notifications are not being used just yet, but we include them now for future possibilies.
            Telestream.Cloud.Notifications.Client.Configuration notificationsConfiguration = new Telestream.Cloud.Notifications.Client.Configuration();
            notificationsConfiguration.ApiKey.Add("X-Api-Key", apiKey);
            CloudPortNotificationsApi = new NotificationsApi(notificationsConfiguration);

            Telestream.Cloud.VantageCloudPort.Client.Configuration cloudPortConfiguration = new Telestream.Cloud.VantageCloudPort.Client.Configuration();
            cloudPortConfiguration.ApiKey.Add("X-Api-Key", apiKey);
            CloudPortApi = new Telestream.Cloud.VantageCloudPort.Api.VantageCloudPortApi(cloudPortConfiguration);

            Telestream.Cloud.Flip.Client.Configuration flipConfiguration = new Telestream.Cloud.Flip.Client.Configuration();
            flipConfiguration.ApiKey.Add("X-Api-Key", apiKey);
            FlipApi = new FlipApi(flipConfiguration);
        }
        public IActionResult Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)]
            HttpRequest req)
        {
            var errorMessage = string.Empty;

            using var mediaInfoLib = _mediaInfoProvider.GetMediaInfoLib(StorageClientProviderContext.None);
            string libraryVersion = mediaInfoLib?.GetOption("Info_Version", "0.7.0.0;MediaInfoDLL_Example_CS;0.7.0.0");

            if (string.IsNullOrEmpty(libraryVersion))
            {
                if (!string.IsNullOrEmpty(errorMessage))
                {
                    return(new NotFoundObjectResult(errorMessage));
                }

                libraryVersion = $"MediaInfo.Dll: this version of the DLL is not compatible. {req?.Host}";
                _logger.LogEvent(LogEventIds.MediaInfoIncompatibleDllVersion, libraryVersion);
                return(new NotFoundObjectResult($"{libraryVersion}"));
            }

            _logger.LogEvent(out var telemetryLink, LogEventIds.MediaInfoCompatibleDllVersion, libraryVersion);
            return(new ContentResult
            {
                Content = $"<HTML><BODY>{libraryVersion}  {req?.Host} <a href=\"{telemetryLink}\">Link to telemetry</a></BODY></HTML>",
                ContentType = @"text/html",
                StatusCode = StatusCodes.Status200OK
            });
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="MediaServicesV3ContentKeyPolicyService"/> class.
        /// </summary>
        /// <param name="settingsProvider">Settings provider.</param>
        /// <param name="log">Log provider.</param>
        /// <param name="identity">Identity provider.</param>
        public MediaServicesV3ContentKeyPolicyService(ISettingsProvider settingsProvider, IObjectLogger <MediaServicesV3ContentKeyPolicyService> log)
        {
            _log = log ?? throw new ArgumentNullException(nameof(log));
            _    = settingsProvider ?? throw new ArgumentNullException(nameof(settingsProvider));

            _amsDrmOpenIdConnectDiscoveryDocument = settingsProvider.GetAppSettingsValue("AmsDrmOpenIdConnectDiscoveryDocument");
            _amsDrmFairPlayPfxPassword            = settingsProvider.GetAppSettingsValue("AmsDrmFairPlayPfxPassword");
            _amsDrmFairPlayAskHex         = settingsProvider.GetAppSettingsValue("AmsDrmFairPlayAskHex");
            _amsDrmFairPlayCertificateB64 = settingsProvider.GetAppSettingsValue("AmsDrmFairPlayCertificateB64");

            if (string.IsNullOrEmpty(_amsDrmOpenIdConnectDiscoveryDocument))
            {
                const string Message = "AmsDrmOpenIdConnectDiscoveryDocument is null or empty.";
                _log.LogEvent(LogEventIds.MediaServicesV3ConfigurationError, Message);
                throw new ArgumentException(Message);
            }

            if (string.IsNullOrEmpty(_amsDrmFairPlayPfxPassword))
            {
                const string Message = "AmsDrmFairPlayPfxPassword is null or empty.";
                _log.LogEvent(LogEventIds.MediaServicesV3ConfigurationError, Message);
                throw new ArgumentException(Message);
            }

            if (string.IsNullOrEmpty(_amsDrmFairPlayAskHex))
            {
                const string Message = "AmsDrmFairPlayAskHex is null or empty.";
                _log.LogEvent(LogEventIds.MediaServicesV3ConfigurationError, Message);
                throw new ArgumentException(Message);
            }

            if (string.IsNullOrEmpty(_amsDrmFairPlayCertificateB64))
            {
                const string Message = "AmsDrmFairPlayCertificateB64 is null or empty.";
                _log.LogEvent(LogEventIds.MediaServicesV3ConfigurationError, Message);
                throw new ArgumentException(Message);
            }
        }
示例#5
0
        public async Task <IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
            CancellationToken cancellationToken)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                _logger.LogEvent(LogEventIds.FunctionAppShuttingDown, LogEventIds.FunctionAppShuttingDown.Name);
                throw new OperationCanceledException("Function invoked with a canceled cancellation token.");
            }

            if (req == null)
            {
                _logger.LogEvent(LogEventIds.EventGridFunctionGotNullHttpRequest, string.Empty);
                return(new BadRequestObjectResult(LogEventIds.EventGridFunctionGotNullHttpRequest.Name));
            }

            if (req.Body == null)
            {
                _logger.LogEvent(LogEventIds.EventGridFunctionGotNullHttpRequestBody, string.Empty);
                return(new BadRequestObjectResult(LogEventIds.EventGridFunctionGotNullHttpRequestBody.Name));
            }

            string requestBody = string.Empty;

            try
            {
                using var sr = new StreamReader(req.Body);
                requestBody  = await sr.ReadToEndAsync().ConfigureAwait(true);
            }
            catch (Exception e) when(
                e is ArgumentException ||
                e is ArgumentOutOfRangeException ||
                e is ObjectDisposedException ||
                e is InvalidOperationException)
            {
                // TODO: Tests for these expection types
                _logger.LogEventObject(LogEventIds.EventGridFunctionExceptionReadingHttpRequestBody, e);
                string msg = $"{LogEventIds.EventGridFunctionExceptionReadingHttpRequestBody} {e.Message}";

                return(new BadRequestObjectResult(msg));
            }

            if (string.IsNullOrEmpty(requestBody))
            {
                _logger.LogEvent(LogEventIds.EventGridFunctionGotEmptyBody, string.Empty);
                return(new BadRequestObjectResult(LogEventIds.EventGridFunctionGotEmptyBody.Name));
            }

            List <EventGridEvent> eventGridEvents;

            try
            {
                eventGridEvents = JsonConvert.DeserializeObject <List <EventGridEvent> >(requestBody);
            }
            catch (JsonException e)
            {
                _logger.LogEventObject(LogEventIds.EventGridFunctionGotUnparsableBody, e);
                string msg = $"{LogEventIds.EventGridFunctionGotUnparsableBody} {e.Message}";
                return(new BadRequestObjectResult(msg));
            }

            if (eventGridEvents.Count == 0)
            {
                _logger.LogEvent(LogEventIds.EventGridFunctionGotEmptyArrayAsBody, string.Empty);
                return(new BadRequestObjectResult(LogEventIds.EventGridFunctionGotEmptyArrayAsBody.Name));
            }
            try
            {
                cancellationToken.Register(() =>
                {
                    _logger.LogEvent(LogEventIds.FunctionAppShuttingDown, LogEventIds.FunctionAppShuttingDown.Name);
                });

                return((IActionResult)await _eventDispatcher.DispatchEventGridEvents(eventGridEvents).ConfigureAwait(true));
            }
            catch (OperationCanceledException oce)
            {
                _logger.LogEventObject(LogEventIds.OperationCancelException, oce);
                throw;
            }
        }
        /// <summary>
        /// EncodeCreateAsync does the heavy lifting to encode and process a video asset in Azure Media Services V3.
        /// </summary>
        /// <param name="encodeCreateDTO">Data payload for creating an AMS V3 encode.</param>
        /// <returns>Returns true if encode request was successful, otherwise false.</returns>
        public async Task <ServiceOperationResultEncodeDispatched> EncodeCreateAsync(RequestMediaServicesV3EncodeCreateDTO encodeCreateDTO)
        {
            _ = encodeCreateDTO ?? throw new ArgumentNullException(nameof(encodeCreateDTO));

            // 1. Get or create the transform
            // 2. Create input asset
            // 3. Create output asset
            // 4. Call the encode
            // 5. Confirm success to base class

            var operationContext = encodeCreateDTO.OperationContext;

            // 1. Get or create the transform
            try
            {
                if (string.IsNullOrWhiteSpace(encodeCreateDTO.TransformName))
                {
                    throw new ArgumentOutOfRangeException(nameof(encodeCreateDTO), "TransformName can not be null or whitespace");
                }
            }
            catch (Exception e)
            {
                throw new GridwichEncodeCreateDataException("Invalid inputs found in EncodeCreateDTO.", encodeCreateDTO.OperationContext, e, LogEventIds.MediaServicesV3EncodeCreateDtoError);
            }

            try
            {
                await _amsV3Service.CreateTransformIfNotExistByNameAsync(encodeCreateDTO.TransformName, operationContext).ConfigureAwait(false);
            }
            catch (Exception e)
            {
                throw new GridwichMediaServicesV3CreateTransformException($"Error creating AMS V3 Transform: {encodeCreateDTO.TransformName}", LogEventIds.MediaServicesV3TransformError, operationContext, e);
            }

            // 2. Create input asset
            var sourceUris = Enumerable.Empty <Uri>();

            try
            {
                sourceUris = encodeCreateDTO.Inputs.Select(u => new Uri(u.BlobUri, UriKind.Absolute));
                if (!sourceUris.Any() || sourceUris.Count() != encodeCreateDTO.Inputs.Count())
                {
                    throw new ArgumentException("Could not parse Inputs.", nameof(encodeCreateDTO));
                }

                // Check that all the blobs actually exist.

                // Create a new muted context for these operations, based on the Op context if available.
                var internalCorrelator = (operationContext != null) ? operationContext.DeepClone() as JObject : new JObject();
                internalCorrelator.Add("~AMS-V3-Encode", $"G:{Guid.NewGuid()}");
                var context = new StorageClientProviderContext(internalCorrelator, muted: true);

                foreach (var blobUri in sourceUris)
                {
                    var exists = await _storageService.GetBlobExistsAsync(blobUri, context).ConfigureAwait(false);

                    if (!exists)
                    {
                        _log.LogEventObject(LogEventIds.MediaServicesV3AttemptToUseNonexistentBlob, blobUri);
                        throw new GridwichMediaServicesV3Exception($"Attempt to use nonexistent blob as input: {blobUri}",
                                                                   LogEventIds.MediaServicesV3AttemptToUseNonexistentBlob, context.ClientRequestIdAsJObject);
                    }
                }
            }
            catch (Exception e) when(!(e is GridwichMediaServicesV3Exception))
            {
                throw new GridwichEncodeCreateDataException("Invalid inputs found in EncodeCreateDTO.", encodeCreateDTO.OperationContext, e, LogEventIds.MediaServicesV3EncodeCreateDtoError);
            }

            string inputAssetName = null;

            try
            {
                inputAssetName = await _amsV3Service.CreateOrUpdateAssetForContainerAsync(sourceUris).ConfigureAwait(false);
            }
            catch (Exception e)
            {
                throw new GridwichMediaServicesV3CreateAssetException("Error creating/getting AMS V3 Asset.", LogEventIds.MediaServicesV3InputAssetError, operationContext, e);
            }

            // 4. Create output asset.
            Uri outputUri = null;

            try
            {
                outputUri = new Uri(encodeCreateDTO.OutputContainer, UriKind.Absolute);
            }
            catch (Exception e)
            {
                throw new GridwichEncodeInvalidOutputContainerException($"Invalid output container specified: {encodeCreateDTO.OutputContainer}", operationContext, e, LogEventIds.MediaServicesV3OutputError);
            }

            string outputAssetName = null;

            try
            {
                outputAssetName = await _amsV3Service.CreateOrUpdateAssetForContainerAsync(Enumerable.Empty <Uri>().Append(outputUri)).ConfigureAwait(false);
            }
            catch (Exception e)
            {
                throw new GridwichMediaServicesV3CreateAssetException($"Error creating/getting AMS V3 output Asset: {outputUri}.", LogEventIds.MediaServicesV3OutputError, operationContext, e);
            }

            // 5. Execute Encode
            var uniqueness = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture).Substring(0, 11);
            var jobName    = outputAssetName + "-" + uniqueness;

            try
            {
                await _amsV3Service.CreateJobAsync(encodeCreateDTO.TransformName,
                                                   inputAssetName,
                                                   outputAssetName,
                                                   jobName,
                                                   encodeCreateDTO.TimeBasedEncode,
                                                   new Dictionary <string, string>()
                {
                    { "mediaServicesV3EncoderSpecificData", JsonConvert.SerializeObject(encodeCreateDTO) },
                    { "inputAssetName", inputAssetName },
                    { "outputAssetContainer", encodeCreateDTO.OutputContainer },
                    { "outputAssetName", outputAssetName },
                    { "operationContext", encodeCreateDTO.OperationContext.ToString() }
                },
                                                   operationContext).ConfigureAwait(false);
            }
            catch (Exception e)
            {
                throw new GridwichEncodeCreateJobException($"Error creating AMS V3 job: {jobName}", operationContext, e, LogEventIds.MediaServicesV3CreateJobApiError);
            }

            // 6. Confirm success to base class
            _log.LogEvent(LogEventIds.MediaServicesV3JobSubmitCalled, $"AMS V3 job successfully logged: {jobName}");
            return(new ServiceOperationResultEncodeDispatched(
                       workflowJobName: jobName,
                       null,
                       encodeCreateDTO.OperationContext));
        }
示例#7
0
        /// <inheritdoc/>
        public async Task <string> SubmitMesJobAsync(string inputAssetId, string preset, Uri outputContainer, Uri callbackEndpoint, IDictionary <string, string> correlationData)
        {
            if (string.IsNullOrWhiteSpace(inputAssetId))
            {
                throw new ArgumentException($@"{nameof(inputAssetId)} is invalid", nameof(inputAssetId));
            }

            if (string.IsNullOrWhiteSpace(preset))
            {
                throw new ArgumentException($@"{nameof(preset)} is invalid", nameof(preset));
            }

            _ = outputContainer ?? throw new ArgumentNullException(nameof(outputContainer));

            _ = callbackEndpoint ?? throw new ArgumentNullException(nameof(callbackEndpoint));

            _ = correlationData ?? throw new ArgumentNullException(nameof(correlationData));

            string outputAssetName;
            string outputAssetStorageAccountName;
            string jobName;

            try
            {
                var outputContainerUriBuilder = new BlobUriBuilder(outputContainer);
                outputAssetStorageAccountName = outputContainerUriBuilder.AccountName;
                outputAssetName = GetOutputAssetName(outputAssetStorageAccountName, outputContainerUriBuilder);
                jobName         = GenerateJobName(outputAssetName);
            }
            catch (Exception e)
            {
                _log.LogExceptionObject(LogEventIds.MediaServicesV2FailedToParseOutputContainer, e, outputContainer);
                throw new GridwichEncodeCreateJobException($"Could not define output asset name or job name from {outputContainer}.", null, e, LogEventIds.MediaServicesV2FailedToParseOutputContainer);
            }

            string processorId;

            try
            {
                processorId = await _mediaServicesV2RestWrapper.GetLatestMediaProcessorAsync("Media Encoder Standard").ConfigureAwait(false);
            }
            catch (Exception e)
            {
                _log.LogExceptionObject(LogEventIds.MediaServicesV2FailedToGetProcessor, e, outputContainer);
                throw new GridwichEncodeCreateJobException($"Could not get media processor.", null, e, LogEventIds.MediaServicesV2FailedToGetProcessor);
            }

            string base64UrlEncodedCorrelationDataJsonString;

            try
            {
                var correlationDataJsonString = JsonConvert.SerializeObject(correlationData);
                base64UrlEncodedCorrelationDataJsonString = Base64UrlEncoder.Encode(correlationDataJsonString);
                if (base64UrlEncodedCorrelationDataJsonString.Length > 4000)
                {
                    const string ErrorMsg = "UrlEncoded and serialized correlationData is larger than 4000";
                    _log.LogEvent(LogEventIds.MediaServicesV2CorrelationDataError, ErrorMsg, correlationData);
                    throw new ArgumentException(ErrorMsg, nameof(correlationData));
                }
            }
            catch (Exception e)
            {
                _log.LogExceptionObject(LogEventIds.MediaServicesV2CorrelationDataError, e, correlationData);
                throw new GridwichEncodeCreateJobException($"Could not convert correlationData.", null, e, LogEventIds.MediaServicesV2CorrelationDataError);
            }

            string notificationEndPointId;

            try
            {
                notificationEndPointId = await _mediaServicesV2RestWrapper.GetOrCreateNotificationEndPointAsync("AmsV2Callback", callbackEndpoint).ConfigureAwait(false);
            }
            catch (Exception e)
            {
                _log.LogExceptionObject(LogEventIds.MediaServicesV2SpecificDataError, e, callbackEndpoint);
                throw new GridwichEncodeCreateJobException($"Could not create notification endpoint for {callbackEndpoint}", null, e, LogEventIds.MediaServicesV2SpecificDataError);
            }

            string jobId;

            try
            {
                jobId = await _mediaServicesV2RestWrapper.CreateJobAsync(
                    jobName,
                    processorId,
                    inputAssetId,
                    preset,
                    outputAssetName,
                    outputAssetStorageAccountName,
                    correlationData : base64UrlEncodedCorrelationDataJsonString,
                    notificationEndPointId).ConfigureAwait(false);
            }
            catch (Exception e)
            {
                _log.LogExceptionObject(LogEventIds.MediaServicesV2SubmitMesJobFailure, e, new
                {
                    jobName,
                    processorId,
                    inputAssetId,
                    preset,
                    outputAssetName,
                    outputAssetStorageAccountName,
                    base64UrlEncodedCorrelationDataJsonString,
                    notificationEndPointId
                });
                throw new GridwichEncodeCreateJobException($"Could not start media encoder standard job.", null, e, LogEventIds.MediaServicesV2SubmitMesJobFailure);
            }
            return(jobId);
        }
示例#8
0
        /// <summary>
        /// EncodeAsync does the heavy lifting to encode and process a video asset.
        /// </summary>
        /// <param name="encodeCreateDTO">encodeCreateDTO.</param>
        /// <returns>Returns true if encode request was successful, otherwise false.</returns>
        public async Task <ServiceOperationResultEncodeDispatched> EncodeCreateAsync(RequestMediaServicesV2EncodeCreateDTO encodeCreateDTO)
        {
            _ = encodeCreateDTO ?? throw new ArgumentNullException(nameof(encodeCreateDTO));

            List <Uri> sourceUris = new List <Uri>();

            try
            {
                sourceUris = encodeCreateDTO.Inputs.ToList().Select(u => new Uri(u.BlobUri)).ToList();
            }
            catch (Exception e)
            {
                throw new GridwichEncodeCreateJobException($"Failed to parse Inputs", encodeCreateDTO.OperationContext, e, LogEventIds.MediaServicesV2InputError);
            }

            string inputAssetId = await _mediaServicesV2Service.CopyFilesIntoNewAsset(sourceUris).ConfigureAwait(false);

            var      presetName = encodeCreateDTO.PresetName;
            TimeSpan?thumbnailTimeSpan;

            if (encodeCreateDTO.ThumbnailTimeSeconds == 0)
            {
                thumbnailTimeSpan = null;
            }
            else
            {
                if (encodeCreateDTO.ThumbnailTimeSeconds < 0)
                {
                    throw new GridwichTimeParameterException(nameof(encodeCreateDTO.ThumbnailTimeSeconds), encodeCreateDTO.ThumbnailTimeSeconds, "Must be above zero.");
                }

                thumbnailTimeSpan = TimeSpan.FromSeconds(encodeCreateDTO.ThumbnailTimeSeconds);
            }


            var preset = _mediaServicesPreset.GetPresetForPresetName(presetName, thumbnailTimeSpan);

            if (string.IsNullOrEmpty(preset))
            {
                throw new GridwichEncodeCreateJobException($"Failed for PresetName {presetName}", encodeCreateDTO.OperationContext, null, LogEventIds.MediaServicesV2PresetError);
            }

            string jobId;

            try
            {
                Uri callbackEndpoint = new Uri(_settingsProvider.GetAppSettingsValue("AmsV2CallbackEndpoint"));
                var outputContainer  = new Uri(encodeCreateDTO.OutputContainer);
                var correlationData  = new Dictionary <string, string>()
                {
                    { "outputAssetContainer", outputContainer.ToString() },
                    { "operationContext", encodeCreateDTO.OperationContext.ToString() },
                };

                jobId = await _mediaServicesV2Service.SubmitMesJobAsync(
                    inputAssetId,
                    preset,
                    outputContainer,
                    callbackEndpoint,
                    correlationData).ConfigureAwait(false);
            }
            catch (Exception e)
            {
                throw new GridwichEncodeCreateJobException($"Failed to create job.", encodeCreateDTO.OperationContext, e, LogEventIds.MediaServicesV2CreateJobError);
            }

            _log.LogEvent(LogEventIds.MediaServicesV2JobSubmitCalled, jobId);

            return(new ServiceOperationResultEncodeDispatched(
                       workflowJobName: jobId,
                       null,
                       encodeCreateDTO.OperationContext));
        }
        public async Task <IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]
            HttpRequest req,
            CancellationToken cancellationToken)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                _logger.LogEvent(LogEventIds.FunctionAppShuttingDown, LogEventIds.FunctionAppShuttingDown.Name);
                throw new OperationCanceledException("Function invoked with a canceled cancellation token.");
            }

            try
            {
                cancellationToken.Register(() =>
                {
                    _logger.LogEvent(LogEventIds.FunctionAppShuttingDown, LogEventIds.FunctionAppShuttingDown.Name);
                });

                _logger.LogEvent(LogEventIds.CallbackFunctionTriggered, $"C# HTTP trigger function processed a request. Path={req?.Path}");

                // TODO: not sure why we have to get the byte array.. might be legacy code
                // MemoryStream stream = new MemoryStream();
                // await req.Body.CopyToAsync(stream);
                // byte[] bodyByteArray = stream.ToArray();

                string requestBody;
                using (var sr = new StreamReader(req.Body))
                {
                    requestBody = await sr.ReadToEndAsync().ConfigureAwait(false);
                }

                if (req.Headers.TryGetValue("ms-signature", out _))
                {
                    // TODO: need to verify headers here. However not sure how to get the signing key.
                    MediaServicesV2NotificationMessage notificationMessage = JsonConvert.DeserializeObject <MediaServicesV2NotificationMessage>(requestBody);

                    var eventGridEventId = System.Guid.NewGuid().ToString();
                    var eventToPublish   = new Microsoft.Azure.EventGrid.Models.EventGridEvent
                    {
                        Id          = eventGridEventId,
                        Data        = notificationMessage,
                        EventTime   = System.DateTime.UtcNow,
                        EventType   = CustomEventTypes.ResponseEncodeMediaServicesV2TranslateCallback,
                        Subject     = $"/{CustomEventTypes.ResponseEncodeMediaServicesV2TranslateCallback}/{eventGridEventId}",
                        DataVersion = "1.0",
                    };

                    await _publisher.PublishEventToTopic(eventToPublish).ConfigureAwait(false);

                    _logger.LogEvent(LogEventIds.CallbackFunctionNotificationMessageProcessed, "processed notification message");
                }
                else
                {
                    _logger.LogEvent(LogEventIds.RequestIsMissingVerifyWebHookRequestSignature, "VerifyWebHookRequestSignature failed.");
                    return(new BadRequestObjectResult("VerifyWebHookRequestSignature failed."));
                }

                return(new OkObjectResult("OK"));
            }
            catch (OperationCanceledException oce)
            {
                _logger.LogEventObject(LogEventIds.OperationCancelException, oce);
                throw;
            }
        }