From 995d20d9d49b8a054c8141030ad647b5bf5fefd6 Mon Sep 17 00:00:00 2001 From: Henry van der Vegte Date: Tue, 10 Nov 2020 11:35:57 +0100 Subject: [PATCH] Text analytics + ARM changes, cleanup (#858) * text analytics + arm changes, cleanup * remove bom --- .../Connector/CostEstimation.cs | 9 +- .../Connector/DatabaseConnector.cs | 42 +-- .../Connector/Enums/EntityRedactionSetting.cs | 13 + .../Enums/SentimentAnalysisSetting.cs | 14 + .../CombinedRecognizedPhrase.cs | 6 +- .../FetchTranscriptionEnvironmentVariables.cs | 5 +- .../FetchTranscription/TextAnalytics.cs | 283 ++++++++++++------ .../TranscriptionProcessor.cs | 12 +- .../Setup/ArmTemplate.json | 240 ++++++++------- 9 files changed, 391 insertions(+), 233 deletions(-) create mode 100644 samples/batch/transcription-enabled-storage/Connector/Enums/EntityRedactionSetting.cs create mode 100644 samples/batch/transcription-enabled-storage/Connector/Enums/SentimentAnalysisSetting.cs diff --git a/samples/batch/transcription-enabled-storage/Connector/CostEstimation.cs b/samples/batch/transcription-enabled-storage/Connector/CostEstimation.cs index 5b41d3a0c..b20d9a37f 100644 --- a/samples/batch/transcription-enabled-storage/Connector/CostEstimation.cs +++ b/samples/batch/transcription-enabled-storage/Connector/CostEstimation.cs @@ -6,6 +6,7 @@ namespace Connector { using System; + using Connector.Enums; public static class CostEstimation { @@ -20,18 +21,18 @@ public static double GetCostEstimation( TimeSpan timeSpan, int numberOfChannels, bool isCustomModel, - bool sentimentAnalysisAdded, - bool entityRedactionAdded) + SentimentAnalysisSetting sentimentSetting, + EntityRedactionSetting entityRedactionSetting) { double costPerHour = isCustomModel ? STTCustomModelCostPerHour : STTCostPerHour; var price = timeSpan.TotalHours * costPerHour; - if (sentimentAnalysisAdded) + if (sentimentSetting != SentimentAnalysisSetting.None) { price += timeSpan.TotalHours * TextAnalyticsCostPerHour; } - if (entityRedactionAdded) + if (entityRedactionSetting != EntityRedactionSetting.None) { price += timeSpan.TotalHours * TextAnalyticsCostPerHour; } diff --git a/samples/batch/transcription-enabled-storage/Connector/DatabaseConnector.cs b/samples/batch/transcription-enabled-storage/Connector/DatabaseConnector.cs index 6f4f82251..56daa01f1 100644 --- a/samples/batch/transcription-enabled-storage/Connector/DatabaseConnector.cs +++ b/samples/batch/transcription-enabled-storage/Connector/DatabaseConnector.cs @@ -107,8 +107,8 @@ private async Task StoreCombinedRecognizedPhrasesAsync(Guid transcriptionId, int var combinedPhrases = speechTranscript.CombinedRecognizedPhrases.Where(t => t.Channel == channel).FirstOrDefault(); - var query = "INSERT INTO dbo.CombinedRecognizedPhrases (ID, TranscriptionID, Channel, Lexical, Itn, MaskedItn, Display)" + - " VALUES (@id, @transcriptionID, @channel, @lexical, @itn, @maskedItn, @display)"; + var query = "INSERT INTO dbo.CombinedRecognizedPhrases (ID, TranscriptionID, Channel, Lexical, Itn, MaskedItn, Display, SentimentPositive, SentimentNeutral, SentimentNegative)" + + " VALUES (@id, @transcriptionID, @channel, @lexical, @itn, @maskedItn, @display, @sentimentPositive, @sentimentNeutral, @sentimentNegative)"; using (var command = new SqlCommand(query, Connection)) { @@ -116,20 +116,14 @@ private async Task StoreCombinedRecognizedPhrasesAsync(Guid transcriptionId, int command.Parameters.AddWithValue("@transcriptionID", transcriptionId); command.Parameters.AddWithValue("@channel", channel); - if (combinedPhrases != null) - { - command.Parameters.AddWithValue("@lexical", combinedPhrases.Lexical); - command.Parameters.AddWithValue("@itn", combinedPhrases.ITN); - command.Parameters.AddWithValue("@maskedItn", combinedPhrases.MaskedITN); - command.Parameters.AddWithValue("@display", combinedPhrases.Display); - } - else - { - command.Parameters.AddWithValue("@lexical", string.Empty); - command.Parameters.AddWithValue("@itn", string.Empty); - command.Parameters.AddWithValue("@maskedItn", string.Empty); - command.Parameters.AddWithValue("@display", string.Empty); - } + command.Parameters.AddWithValue("@lexical", combinedPhrases.Lexical ?? string.Empty); + command.Parameters.AddWithValue("@itn", combinedPhrases.ITN ?? string.Empty); + command.Parameters.AddWithValue("@maskedItn", combinedPhrases.MaskedITN ?? string.Empty); + command.Parameters.AddWithValue("@display", combinedPhrases.Display ?? string.Empty); + + command.Parameters.AddWithValue("@sentimentPositive", combinedPhrases?.Sentiment?.Positive ?? 0f); + command.Parameters.AddWithValue("@sentimentNeutral", combinedPhrases?.Sentiment?.Neutral ?? 0f); + command.Parameters.AddWithValue("@sentimentNegative", combinedPhrases?.Sentiment?.Negative ?? 0f); var result = await command.ExecuteNonQueryAsync().ConfigureAwait(false); @@ -194,18 +188,10 @@ private async Task StoreNBestAsync(Guid recognizedPhraseID, NBest nBest) command.Parameters.AddWithValue("@itn", nBest.ITN); command.Parameters.AddWithValue("@maskedItn", nBest.MaskedITN); command.Parameters.AddWithValue("@display", nBest.Display); - if (nBest.Sentiment != null) - { - command.Parameters.AddWithValue("@sentimentNegative", nBest.Sentiment.Negative); - command.Parameters.AddWithValue("@sentimentNeutral", nBest.Sentiment.Neutral); - command.Parameters.AddWithValue("@sentimentPositive", nBest.Sentiment.Positive); - } - else - { - command.Parameters.AddWithValue("@sentimentNegative", 0f); - command.Parameters.AddWithValue("@sentimentNeutral", 0f); - command.Parameters.AddWithValue("@sentimentPositive", 0f); - } + + command.Parameters.AddWithValue("@sentimentNegative", nBest?.Sentiment?.Negative ?? 0f); + command.Parameters.AddWithValue("@sentimentNeutral", nBest?.Sentiment?.Neutral ?? 0f); + command.Parameters.AddWithValue("@sentimentPositive", nBest?.Sentiment?.Positive ?? 0f); var result = await command.ExecuteNonQueryAsync().ConfigureAwait(false); diff --git a/samples/batch/transcription-enabled-storage/Connector/Enums/EntityRedactionSetting.cs b/samples/batch/transcription-enabled-storage/Connector/Enums/EntityRedactionSetting.cs new file mode 100644 index 000000000..d476193c8 --- /dev/null +++ b/samples/batch/transcription-enabled-storage/Connector/Enums/EntityRedactionSetting.cs @@ -0,0 +1,13 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// + +namespace Connector.Enums +{ + public enum EntityRedactionSetting + { + None, + UtteranceLevel + } +} diff --git a/samples/batch/transcription-enabled-storage/Connector/Enums/SentimentAnalysisSetting.cs b/samples/batch/transcription-enabled-storage/Connector/Enums/SentimentAnalysisSetting.cs new file mode 100644 index 000000000..f91f99ba1 --- /dev/null +++ b/samples/batch/transcription-enabled-storage/Connector/Enums/SentimentAnalysisSetting.cs @@ -0,0 +1,14 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// + +namespace Connector.Enums +{ + public enum SentimentAnalysisSetting + { + None, + UtteranceLevel, + AudioLevel + } +} diff --git a/samples/batch/transcription-enabled-storage/Connector/Serializable/TranscriptionResult/CombinedRecognizedPhrase.cs b/samples/batch/transcription-enabled-storage/Connector/Serializable/TranscriptionResult/CombinedRecognizedPhrase.cs index 4fd2be629..2721d79fa 100644 --- a/samples/batch/transcription-enabled-storage/Connector/Serializable/TranscriptionResult/CombinedRecognizedPhrase.cs +++ b/samples/batch/transcription-enabled-storage/Connector/Serializable/TranscriptionResult/CombinedRecognizedPhrase.cs @@ -9,13 +9,14 @@ namespace Connector public class CombinedRecognizedPhrase { - public CombinedRecognizedPhrase(int channel, string lexical, string itn, string maskedItn, string display) + public CombinedRecognizedPhrase(int channel, string lexical, string itn, string maskedItn, string display, Sentiment sentiment) { Channel = channel; Lexical = lexical; ITN = itn; MaskedITN = maskedItn; Display = display; + Sentiment = sentiment; } [JsonProperty("channel")] @@ -32,5 +33,8 @@ public CombinedRecognizedPhrase(int channel, string lexical, string itn, string [JsonProperty("display")] public string Display { get; set; } + + [JsonProperty("sentiment")] + public Sentiment Sentiment { get; set; } } } diff --git a/samples/batch/transcription-enabled-storage/FetchTranscription/FetchTranscriptionEnvironmentVariables.cs b/samples/batch/transcription-enabled-storage/FetchTranscription/FetchTranscriptionEnvironmentVariables.cs index 8e5ef0d17..2a2b56802 100644 --- a/samples/batch/transcription-enabled-storage/FetchTranscription/FetchTranscriptionEnvironmentVariables.cs +++ b/samples/batch/transcription-enabled-storage/FetchTranscription/FetchTranscriptionEnvironmentVariables.cs @@ -6,13 +6,14 @@ namespace FetchTranscriptionFunction { using System; + using Connector.Enums; using Connector.Extensions; public static class FetchTranscriptionEnvironmentVariables { - public static readonly bool AddEntityRedaction = bool.TryParse(Environment.GetEnvironmentVariable(nameof(AddEntityRedaction), EnvironmentVariableTarget.Process), out AddEntityRedaction) && AddEntityRedaction; + public static readonly SentimentAnalysisSetting SentimentAnalysisSetting = Enum.TryParse(Environment.GetEnvironmentVariable(nameof(SentimentAnalysisSetting), EnvironmentVariableTarget.Process), out SentimentAnalysisSetting) ? SentimentAnalysisSetting : SentimentAnalysisSetting.None; - public static readonly bool AddSentimentAnalysis = bool.TryParse(Environment.GetEnvironmentVariable(nameof(AddSentimentAnalysis), EnvironmentVariableTarget.Process), out AddSentimentAnalysis) && AddSentimentAnalysis; + public static readonly EntityRedactionSetting EntityRedactionSetting = Enum.TryParse(Environment.GetEnvironmentVariable(nameof(EntityRedactionSetting), EnvironmentVariableTarget.Process), out EntityRedactionSetting) ? EntityRedactionSetting : EntityRedactionSetting.None; public static readonly bool CreateHtmlResultFile = bool.TryParse(Environment.GetEnvironmentVariable(nameof(CreateHtmlResultFile), EnvironmentVariableTarget.Process), out CreateHtmlResultFile) && CreateHtmlResultFile; diff --git a/samples/batch/transcription-enabled-storage/FetchTranscription/TextAnalytics.cs b/samples/batch/transcription-enabled-storage/FetchTranscription/TextAnalytics.cs index 88eff109b..2870b36ba 100644 --- a/samples/batch/transcription-enabled-storage/FetchTranscription/TextAnalytics.cs +++ b/samples/batch/transcription-enabled-storage/FetchTranscription/TextAnalytics.cs @@ -7,9 +7,11 @@ namespace FetchTranscriptionFunction { using System; using System.Collections.Generic; + using System.Globalization; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; + using System.Reflection.Metadata.Ecma335; using System.Text; using System.Threading.Tasks; using System.Web; @@ -25,6 +27,8 @@ public class TextAnalytics // Request limits for v3 API: https://docs.microsoft.com/en-us/azure/cognitive-services/text-analytics/concepts/data-limits private const int SentimentRequestLimit = 10; + private const int TextAnalyticsRequestCharacterLimit = 5120; + private const int EntityRecognitionRequestLimit = 5; private const string Caller = "Microsoft Speech to Text"; @@ -49,48 +53,89 @@ public TextAnalytics(string locale, string subscriptionKey, string region, ILogg Log = log; } - public async Task> AddSentimentToTranscriptAsync(SpeechTranscript speechTranscript) + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Don't fail full transcription job because of text analytics error - return transcript without text analytics.")] + public async Task> AddSentimentToTranscriptAsync(SpeechTranscript speechTranscript, SentimentAnalysisSetting sentimentSetting) { if (speechTranscript == null) { throw new ArgumentNullException(nameof(speechTranscript)); } - var textAnalyticsChunks = CreateRequestChunks(speechTranscript, SentimentRequestLimit); + var sentimentErrors = new List(); - var responses = new List(); - foreach (var chunk in textAnalyticsChunks) + try { - var chunkString = JsonConvert.SerializeObject(chunk); - var response = await MakeRequestAsync(chunkString, SentimentSuffix).ConfigureAwait(false); - responses.Add(response); - } + var textAnalyticsChunks = new List(); - Log.LogInformation($"Total responses: {responses.Count}"); - var sentimentErrors = await AddSentimentToSpeechTranscriptAsync(responses, speechTranscript).ConfigureAwait(false); - return sentimentErrors; + if (sentimentSetting == SentimentAnalysisSetting.UtteranceLevel) + { + textAnalyticsChunks = CreateUtteranceLevelRequests(speechTranscript, SentimentRequestLimit); + } + else if (sentimentSetting == SentimentAnalysisSetting.AudioLevel) + { + textAnalyticsChunks = CreateAudioLevelRequests(speechTranscript, SentimentRequestLimit); + } + + var responses = new List(); + foreach (var chunk in textAnalyticsChunks) + { + var chunkString = JsonConvert.SerializeObject(chunk); + var response = await MakeRequestAsync(chunkString, SentimentSuffix).ConfigureAwait(false); + responses.Add(response); + } + + Log.LogInformation($"Total responses: {responses.Count}"); + sentimentErrors = await AddSentimentToSpeechTranscriptAsync(responses, speechTranscript, sentimentSetting).ConfigureAwait(false); + + return sentimentErrors; + } + catch (Exception e) + { + var sentimentError = $"Sentiment Analysis failed with exception: {e.Message}"; + Log.LogError(sentimentError); + sentimentErrors.Add(sentimentError); + return sentimentErrors; + } } - public async Task> RedactEntitiesAsync(SpeechTranscript speechTranscript) + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Don't fail full transcription job because of text analytics error - return transcript without text analytics.")] + public async Task> RedactEntitiesAsync(SpeechTranscript speechTranscript, EntityRedactionSetting entityRedactionSetting) { if (speechTranscript == null) { throw new ArgumentNullException(nameof(speechTranscript)); } - var textAnalyticsChunks = CreateRequestChunks(speechTranscript, EntityRecognitionRequestLimit); - var responses = new List(); - foreach (var chunk in textAnalyticsChunks) + var entityRedactionErrors = new List(); + + try { - var chunkString = JsonConvert.SerializeObject(chunk); - var response = await MakeRequestAsync(chunkString, EntityRecognitionSuffix).ConfigureAwait(false); - responses.Add(response); - } + var textAnalyticsChunks = new List(); + if (entityRedactionSetting == EntityRedactionSetting.UtteranceLevel) + { + textAnalyticsChunks = CreateUtteranceLevelRequests(speechTranscript, EntityRecognitionRequestLimit); + } + + var responses = new List(); + foreach (var chunk in textAnalyticsChunks) + { + var chunkString = JsonConvert.SerializeObject(chunk); + var response = await MakeRequestAsync(chunkString, EntityRecognitionSuffix).ConfigureAwait(false); + responses.Add(response); + } - Log.LogInformation($"Total responses: {responses.Count}"); - var entityRedactionErrors = await RedactEntitiesInSpeechTranscriptAsync(responses, speechTranscript).ConfigureAwait(false); + Log.LogInformation($"Total responses: {responses.Count}"); + entityRedactionErrors = await RedactEntitiesInSpeechTranscriptAsync(responses, speechTranscript, entityRedactionSetting).ConfigureAwait(false); - return entityRedactionErrors; + return entityRedactionErrors; + } + catch (Exception e) + { + var entityRedactionError = $"Entity Redaction failed with exception: {e.Message}"; + Log.LogError(entityRedactionError); + entityRedactionErrors.Add(entityRedactionError); + return entityRedactionErrors; + } } private static bool IsMaskableEntityType(TextAnalyticsEntity entity) @@ -110,41 +155,44 @@ private static bool IsMaskableEntityType(TextAnalyticsEntity entity) } } - private static RecognizedPhrase RedactEntityInRecognizedPhrase(RecognizedPhrase recognizedPhrase, TextAnalyticsEntity entity) + private static string RedactEntitiesInText(string text, IEnumerable entities) { - if (!IsMaskableEntityType(entity) || !recognizedPhrase.NBest.Any()) - { - return recognizedPhrase; - } + var maskableEntities = entities.Where(e => IsMaskableEntityType(e)).ToList(); + var cleanedEntities = RemoveOverlappingEntities(maskableEntities); - var nBest = recognizedPhrase.NBest.FirstOrDefault(); + // Order descending to make insertions that do not impact the offset of other entities + cleanedEntities = cleanedEntities.OrderByDescending(o => o.Offset).ToList(); - // only keep first nBest: - recognizedPhrase.NBest = new[] { nBest }; + foreach (var entity in cleanedEntities) + { + text = RedactEntityInText(text, entity); + } - var displayForm = nBest.Display; + return text; + } - var preMask = displayForm.Substring(0, entity.Offset); - var postMask = displayForm.Substring(entity.Offset + entity.Length, displayForm.Length - (entity.Offset + entity.Length)); + private static string RedactEntityInText(string text, TextAnalyticsEntity entity) + { + var preMask = text.Substring(0, entity.Offset); + var postMask = text.Substring(entity.Offset + entity.Length, text.Length - (entity.Offset + entity.Length)); if (entity.Category == EntityCategory.Quantity && entity.SubCategory.Equals("Number", StringComparison.OrdinalIgnoreCase)) { - displayForm = preMask + new string('#', entity.Length) + postMask; + text = preMask + new string('#', entity.Length) + postMask; } else { if (!string.IsNullOrEmpty(entity.SubCategory)) { - displayForm = $"{preMask}#{entity.Category}-{entity.SubCategory}#{postMask}"; + text = $"{preMask}#{entity.Category}-{entity.SubCategory}#{postMask}"; } else { - displayForm = $"{preMask}#{entity.Category}#{postMask}"; + text = $"{preMask}#{entity.Category}#{postMask}"; } } - nBest.Display = displayForm; - return recognizedPhrase; + return text; } private static List RemoveOverlappingEntities(List textAnalyticsEntities) @@ -187,9 +235,8 @@ private static List RemoveOverlappingEntities(List CreateRequestChunks(SpeechTranscript speechTranscript, int documentRequestLimit) + private List CreateUtteranceLevelRequests(SpeechTranscript speechTranscript, int documentRequestLimit) { - var textAnalyticsDocumentList = new List(); var textAnalyticChunks = new List(); if (!speechTranscript.RecognizedPhrases.Any()) @@ -197,10 +244,13 @@ private List CreateRequestChunks(SpeechTranscript sp return textAnalyticChunks; } + var textAnalyticsDocumentList = new List(); foreach (var recognizedPhrase in speechTranscript.RecognizedPhrases) { var id = $"{recognizedPhrase.Channel}_{recognizedPhrase.Offset}"; - var textAnalyticsDocument = new TextAnalyticsRequest(Locale, id, recognizedPhrase.NBest.FirstOrDefault().Display); + var text = recognizedPhrase.NBest.FirstOrDefault().Display; + text = text.Substring(0, Math.Min(text.Length, TextAnalyticsRequestCharacterLimit)); + var textAnalyticsDocument = new TextAnalyticsRequest(Locale, id, text); textAnalyticsDocumentList.Add(textAnalyticsDocument); } @@ -213,7 +263,41 @@ private List CreateRequestChunks(SpeechTranscript sp return textAnalyticChunks; } - private async Task> RedactEntitiesInSpeechTranscriptAsync(List responses, SpeechTranscript speechTranscript) + private List CreateAudioLevelRequests(SpeechTranscript speechTranscript, int documentRequestLimit) + { + var textAnalyticChunks = new List(); + if (!speechTranscript.RecognizedPhrases.Any()) + { + return textAnalyticChunks; + } + + var textAnalyticsDocumentList = new List(); + foreach (var combinedRecognizedPhrase in speechTranscript.CombinedRecognizedPhrases) + { + var channel = combinedRecognizedPhrase.Channel; + var requestCount = 0; + + var displayForm = combinedRecognizedPhrase.Display; + + for (int i = 0; i < displayForm.Length; i += TextAnalyticsRequestCharacterLimit) + { + var displayChunk = displayForm.Substring(i, Math.Min(TextAnalyticsRequestCharacterLimit, displayForm.Length - i)); + var textAnalyticsDocument = new TextAnalyticsRequest(Locale, $"{channel}_{requestCount}", displayChunk); + textAnalyticsDocumentList.Add(textAnalyticsDocument); + requestCount += 1; + } + } + + for (int i = 0; i < textAnalyticsDocumentList.Count; i += documentRequestLimit) + { + textAnalyticChunks.Add(new TextAnalyticsRequestsChunk(textAnalyticsDocumentList.GetRange(i, Math.Min(documentRequestLimit, textAnalyticsDocumentList.Count - i)))); + } + + Log.LogInformation($"Received {textAnalyticChunks.Count} text analytics chunks from {textAnalyticsDocumentList.Count} documents."); + return textAnalyticChunks; + } + + private async Task> RedactEntitiesInSpeechTranscriptAsync(List responses, SpeechTranscript speechTranscript, EntityRedactionSetting entityRedactionSetting) { var entityRedactionErrors = new List(); @@ -239,69 +323,64 @@ private async Task> RedactEntitiesInSpeechTranscriptAsync(Li entityRedactionErrors.Add(errorMessage); } - // Matching entities and transcription JSON by using the timestamp foreach (var document in textAnalyticsResponse.Documents) { - var newSegment = speechTranscript.RecognizedPhrases.Where(e => $"{e.Channel}_{e.Offset}".Equals(document.Id, StringComparison.Ordinal)).FirstOrDefault(); - - // Remove all text but the display form - if (newSegment.NBest == null || !newSegment.NBest.Any()) + if (entityRedactionSetting == EntityRedactionSetting.UtteranceLevel) { - continue; - } - - var nBest = newSegment.NBest.FirstOrDefault(); + var phrase = speechTranscript.RecognizedPhrases.Where(e => $"{e.Channel}_{e.Offset}".Equals(document.Id, StringComparison.Ordinal)).FirstOrDefault(); - nBest.ITN = string.Empty; - nBest.MaskedITN = string.Empty; - nBest.Lexical = string.Empty; + // Remove all text but the display form + if (phrase.NBest == null || !phrase.NBest.Any()) + { + continue; + } - // Remove word level timestamps if they exist - nBest.Words = null; + var nBest = phrase.NBest.FirstOrDefault(); + nBest.ITN = string.Empty; + nBest.MaskedITN = string.Empty; + nBest.Lexical = string.Empty; - var maskableEntities = document.Entities.Where(e => IsMaskableEntityType(e)).ToList(); - var entities = RemoveOverlappingEntities(maskableEntities); + // Remove word level timestamps if they exist + nBest.Words = null; - // Order descending to make insertions that do not impact the offset of other entities - entities = entities.OrderByDescending(o => o.Offset).ToList(); + nBest.Display = RedactEntitiesInText(nBest.Display, document.Entities); + phrase.NBest = new[] { nBest }; - foreach (var entity in entities) - { - RedactEntityInRecognizedPhrase(newSegment, entity); - } - - // Create full transcription per channel - if (fullTranscriptionPerChannelDict.ContainsKey(newSegment.Channel)) - { - fullTranscriptionPerChannelDict[newSegment.Channel].Append(" " + nBest.Display); - } - else - { - fullTranscriptionPerChannelDict.Add(newSegment.Channel, new StringBuilder(nBest.Display)); + if (fullTranscriptionPerChannelDict.ContainsKey(phrase.Channel)) + { + fullTranscriptionPerChannelDict[phrase.Channel].Append(" " + nBest.Display); + } + else + { + fullTranscriptionPerChannelDict.Add(phrase.Channel, new StringBuilder(nBest.Display)); + } } } } - foreach (var combinedRecognizedPhrase in speechTranscript.CombinedRecognizedPhrases) + if (entityRedactionSetting == EntityRedactionSetting.UtteranceLevel) { - var channel = combinedRecognizedPhrase.Channel; + foreach (var combinedRecognizedPhrase in speechTranscript.CombinedRecognizedPhrases) + { + var channel = combinedRecognizedPhrase.Channel; - // Remove full transcription for channel: - combinedRecognizedPhrase.MaskedITN = string.Empty; - combinedRecognizedPhrase.ITN = string.Empty; - combinedRecognizedPhrase.Lexical = string.Empty; - combinedRecognizedPhrase.Display = string.Empty; + // Remove full transcription for channel: + combinedRecognizedPhrase.MaskedITN = string.Empty; + combinedRecognizedPhrase.ITN = string.Empty; + combinedRecognizedPhrase.Lexical = string.Empty; + combinedRecognizedPhrase.Display = string.Empty; - if (fullTranscriptionPerChannelDict.ContainsKey(channel)) - { - combinedRecognizedPhrase.Display = fullTranscriptionPerChannelDict[channel].ToString(); + if (fullTranscriptionPerChannelDict.ContainsKey(channel)) + { + combinedRecognizedPhrase.Display = fullTranscriptionPerChannelDict[channel].ToString(); + } } } return entityRedactionErrors; } - private async Task> AddSentimentToSpeechTranscriptAsync(List responses, SpeechTranscript speechTranscript) + private async Task> AddSentimentToSpeechTranscriptAsync(List responses, SpeechTranscript speechTranscript, SentimentAnalysisSetting sentimentSetting) { var sentimentErrors = new List(); @@ -310,6 +389,8 @@ private async Task> AddSentimentToSpeechTranscriptAsync(List return sentimentErrors; } + var textAnalyticsDocuments = new List(); + foreach (var message in responses) { message.EnsureSuccessStatusCode(); @@ -324,12 +405,17 @@ private async Task> AddSentimentToSpeechTranscriptAsync(List sentimentErrors.Add(errorMessage); } - // Matching sentiment and transcription JSON by using the timestamp - foreach (var document in textAnalyticsResponse.Documents) + textAnalyticsDocuments.AddRange(textAnalyticsResponse.Documents); + } + + if (sentimentSetting == SentimentAnalysisSetting.UtteranceLevel) + { + foreach (var document in textAnalyticsDocuments) { - var targetSegment = speechTranscript.RecognizedPhrases.Where(e => $"{e.Channel}_{e.Offset}".Equals(document.Id, StringComparison.Ordinal)).FirstOrDefault(); + // Matching sentiment and transcription JSON by using the timestamp + var target = speechTranscript.RecognizedPhrases.Where(e => $"{e.Channel}_{e.Offset}".Equals(document.Id, StringComparison.Ordinal)).FirstOrDefault(); - var nBest = targetSegment.NBest.FirstOrDefault(); + var nBest = target.NBest.FirstOrDefault(); if (nBest != null) { if (nBest.Sentiment == null) @@ -343,6 +429,27 @@ private async Task> AddSentimentToSpeechTranscriptAsync(List } } } + else if (sentimentSetting == SentimentAnalysisSetting.AudioLevel) + { + foreach (var combinedRecognizedPhrase in speechTranscript.CombinedRecognizedPhrases) + { + var documents = textAnalyticsDocuments.Where(document => document.Id.StartsWith($"{combinedRecognizedPhrase.Channel}_", StringComparison.OrdinalIgnoreCase)); + + if (!documents.Any()) + { + continue; + } + + if (combinedRecognizedPhrase.Sentiment == null) + { + combinedRecognizedPhrase.Sentiment = new Sentiment(); + } + + combinedRecognizedPhrase.Sentiment.Negative = documents.Average(d => d.ConfidenceScores.Negative); + combinedRecognizedPhrase.Sentiment.Positive = documents.Average(d => d.ConfidenceScores.Positive); + combinedRecognizedPhrase.Sentiment.Neutral = documents.Average(d => d.ConfidenceScores.Neutral); + } + } return sentimentErrors; } diff --git a/samples/batch/transcription-enabled-storage/FetchTranscription/TranscriptionProcessor.cs b/samples/batch/transcription-enabled-storage/FetchTranscription/TranscriptionProcessor.cs index bd1883e40..3060e39b3 100644 --- a/samples/batch/transcription-enabled-storage/FetchTranscription/TranscriptionProcessor.cs +++ b/samples/batch/transcription-enabled-storage/FetchTranscription/TranscriptionProcessor.cs @@ -202,15 +202,15 @@ private static async Task ProcessSucceededTranscriptionAsync(string transcriptio { var textAnalyticsErrors = new List(); - if (FetchTranscriptionEnvironmentVariables.AddSentimentAnalysis) + if (FetchTranscriptionEnvironmentVariables.SentimentAnalysisSetting != SentimentAnalysisSetting.None) { - var sentimentErrors = await textAnalytics.AddSentimentToTranscriptAsync(transcriptionResult).ConfigureAwait(false); + var sentimentErrors = await textAnalytics.AddSentimentToTranscriptAsync(transcriptionResult, FetchTranscriptionEnvironmentVariables.SentimentAnalysisSetting).ConfigureAwait(false); textAnalyticsErrors.AddRange(sentimentErrors); } - if (FetchTranscriptionEnvironmentVariables.AddEntityRedaction) + if (FetchTranscriptionEnvironmentVariables.EntityRedactionSetting != EntityRedactionSetting.None) { - var entityRedactionErrors = await textAnalytics.RedactEntitiesAsync(transcriptionResult).ConfigureAwait(false); + var entityRedactionErrors = await textAnalytics.RedactEntitiesAsync(transcriptionResult, FetchTranscriptionEnvironmentVariables.EntityRedactionSetting).ConfigureAwait(false); textAnalyticsErrors.AddRange(entityRedactionErrors); } @@ -249,8 +249,8 @@ private static async Task ProcessSucceededTranscriptionAsync(string transcriptio duration, transcriptionResult.CombinedRecognizedPhrases.Count(), serviceBusMessage.UsesCustomModel, - FetchTranscriptionEnvironmentVariables.AddSentimentAnalysis, - FetchTranscriptionEnvironmentVariables.AddEntityRedaction); + FetchTranscriptionEnvironmentVariables.SentimentAnalysisSetting, + FetchTranscriptionEnvironmentVariables.EntityRedactionSetting); var jobId = containsMultipleTranscriptions ? Guid.NewGuid() : new Guid(transcriptionLocation.Split('/').LastOrDefault()); var dbConnectionString = FetchTranscriptionEnvironmentVariables.DatabaseConnectionString; diff --git a/samples/batch/transcription-enabled-storage/Setup/ArmTemplate.json b/samples/batch/transcription-enabled-storage/Setup/ArmTemplate.json index 7655417e3..04d98f669 100644 --- a/samples/batch/transcription-enabled-storage/Setup/ArmTemplate.json +++ b/samples/batch/transcription-enabled-storage/Setup/ArmTemplate.json @@ -4,7 +4,6 @@ "parameters": { "StorageAccount": { "type": "String", - "defaultValue": "", "metadata": { "description": "The name of the storage account. It must be unique across all existing storage account names in Azure, between 3 and 24 characters long, and can contain only lowercase letters and numbers." } @@ -197,10 +196,10 @@ } }, "TextAnalyticsRegion": { - "defaultValue": "none", + "defaultValue": "None", "type": "String", "allowedValues": [ - "none", + "None", "centralus", "eastus", "eastus2", @@ -227,16 +226,25 @@ "description": "The region the Text Analytics subscription is associated with. If none is selected, no text analysis will be performed." } }, - "AddSentimentAnalysis": { - "defaultValue": false, - "type": "bool", + "SentimentAnalysis": { + "defaultValue": "None", + "type": "String", + "allowedValues": [ + "None", + "UtteranceLevel", + "AudioLevel" + ], "metadata": { - "description": "A value indicating whether sentiment analysis is requested. Will only be performed if a Text Analytics Key and Region is provided." + "description": "A value indicating whether sentiment analysis is requested (either per utterance or per audio). Will only be performed if a Text Analytics Key and Region is provided." } }, - "AddEntityRedaction": { - "defaultValue": false, - "type": "bool", + "EntityRedaction": { + "defaultValue": "None", + "type": "String", + "allowedValues": [ + "None", + "UtteranceLevel" + ], "metadata": { "description": "A value indicating whether entity redaction is requested. Will only be performed if a Text Analytics Key and Region is provided." } @@ -254,37 +262,48 @@ "metadata": { "description": "The administrator password of the SQL Server. If it is left empty, no SQL server/database will be created." } + }, + "DeploymentId": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Id that will be suffixed to all created resources to identify resources of a certain deployment. Leave as is to use timestamp as deployment id." + } } }, "variables": { - "UseSqlDatabase": "[and(not(equals(parameters('SqlAdministratorLogin'),'')), not(equals(parameters('SqlAdministratorLoginPassword'),'')))]", - "SqlServerName": "[concat('sqlserver', uniqueString(subscription().id, resourceGroup().id))]", - "DatabaseName": "[concat('Database-', resourceGroup().name)]", + "AudioInputContainer": "audio-input", + "JsonResultOutputContainer": "json-result-output", + "HtmlResultOutputContainer": "html-result-output", + "ErrorReportOutputContainer": "error-report", + "ErrorFilesOutputContainer": "error-files", + "CreateHtmlResultFile": false, + "TimerBasedExecution": true, + "MessagesPerFunctionExecution": 500, + "FilesPerTranscriptionJob": 16, + "RetryLimit": 4, + "InstanceId": "[parameters('DeploymentId')]", "StorageAccountName": "[parameters('StorageAccount')]", - "audioInputContainer": "audio-input", - "jsonResultOutputContainer": "json-result-output", - "htmlResultOutputContainer": "html-result-output", - "errorReportOutputContainer": "error-report", - "errorFilesOutputContainer": "error-files", - "startTranscriptionFunctionName": "[take(concat('StartTranscriptionFunction-', resourceGroup().name),60)]", - "startTranscriptionFunctionId": "[resourceId('Microsoft.Web/sites', variables('startTranscriptionFunctionName'))]", - "fetchTranscriptionFunctionName": "[take(concat('FetchTranscriptionFunction-', resourceGroup().name),60)]", - "fetchTranscriptionFunctionId": "[resourceId('Microsoft.Web/sites', variables('fetchTranscriptionFunctionName'))]", - "authRuleRMK": "[resourceId('Microsoft.ServiceBus/namespaces/authorizationRules', concat('ServiceBus-', resourceGroup().name),'RootManageSharedAccessKey')]", - "authRuleFT": "[resourceId('Microsoft.ServiceBus/namespaces/queues/authorizationRules',concat('ServiceBus-', resourceGroup().name), 'fetch_transcription_queue','FetchTranscription')]", - "authRuleCT": "[resourceId('Microsoft.ServiceBus/namespaces/queues/authorizationRules',concat('ServiceBus-', resourceGroup().name), 'start_transcription_queue','StartTranscription')]", - "appServicePlanName": "AcceleratorAppServicePlan", - "createHtmlResultFile": true, - "timerBasedExecution": true, - "messagesPerFunctionExecution": 500, - "filesPerTranscriptionJob": 32, - "retryLimit": 4 + "UseSqlDatabase": "[and(not(equals(parameters('SqlAdministratorLogin'),'')), not(equals(parameters('SqlAdministratorLoginPassword'),'')))]", + "SqlServerName": "[concat('sqlserver', toLower(variables('InstanceId')))]", + "DatabaseName": "[concat('Database-', toLower(variables('InstanceId')))]", + "ServiceBusName": "[concat('ServiceBus-', variables('InstanceId'))]", + "AppInsightsName": "[concat('AcceleratorInsights-', variables('InstanceId'))]", + "EventGridSystemTopicName": "[concat(variables('StorageAccountName'),'-',variables('InstanceId'))]", + "StartTranscriptionFunctionName": "[take(concat('StartTranscriptionFunction-', variables('InstanceId')),60)]", + "StartTranscriptionFunctionId": "[resourceId('Microsoft.Web/sites', variables('StartTranscriptionFunctionName'))]", + "FetchTranscriptionFunctionName": "[take(concat('FetchTranscriptionFunction-', variables('InstanceId')),60)]", + "FetchTranscriptionFunctionId": "[resourceId('Microsoft.Web/sites', variables('FetchTranscriptionFunctionName'))]", + "AuthRuleRMK": "[resourceId('Microsoft.ServiceBus/namespaces/authorizationRules', variables('ServiceBusName'),'RootManageSharedAccessKey')]", + "AuthRuleFT": "[resourceId('Microsoft.ServiceBus/namespaces/queues/authorizationRules', variables('ServiceBusName'), 'fetch_transcription_queue','FetchTranscription')]", + "AuthRuleCT": "[resourceId('Microsoft.ServiceBus/namespaces/queues/authorizationRules', variables('ServiceBusName'), 'start_transcription_queue','StartTranscription')]", + "AppServicePlanName": "[concat('AppServicePlan-', variables('InstanceId'))]" }, "resources": [ { "type": "microsoft.insights/components", "apiVersion": "2015-05-01", - "name": "AcceleratorInsights", + "name": "[variables('AppInsightsName')]", "location": "[resourceGroup().location]", "tags": { "applicationType": "web", @@ -298,7 +317,7 @@ { "type": "Microsoft.ServiceBus/namespaces", "apiVersion": "2018-01-01-preview", - "name": "[concat('ServiceBus-', resourceGroup().name)]", + "name": "[variables('ServiceBusName')]", "location": "[resourceGroup().location]", "sku": { "name": "Standard", @@ -445,10 +464,10 @@ { "type": "Microsoft.ServiceBus/namespaces/AuthorizationRules", "apiVersion": "2017-04-01", - "name": "[concat(concat('ServiceBus-', resourceGroup().name), '/RootManageSharedAccessKey')]", + "name": "[concat(variables('ServiceBusName'), '/RootManageSharedAccessKey')]", "location": "[resourceGroup().location]", "dependsOn": [ - "[resourceId('Microsoft.ServiceBus/namespaces', concat('ServiceBus-', resourceGroup().name))]" + "[resourceId('Microsoft.ServiceBus/namespaces', variables('ServiceBusName'))]" ], "properties": { "rights": [ @@ -461,10 +480,10 @@ { "type": "Microsoft.ServiceBus/namespaces/queues", "apiVersion": "2017-04-01", - "name": "[concat(concat('ServiceBus-', resourceGroup().name), '/start_transcription_queue')]", + "name": "[concat(variables('ServiceBusName'), '/start_transcription_queue')]", "location": "[resourceGroup().location]", "dependsOn": [ - "[resourceId('Microsoft.ServiceBus/namespaces', concat('ServiceBus-', resourceGroup().name))]" + "[resourceId('Microsoft.ServiceBus/namespaces', variables('ServiceBusName'))]" ], "properties": { "lockDuration": "PT4M", @@ -485,10 +504,10 @@ { "type": "Microsoft.ServiceBus/namespaces/queues", "apiVersion": "2017-04-01", - "name": "[concat(concat('ServiceBus-', resourceGroup().name), '/fetch_transcription_queue')]", + "name": "[concat(variables('ServiceBusName'), '/fetch_transcription_queue')]", "location": "[resourceGroup().location]", "dependsOn": [ - "[resourceId('Microsoft.ServiceBus/namespaces', concat('ServiceBus-', resourceGroup().name))]" + "[resourceId('Microsoft.ServiceBus/namespaces', variables('ServiceBusName'))]" ], "properties": { "lockDuration": "PT1M", @@ -546,11 +565,11 @@ { "type": "Microsoft.ServiceBus/namespaces/queues/authorizationRules", "apiVersion": "2017-04-01", - "name": "[concat(concat('ServiceBus-', resourceGroup().name), '/fetch_transcription_queue/FetchTranscription')]", + "name": "[concat(variables('ServiceBusName'), '/fetch_transcription_queue/FetchTranscription')]", "location": "[resourceGroup().location]", "dependsOn": [ - "[resourceId('Microsoft.ServiceBus/namespaces/queues', concat('ServiceBus-', resourceGroup().name), 'fetch_transcription_queue')]", - "[resourceId('Microsoft.ServiceBus/namespaces', concat('ServiceBus-', resourceGroup().name))]" + "[resourceId('Microsoft.ServiceBus/namespaces/queues', variables('ServiceBusName'), 'fetch_transcription_queue')]", + "[resourceId('Microsoft.ServiceBus/namespaces', variables('ServiceBusName'))]" ], "properties": { "rights": [ @@ -562,11 +581,11 @@ { "type": "Microsoft.ServiceBus/namespaces/queues/authorizationRules", "apiVersion": "2017-04-01", - "name": "[concat(concat('ServiceBus-', resourceGroup().name), '/start_transcription_queue/StartTranscription')]", + "name": "[concat(variables('ServiceBusName'), '/start_transcription_queue/StartTranscription')]", "location": "[resourceGroup().location]", "dependsOn": [ - "[resourceId('Microsoft.ServiceBus/namespaces/queues', concat('ServiceBus-', resourceGroup().name), 'start_transcription_queue')]", - "[resourceId('Microsoft.ServiceBus/namespaces', concat('ServiceBus-', resourceGroup().name))]" + "[resourceId('Microsoft.ServiceBus/namespaces/queues', variables('ServiceBusName'), 'start_transcription_queue')]", + "[resourceId('Microsoft.ServiceBus/namespaces', variables('ServiceBusName'))]" ], "properties": { "rights": [ @@ -578,7 +597,7 @@ { "type": "Microsoft.Storage/storageAccounts/blobServices/containers", "apiVersion": "2019-06-01", - "name": "[concat(variables('StorageAccountName'), '/default/', variables('audioInputContainer'))]", + "name": "[concat(variables('StorageAccountName'), '/default/', variables('AudioInputContainer'))]", "location": "[resourceGroup().location]", "dependsOn": [ "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('StorageAccountName'), 'default')]", @@ -591,7 +610,7 @@ { "type": "Microsoft.Storage/storageAccounts/blobServices/containers", "apiVersion": "2019-06-01", - "name": "[concat(variables('StorageAccountName'), '/default/', variables('jsonResultOutputContainer'))]", + "name": "[concat(variables('StorageAccountName'), '/default/', variables('JsonResultOutputContainer'))]", "location": "[resourceGroup().location]", "dependsOn": [ "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('StorageAccountName'), 'default')]", @@ -603,9 +622,9 @@ }, { "type": "Microsoft.Storage/storageAccounts/blobServices/containers", - "condition": "[variables('createHtmlResultFile')]", + "condition": "[variables('CreateHtmlResultFile')]", "apiVersion": "2019-06-01", - "name": "[concat(variables('StorageAccountName'), '/default/', variables('htmlResultOutputContainer'))]", + "name": "[concat(variables('StorageAccountName'), '/default/', variables('HtmlResultOutputContainer'))]", "location": "[resourceGroup().location]", "dependsOn": [ "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('StorageAccountName'), 'default')]", @@ -618,7 +637,7 @@ { "type": "Microsoft.Storage/storageAccounts/blobServices/containers", "apiVersion": "2019-06-01", - "name": "[concat(variables('StorageAccountName'), '/default/', variables('errorReportOutputContainer'))]", + "name": "[concat(variables('StorageAccountName'), '/default/', variables('ErrorReportOutputContainer'))]", "location": "[resourceGroup().location]", "dependsOn": [ "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('StorageAccountName'), 'default')]", @@ -631,7 +650,7 @@ { "type": "Microsoft.Storage/storageAccounts/blobServices/containers", "apiVersion": "2019-06-01", - "name": "[concat(variables('StorageAccountName'), '/default/', variables('errorFilesOutputContainer'))]", + "name": "[concat(variables('StorageAccountName'), '/default/', variables('ErrorFilesOutputContainer'))]", "location": "[resourceGroup().location]", "dependsOn": [ "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('StorageAccountName'), 'default')]", @@ -642,28 +661,41 @@ } }, { - "type": "Microsoft.Storage/storageAccounts/providers/eventSubscriptions", - "apiVersion": "2019-06-01", - "name": "[concat(variables('StorageAccountName'), '/Microsoft.EventGrid/BlobCreatedEvent')]", + "name": "[variables('EventGridSystemTopicName')]", + "type": "Microsoft.EventGrid/systemTopics", + "apiVersion": "2020-04-01-preview", "location": "[resourceGroup().location]", "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('StorageAccountName'), 'default')]", + "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]" + ], + "properties": { + "source": "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]", + "topicType": "Microsoft.Storage.StorageAccounts" + } + }, + { + "type": "Microsoft.EventGrid/systemTopics/eventSubscriptions", + "apiVersion": "2020-04-01-preview", + "name": "[concat(variables('EventGridSystemTopicName'), '/BlobCreatedEvent')]", + "dependsOn": [ + "[resourceId('Microsoft.EventGrid/systemTopics', variables('EventGridSystemTopicName'))]", "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('StorageAccountName'), 'default')]", "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]", - "[resourceId('Microsoft.ServiceBus/namespaces/queues', concat('ServiceBus-', resourceGroup().name), 'start_transcription_queue')]" + "[resourceId('Microsoft.ServiceBus/namespaces/queues', variables('ServiceBusName'), 'start_transcription_queue')]" ], "properties": { - "topic": "[concat('/subscriptions/',subscription().subscriptionId,'/resourceGroups/',resourceGroup().name,'/providers/Microsoft.Storage/storageAccounts/',variables('StorageAccountName'))]", "destination": { "endpointType": "ServiceBusQueue", "properties": { - "resourceId": "[concat('/subscriptions/',subscription().subscriptionId,'/resourceGroups/',resourceGroup().name,'/providers/Microsoft.ServiceBus/namespaces/ServiceBus-',resourceGroup().name,'/queues/start_transcription_queue')]" + "resourceId": "[concat('/subscriptions/',subscription().subscriptionId,'/resourceGroups/',resourceGroup().name,'/providers/Microsoft.ServiceBus/namespaces/', variables('ServiceBusName'),'/queues/start_transcription_queue')]" } }, "filter": { "includedEventTypes": [ "Microsoft.Storage.BlobCreated" ], - "subjectBeginsWith": "[concat('/blobServices/default/containers/', variables('audioInputContainer'))]", + "subjectBeginsWith": "[concat('/blobServices/default/containers/', variables('AudioInputContainer'))]", "advancedFilters": [] }, "labels": [], @@ -674,7 +706,7 @@ "apiVersion": "2018-02-01", "type": "Microsoft.Web/serverfarms", "kind": "app", - "name": "[variables('appServicePlanName')]", + "name": "[variables('AppServicePlanName')]", "location": "[resourceGroup().location]", "properties": {}, "dependsOn": [], @@ -685,29 +717,29 @@ { "type": "Microsoft.Web/sites", "apiVersion": "2018-11-01", - "name": "[variables('startTranscriptionFunctionName')]", + "name": "[variables('StartTranscriptionFunctionName')]", "location": "[resourceGroup().location]", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('AppServicePlanName'))]" ], "kind": "functionapp", "properties": { - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]", + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('AppServicePlanName'))]", "httpsOnly": "true" } }, { "type": "Microsoft.Web/sites/config", - "name": "[concat(variables('startTranscriptionFunctionName'), '/AppSettings')]", + "name": "[concat(variables('StartTranscriptionFunctionName'), '/AppSettings')]", "apiVersion": "2018-11-01", "location": "[resourceGroup().location]", "dependsOn": [ - "[resourceId('Microsoft.Insights/components', 'AcceleratorInsights')]", + "[resourceId('Microsoft.Insights/components', variables('AppInsightsName'))]", "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]", - "[concat('Microsoft.Web/sites/', variables('startTranscriptionFunctionName'))]", - "[variables('authRuleRMK')]", - "[variables('authRuleFT')]", - "[variables('authRuleCT')]" + "[concat('Microsoft.Web/sites/', variables('StartTranscriptionFunctionName'))]", + "[variables('AuthRuleRMK')]", + "[variables('AuthRuleFT')]", + "[variables('AuthRuleCT')]" ], "tags": { "displayName": "WebAppSettings" @@ -715,81 +747,81 @@ "properties": { "AddDiarization": "[parameters('AddDiarization')]", "AddWordLevelTimestamps": "[parameters('AddWordLevelTimestamps')]", - "APPINSIGHTS_INSTRUMENTATIONKEY": "[reference(resourceId('Microsoft.Insights/components', 'AcceleratorInsights'), '2015-05-01').InstrumentationKey]", - "AudioInputContainer": "[variables('audioInputContainer')]", - "AzureServiceBus": "[listKeys(variables('authRuleRMK'),'2015-08-01').primaryConnectionString]", + "APPINSIGHTS_INSTRUMENTATIONKEY": "[reference(resourceId('Microsoft.Insights/components', variables('AppInsightsName')), '2015-05-01').InstrumentationKey]", + "AudioInputContainer": "[variables('AudioInputContainer')]", + "AzureServiceBus": "[listKeys(variables('AuthRuleRMK'),'2015-08-01').primaryConnectionString]", "AzureSpeechServicesKey": "[parameters('AzureSpeechServicesKey')]", "AzureSpeechServicesRegion": "[parameters('AzureSpeechServicesRegion')]", "AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('StorageAccountName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value)]", "AzureWebJobsDashboard": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('StorageAccountName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value)]", "CustomModelId": "[parameters('CustomModelId')]", - "ErrorFilesOutputContainer": "[variables('errorFilesOutputContainer')]", - "ErrorReportOutputContainer": "[variables('errorReportOutputContainer')]", - "FetchTranscriptionServiceBusConnectionString": "[listKeys(variables('authRuleFT'),'2015-08-01').primaryConnectionString]", - "FilesPerTranscriptionJob": "[variables('filesPerTranscriptionJob')]", + "ErrorFilesOutputContainer": "[variables('ErrorFilesOutputContainer')]", + "ErrorReportOutputContainer": "[variables('ErrorReportOutputContainer')]", + "FetchTranscriptionServiceBusConnectionString": "[listKeys(variables('AuthRuleFT'),'2015-08-01').primaryConnectionString]", + "FilesPerTranscriptionJob": "[variables('FilesPerTranscriptionJob')]", "FUNCTIONS_EXTENSION_VERSION": "~3", "FUNCTIONS_WORKER_RUNTIME": "dotnet", "Locale": "[parameters('Locale')]", - "MessagesPerFunctionExecution": "[variables('messagesPerFunctionExecution')]", + "MessagesPerFunctionExecution": "[variables('MessagesPerFunctionExecution')]", "ProfanityFilterMode": "[parameters('ProfanityFilterMode')]", "PunctuationMode": "[parameters('PunctuationMode')]", - "RetryLimit": "[variables('retryLimit')]", + "RetryLimit": "[variables('RetryLimit')]", "SecondaryCustomModelId": "[parameters('SecondaryCustomModelId')]", "SecondaryLocale": "[parameters('SecondaryLocale')]", - "StartTranscriptionServiceBusConnectionString": "[listKeys(variables('authRuleCT'),'2015-08-01').primaryConnectionString]", + "StartTranscriptionServiceBusConnectionString": "[listKeys(variables('AuthRuleCT'),'2015-08-01').primaryConnectionString]", "WEBSITE_RUN_FROM_PACKAGE": "[if(variables('timerBasedExecution'),'https://mspublicstorage.blob.core.windows.net/transcription-enabled-storage/StartTranscriptionByTimer.zip','https://mspublicstorage.blob.core.windows.net/transcription-enabled-storage/StartTranscriptionByServiceBus.zip')]" } }, { "type": "Microsoft.Web/sites", "apiVersion": "2018-11-01", - "name": "[variables('fetchTranscriptionFunctionName')]", + "name": "[variables('FetchTranscriptionFunctionName')]", "location": "[resourceGroup().location]", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('AppServicePlanName'))]" ], "kind": "functionapp", "properties": { - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]", + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('AppServicePlanName'))]", "httpsOnly": "true" } }, { "type": "Microsoft.Web/sites/config", - "name": "[concat(variables('fetchTranscriptionFunctionName'), '/AppSettings')]", + "name": "[concat(variables('FetchTranscriptionFunctionName'), '/AppSettings')]", "location": "[resourceGroup().location]", "apiVersion": "2018-11-01", "dependsOn": [ - "[resourceId('Microsoft.Insights/components', 'AcceleratorInsights')]", + "[resourceId('Microsoft.Insights/components', variables('AppInsightsName'))]", "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]", - "[concat('Microsoft.Web/sites/', variables('fetchTranscriptionFunctionName'))]", - "[variables('authRuleRMK')]", - "[variables('authRuleFT')]", - "[variables('authRuleCT')]" + "[concat('Microsoft.Web/sites/', variables('FetchTranscriptionFunctionName'))]", + "[variables('AuthRuleRMK')]", + "[variables('AuthRuleFT')]", + "[variables('AuthRuleCT')]" ], "tags": { "displayName": "WebAppSettings" }, "properties": { - "APPINSIGHTS_INSTRUMENTATIONKEY": "[reference(resourceId('Microsoft.Insights/components', 'AcceleratorInsights'), '2015-05-01').InstrumentationKey]", - "AddEntityRedaction": "[parameters('AddEntityRedaction')]", - "AddSentimentAnalysis": "[parameters('AddSentimentAnalysis')]", - "AudioInputContainer": "[variables('audioInputContainer')]", - "AzureServiceBus": "[listKeys(variables('authRuleRMK'),'2015-08-01').primaryConnectionString]", + "APPINSIGHTS_INSTRUMENTATIONKEY": "[reference(resourceId('Microsoft.Insights/components', variables('AppInsightsName')), '2015-05-01').InstrumentationKey]", + "EntityRedactionSetting": "[parameters('EntityRedaction')]", + "SentimentAnalysisSetting": "[parameters('SentimentAnalysis')]", + "AudioInputContainer": "[variables('AudioInputContainer')]", + "AzureServiceBus": "[listKeys(variables('AuthRuleRMK'),'2015-08-01').primaryConnectionString]", "AzureSpeechServicesKey": "[parameters('AzureSpeechServicesKey')]", "AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('StorageAccountName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value)]", "AzureWebJobsDashboard": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('StorageAccountName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value)]", - "CreateHtmlResultFile": "[variables('createHtmlResultFile')]", + "CreateHtmlResultFile": "[variables('CreateHtmlResultFile')]", "DatabaseConnectionString": "[if(variables('UseSqlDatabase'), concat('Server=tcp:',reference(variables('SqlServerName'), '2014-04-01-preview').fullyQualifiedDomainName,',1433;Initial Catalog=',variables('DatabaseName'),';Persist Security Info=False;User ID=',parameters('SqlAdministratorLogin'),';Password=',parameters('SqlAdministratorLoginPassword'),';MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;'), '')]", - "ErrorFilesOutputContainer": "[variables('errorFilesOutputContainer')]", - "ErrorReportOutputContainer": "[variables('errorReportOutputContainer')]", - "FetchTranscriptionServiceBusConnectionString": "[listKeys(variables('authRuleFT'),'2015-08-01').primaryConnectionString]", + "ErrorFilesOutputContainer": "[variables('ErrorFilesOutputContainer')]", + "ErrorReportOutputContainer": "[variables('ErrorReportOutputContainer')]", + "FetchTranscriptionServiceBusConnectionString": "[listKeys(variables('AuthRuleFT'),'2015-08-01').primaryConnectionString]", "FUNCTIONS_EXTENSION_VERSION": "~3", "FUNCTIONS_WORKER_RUNTIME": "dotnet", - "HtmlResultOutputContainer": "[variables('htmlResultOutputContainer')]", - "JsonResultOutputContainer": "[variables('jsonResultOutputContainer')]", - "RetryLimit": "[variables('retryLimit')]", - "StartTranscriptionServiceBusConnectionString": "[listKeys(variables('authRuleCT'),'2015-08-01').primaryConnectionString]", + "HtmlResultOutputContainer": "[variables('HtmlResultOutputContainer')]", + "JsonResultOutputContainer": "[variables('JsonResultOutputContainer')]", + "RetryLimit": "[variables('RetryLimit')]", + "StartTranscriptionServiceBusConnectionString": "[listKeys(variables('AuthRuleCT'),'2015-08-01').primaryConnectionString]", "TextAnalyticsKey": "[parameters('TextAnalyticsKey')]", "TextAnalyticsRegion": "[parameters('TextAnalyticsRegion')]", "UseSqlDatabase": "[variables('UseSqlDatabase')]", @@ -798,13 +830,13 @@ } ], "outputs": { - "startTranscriptionFunctionId": { + "StartTranscriptionFunctionId": { "type": "string", - "value": "[variables('startTranscriptionFunctionId')]" + "value": "[variables('StartTranscriptionFunctionId')]" }, "FetchTranscriptionFunctionId": { "type": "string", - "value": "[variables('fetchTranscriptionFunctionId')]" + "value": "[variables('FetchTranscriptionFunctionId')]" } } -} \ No newline at end of file +}