diff --git a/src/Test/Core/SeJsonParserTest.cs b/src/Test/Core/SeJsonParserTest.cs index aacef6e02..5def62a18 100644 --- a/src/Test/Core/SeJsonParserTest.cs +++ b/src/Test/Core/SeJsonParserTest.cs @@ -192,5 +192,50 @@ namespace Test.Core }".Replace('\'', '"'), "items"); Assert.AreEqual(4, result.Count); } + + [TestMethod] + public void GetRootElements_Simple_Value() + { + var parser = new SeJsonParser(); + var result = parser.GetRootElements("{ \"tag\": \"hi there!\" }"); + Assert.AreEqual(1, result.Count); + Assert.AreEqual("tag", result[0].Name); + Assert.AreEqual("hi there!", result[0].Json); + } + + [TestMethod] + public void GetRootElements_Simple_Object() + { + var parser = new SeJsonParser(); + var result = parser.GetRootElements("{ \"tag\": { \"name\" : \"Joe\" } }"); + Assert.AreEqual(1, result.Count); + Assert.AreEqual("tag", result[0].Name); + Assert.AreEqual("{ \"name\" : \"Joe\" }", result[0].Json); + } + + + [TestMethod] + public void GetRootElements_Simple_Value_And_Object() + { + var parser = new SeJsonParser(); + var result = parser.GetRootElements("{ \"tag1\": \"hi there!\", \"tag2\": { \"name\" : \"Joe\" } }"); + Assert.AreEqual(2, result.Count); + Assert.AreEqual("tag1", result[0].Name); + Assert.AreEqual("hi there!", result[0].Json); + Assert.AreEqual("tag2", result[1].Name); + Assert.AreEqual("{ \"name\" : \"Joe\" }", result[1].Json); + } + + [TestMethod] + public void GetRootElements_Two_Simple_Value() + { + var parser = new SeJsonParser(); + var result = parser.GetRootElements("{ \"tag\": \"hi there!\",\"tag2\": \"hi!\", }"); + Assert.AreEqual(2, result.Count); + Assert.AreEqual("tag", result[0].Name); + Assert.AreEqual("hi there!", result[0].Json); + Assert.AreEqual("tag2", result[1].Name); + Assert.AreEqual("hi!", result[1].Json); + } } } \ No newline at end of file diff --git a/src/libse/Common/SeJsonParser.cs b/src/libse/Common/SeJsonParser.cs index 04d4cc3a2..c633a8bc3 100644 --- a/src/libse/Common/SeJsonParser.cs +++ b/src/libse/Common/SeJsonParser.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using Nikse.SubtitleEdit.Core.ContainerFormats.Ebml; +using System.Collections.Generic; using System.Text; namespace Nikse.SubtitleEdit.Core.Common @@ -1300,5 +1301,389 @@ namespace Nikse.SubtitleEdit.Core.Common } return string.Empty; } + + public class RootElement + { + public string Name { get; set; } + public string Json { get; set; } + } + + public List GetRootElements(string content) + { + Errors = new List(); + var list = new List(); + var i = 0; + var max = content.Length; + var state = new Stack(); + var objectName = string.Empty; + var start = -1; + while (i < max) + { + var ch = content[i]; + if (_whiteSpace.Contains(ch)) // ignore white space + { + i++; + } + + else if (state.Count == 0) // root + { + if (ch == '{') + { + state.Push(new StateElement + { + Name = "Root", + State = SeJsonState.Object + }); + i++; + } + else if (ch == '[') + { + state.Push(new StateElement + { + Name = "Root", + State = SeJsonState.Array + }); + i++; + } + else + { + Errors.Add($"Unexpected char {ch} at position {i}"); + return list; + } + } + + else if (state.Peek().State == SeJsonState.Object) // after '{' + { + if (ch == '"') + { + i++; + int end = content.IndexOf('"', i); + objectName = content.Substring(i, end - i).Trim(); + int colon = content.IndexOf(':', end); + if (colon < 0) + { + Errors.Add($"Fatal - expected char : after position {end}"); + return list; + } + + i += colon - i + 1; + state.Push(new StateElement + { + Name = objectName, + State = SeJsonState.Value + }); + + if (state.Count == 2) + { + start = i; // element in root + } + } + else if (ch == '}') + { + i++; + state.Pop(); + + if (state.Count == 2 && start >= 0) + { + var str = content.Substring(start, i - start).Trim(); + list.Add(new RootElement() + { + Name = state.Peek().Name, + Json = str, + }); + + start = -1; + } + } + else if (ch == ',') // next object + { + if (state.Count == 1 && start >= 0) + { + var str = content.Substring(start, i - start).Trim(); + list.Add(new RootElement() + { + Name = state.Peek().Name, + Json = str, + }); + + start = i + 1; + } + + i++; + if (state.Peek().Count > 0) + { + state.Peek().Count++; + } + else + { + Errors.Add($"Unexpected char {ch} at position {i}"); + return list; + } + } + else if (ch == ']') // next object + { + i++; + if (state.Peek().Count > 0) + { + state.Pop(); + } + else + { + Errors.Add($"Unexpected char {ch} at position {i}"); + return list; + } + } + else + { + Errors.Add($"Unexpected char {ch} at position {i}"); + return list; + } + } + + else if (state.Peek().State == SeJsonState.Value) // value - string/ number / object / array / true / false / null + "," + "}" + { + if (ch == '"') // string + { + i++; + var skip = true; + int end = 0; + var endSeek = i; + while (skip) + { + end = content.IndexOf('"', endSeek); + if (end < 0) + { + Errors.Add($"Fatal - expected char \" after position {endSeek}"); + return list; + } + skip = content[end - 1] == '\\'; + if (skip) + { + endSeek = end + 1; + } + if (endSeek >= max) + { + Errors.Add($"Fatal - expected end tag after position {endSeek}"); + return list; + } + } + + if (state.Count == 2) + { + var objectValue = content.Substring(i, end - i).Trim(); + var x = state.Peek(); + list.Add(new RootElement(){ Name = x.Name, Json = objectValue }); + start = -1; + } + + i += end - i + 1; + state.Pop(); + if (state.Count > 0) + { + state.Peek().Count++; + } + } + else if (ch == '}') // empty value + { + i++; + var value = state.Pop(); + if (state.Count > 0) + { + if (value.State == SeJsonState.Value) + { + var st = state.Pop(); + + if (state.Count == 2) + { + var str = content.Substring(start, i - start).Trim(); + list.Add(new RootElement() + { + Name = state.Peek().Name, + Json = str, + }); + + start = -1; + } + } + else + { + state.Peek().Count++; + } + } + } + else if (ch == ',') // next object + { + i++; + state.Pop(); + if (state.Count > 0 && state.Peek().Count > 0) + { + state.Peek().Count++; + } + else + { + Errors.Add($"Unexpected char {ch} at position {i}"); + return list; + } + } + else if (ch == 'n' && max > i + 3 && content[i + 1] == 'u' && content[i + 2] == 'l' && content[i + 3] == 'l') + { + i += 4; + state.Pop(); + if (state.Count > 0) + { + state.Peek().Count++; + } + //if (objectName == name) + //{ + // list.Add(null); + //} + + } + else if (ch == 't' && max > i + 3 && content[i + 1] == 'r' && content[i + 2] == 'u' && content[i + 3] == 'e') + { + i += 4; + state.Pop(); + if (state.Count > 0) + { + state.Peek().Count++; + } + //if (objectName == name) + //{ + // list.Add("true"); + //} + } + else if (ch == 'f' && max > i + 4 && content[i + 1] == 'a' && content[i + 2] == 'l' && content[i + 3] == 's' && content[i + 4] == 'e') + { + i += 5; + state.Pop(); + if (state.Count > 0) + { + state.Peek().Count++; + } + //if (objectName == name) + //{ + // list.Add("false"); + //} + } + else if ("+-0123456789".IndexOf(ch) >= 0) + { + var sb = new StringBuilder(); + while (i < max && "+-0123456789.Ee".IndexOf(content[i]) >= 0) + { + sb.Append(content[i]); + i++; + } + state.Pop(); + if (state.Count > 0) + { + state.Peek().Count++; + } + //if (objectName == name) + //{ + // list.Add(sb.ToString()); + //} + } + else if (ch == '{') + { + if (state.Count > 1) + { + var value = state.Pop(); + state.Peek().Count++; + state.Push(value); + } + state.Push(new StateElement + { + State = SeJsonState.Object, + Name = objectName + }); + i++; + } + else if (ch == '[') + { + if (state.Count > 1) + { + var value = state.Pop(); + state.Peek().Count++; + state.Push(value); + } + state.Push(new StateElement + { + State = SeJsonState.Array, + Name = objectName + }); + i++; + //if (start < 0 && objectName == name) + //{ + // start = i; + //} + } + else + { + Errors.Add($"Unexpected char {ch} at position {i}"); + return list; + } + } + + else if (state.Peek().State == SeJsonState.Array) // array, after '[' + { + if (ch == ']') + { + state.Pop(); + i++; + //if (state.Count > 0 && state.Peek().Name == name && start > -1) + //{ + // list.Add(content.Substring(start, i - start - 1).Trim()); + // start = -1; + //} + } + else if (ch == ',' && state.Peek().Count > 0) + { + //if (start >= 0 && state.Peek().State == SeJsonState.Array && state.Peek().Name == name) + //{ + // list.Add(content.Substring(start, i - start).Trim()); + // start = i + 1; + //} + if (state.Count > 0 && state.Peek().Count > 0) + { + state.Peek().Count++; + } + else + { + Errors.Add($"Unexpected char {ch} at position {i}"); + return list; + } + i++; + } + else if (ch == '{') + { + if (state.Count > 0) + { + state.Peek().Count++; + } + state.Push(new StateElement + { + Name = objectName, + State = SeJsonState.Object + }); + i++; + } + else + { + if (state.Count > 0) + { + state.Peek().Count++; + } + state.Push(new StateElement + { + Name = objectName + "_array_value", + State = SeJsonState.Value + }); + } + } + + } + + return list; + } } } diff --git a/src/libse/Common/Settings.cs b/src/libse/Common/Settings.cs index ec7164c0f..4c7804703 100644 --- a/src/libse/Common/Settings.cs +++ b/src/libse/Common/Settings.cs @@ -193,6 +193,8 @@ namespace Nikse.SubtitleEdit.Core.Common public string TextToSpeechEngine { get; set; } public string TextToSpeechLastVoice { get; set; } public string TextToSpeechElevenLabsApiKey { get; set; } + public string TextToSpeechAzureApiKey { get; set; } + public string TextToSpeechAzureRegion { get; set; } public bool DisableVidoInfoViaLabel { get; set; } public bool ListViewSyntaxColorDurationSmall { get; set; } public bool ListViewSyntaxColorDurationBig { get; set; } @@ -557,6 +559,7 @@ namespace Nikse.SubtitleEdit.Core.Common AnthropicApiUrl = "https://api.anthropic.com/v1/messages"; AnthropicPrompt = "Translate from {0} to {1}, keep sentences in {1} as they are, do not censor the translation, give only the output without commenting on what you read:"; AnthropicApiModel = "claude-3-opus-20240229"; + TextToSpeechAzureRegion = "westeurope"; TranslateAllowSplit = true; TranslateViaCopyPasteAutoCopyToClipboard = true; TranslateViaCopyPasteMaxSize = 5000; @@ -5481,6 +5484,18 @@ $HorzAlign = Center settings.Tools.TextToSpeechElevenLabsApiKey = subNode.InnerText; } + subNode = node.SelectSingleNode("TextToSpeechAzureApiKey"); + if (subNode != null) + { + settings.Tools.TextToSpeechAzureApiKey = subNode.InnerText; + } + + subNode = node.SelectSingleNode("TextToSpeechAzureRegion"); + if (subNode != null) + { + settings.Tools.TextToSpeechAzureRegion = subNode.InnerText; + } + subNode = node.SelectSingleNode("TranslateViaCopyPasteAutoCopyToClipboard"); if (subNode != null) { @@ -12023,6 +12038,8 @@ $HorzAlign = Center textWriter.WriteElementString("TextToSpeechEngine", settings.Tools.TextToSpeechEngine); textWriter.WriteElementString("TextToSpeechLastVoice", settings.Tools.TextToSpeechLastVoice); textWriter.WriteElementString("TextToSpeechElevenLabsApiKey", settings.Tools.TextToSpeechElevenLabsApiKey); + textWriter.WriteElementString("TextToSpeechAzureApiKey", settings.Tools.TextToSpeechAzureApiKey); + textWriter.WriteElementString("TextToSpeechAzureRegion", settings.Tools.TextToSpeechAzureRegion); textWriter.WriteElementString("DisableVidoInfoViaLabel", settings.Tools.DisableVidoInfoViaLabel.ToString(CultureInfo.InvariantCulture)); textWriter.WriteElementString("ListViewSyntaxColorDurationSmall", settings.Tools.ListViewSyntaxColorDurationSmall.ToString(CultureInfo.InvariantCulture)); textWriter.WriteElementString("ListViewSyntaxColorDurationBig", settings.Tools.ListViewSyntaxColorDurationBig.ToString(CultureInfo.InvariantCulture)); diff --git a/src/libse/TextToSpeech/PiperModel.cs b/src/libse/TextToSpeech/PiperModel.cs new file mode 100644 index 000000000..005771548 --- /dev/null +++ b/src/libse/TextToSpeech/PiperModel.cs @@ -0,0 +1,30 @@ +using System.Linq; + +namespace Nikse.SubtitleEdit.Core.TextToSpeech +{ + public class PiperModel + { + public string Voice { get; set; } + public string Language { get; set; } + public string Quality { get; set; } + public string Model { get; set; } + public string ModelShort => Model.Split('/').Last(); + + public string Config { get; set; } + public string ConfigShort => Config.Split('/').Last(); + + public override string ToString() + { + return $"{Language} - {Voice} ({Quality})"; + } + + public PiperModel(string voice, string language, string quality, string model, string config) + { + Voice = voice; + Language = language; + Quality = quality; + Model = model; + Config = config; + } + } +} diff --git a/src/libse/TextToSpeech/PiperModels.cs b/src/libse/TextToSpeech/PiperModels.cs deleted file mode 100644 index 7e4841601..000000000 --- a/src/libse/TextToSpeech/PiperModels.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Nikse.SubtitleEdit.Core.TextToSpeech -{ - public class PiperModels - { - public string Voice { get; set; } - public string Language { get; set; } - public string Quality { get; set; } - public string Model { get; set; } - public string ModelShort => Model.Split('/').Last(); - - public string Config { get; set; } - public string ConfigShort => Config.Split('/').Last(); - - public override string ToString() - { - return $"{Language} - {Voice} ({Quality})"; - } - - public PiperModels(string voice, string language, string quality, string model, string config) - { - Voice = voice; - Language = language; - Quality = quality; - Model = model; - Config = config; - } - - public static List GetVoices() - { - var models = new List - { - new PiperModels("kareem", "Arabic", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/ar/ar_JO/kareem/medium/ar_JO-kareem-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/ar/ar_JO/kareem/medium/ar_JO-kareem-medium.onnx.json"), - new PiperModels("upc_ona", "Catalan", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/ca/ca_ES/upc_ona/medium/ca_ES-upc_ona-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/ca/ca_ES/upc_ona/medium/ca_ES-upc_ona-medium.onnx.json"), - new PiperModels("jirka", "Czech", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/cs/cs_CZ/jirka/medium/cs_CZ-jirka-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/cs/cs_CZ/jirka/medium/cs_CZ-jirka-medium.onnx.json"), - new PiperModels("talesyntese", "Danish", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/da/da_DK/talesyntese/medium/da_DK-talesyntese-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/da/da_DK/talesyntese/medium/da_DK-talesyntese-medium.onnx.json"), - new PiperModels("eva_k", "German", "low", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/de/de_DE/eva_k/x_low/de_DE-eva_k-x_low.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/de/de_DE/eva_k/x_low/de_DE-eva_k-x_low.onnx.json"), - new PiperModels("rapunzelina", "Greek", "low", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/el/el_GR/rapunzelina/low/el_GR-rapunzelina-low.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/el/el_GR/rapunzelina/low/el_GR-rapunzelina-low.onnx.json"), - new PiperModels("alan", "English GB", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_GB/alan/medium/en_GB-alan-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_GB/alan/medium/en_GB-alan-medium.onnx.json"), - new PiperModels("alba", "English GB", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_GB/alba/medium/en_GB-alba-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_GB/alba/medium/en_GB-alba-medium.onnx.json"), - new PiperModels("cori", "English GB", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_GB/cori/high/en_GB-cori-high.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_GB/cori/medium/en_GB-cori-medium.onnx.json"), - new PiperModels("jenny_dioco", "English GB", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_GB/jenny_dioco/medium/en_GB-jenny_dioco-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_GB/jenny_dioco/medium/en_GB-jenny_dioco-medium.onnx.json"), - new PiperModels("northern_english_male", "English GB", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_GB/northern_english_male/medium/en_GB-northern_english_male-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_GB/northern_english_male/medium/en_GB-northern_english_male-medium.onnx.json"), - new PiperModels("semaine", "English GB", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_GB/semaine/medium/en_GB-semaine-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_GB/semaine/medium/en_GB-semaine-medium.onnx.json"), - new PiperModels("amy", "English US", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/amy/medium/en_US-amy-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/amy/medium/en_US-amy-medium.onnx.json"), - new PiperModels("arctic", "English US", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/arctic/medium/en_US-arctic-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/arctic/medium/en_US-arctic-medium.onnx.json"), - new PiperModels("hfc_female", "English US", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/hfc_female/medium/en_US-hfc_female-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/hfc_female/medium/en_US-hfc_female-medium.onnx.json"), - new PiperModels("hfc_male", "English US", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/hfc_male/medium/en_US-hfc_male-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/hfc_male/medium/en_US-hfc_male-medium.onnx.json"), - new PiperModels("joe", "English US", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/joe/medium/en_US-joe-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/joe/medium/en_US-joe-medium.onnx.json"), - new PiperModels("kristin", "English US", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/kristin/medium/en_US-kristin-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/kristin/medium/en_US-kristin-medium.onnx.json"), - new PiperModels("kusal", "English US", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/kusal/medium/en_US-kusal-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/kusal/medium/en_US-kusal-medium.onnx.json"), - new PiperModels("l2arctic", "English US", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/l2arctic/medium/en_US-l2arctic-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/l2arctic/medium/en_US-l2arctic-medium.onnx.json"), - new PiperModels("lessac", "English US", "high", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/lessac/high/en_US-lessac-high.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/lessac/high/en_US-lessac-high.onnx.json"), - new PiperModels("libritts", "English US", "high", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/libritts/high/en_US-libritts-high.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/libritts/high/en_US-libritts-high.onnx.json"), - new PiperModels("ljspeech", "English US", "high", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/ljspeech/high/en_US-ljspeech-high.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/ljspeech/high/en_US-ljspeech-high.onnx.json"), - new PiperModels("ryan", "English US", "high", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/ryan/high/en_US-ryan-high.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/ryan/high/en_US-ryan-high.onnx.json"), - new PiperModels("davefx", "Spanish ES", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/es/es_ES/davefx/medium/es_ES-davefx-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/es/es_ES/davefx/medium/es_ES-davefx-medium.onnx.json"), - new PiperModels("claude", "Spanish MX", "high", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/es/es_MX/claude/high/es_MX-claude-high.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/es/es_MX/claude/high/es_MX-claude-high.onnx.json"), - new PiperModels("amir", "Farsi", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/fa/fa_IR/amir/medium/fa_IR-amir-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/fa/fa_IR/amir/medium/fa_IR-amir-medium.onnx.json"), - new PiperModels("gyro", "Farsi", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/fa/fa_IR/gyro/medium/fa_IR-gyro-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/fa/fa_IR/gyro/medium/fa_IR-gyro-medium.onnx.json"), - new PiperModels("harri", "Finnish", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/fi/fi_FI/harri/medium/fi_FI-harri-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/fi/fi_FI/harri/medium/fi_FI-harri-medium.onnx.json"), - new PiperModels("mls", "French", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/fr/fr_FR/mls/medium/fr_FR-mls-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/fr/fr_FR/mls/medium/fr_FR-mls-medium.onnx.json"), - new PiperModels("siwis", "French", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/fr/fr_FR/siwis/medium/fr_FR-siwis-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/fr/fr_FR/siwis/medium/fr_FR-siwis-medium.onnx.json"), - new PiperModels("tom", "French", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/fr/fr_FR/tom/medium/fr_FR-tom-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/fr/fr_FR/tom/medium/fr_FR-tom-medium.onnx.json"), - new PiperModels("upmc", "French", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/fr/fr_FR/upmc/medium/fr_FR-upmc-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/fr/fr_FR/upmc/medium/fr_FR-upmc-medium.onnx.json?"), - new PiperModels("berta", "Hungarian", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/hu/hu_HU/berta/medium/hu_HU-berta-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/hu/hu_HU/berta/medium/hu_HU-berta-medium.onnx.json"), - new PiperModels("imre", "Hungarian", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/hu/hu_HU/imre/medium/hu_HU-imre-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/hu/hu_HU/imre/medium/hu_HU-imre-medium.onnx.json"), - new PiperModels("bui", "Icelandic", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/is/is_IS/bui/medium/is_IS-bui-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/is/is_IS/bui/medium/is_IS-bui-medium.onnx.json"), - new PiperModels("salka", "Icelandic", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/is/is_IS/salka/medium/is_IS-salka-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/is/is_IS/salka/medium/is_IS-salka-medium.onnx.json"), - new PiperModels("steinn", "Icelandic", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/is/is_IS/steinn/medium/is_IS-steinn-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/is/is_IS/steinn/medium/is_IS-steinn-medium.onnx.json"), - new PiperModels("ugla", "Icelandic", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/is/is_IS/ugla/medium/is_IS-ugla-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/is/is_IS/ugla/medium/is_IS-ugla-medium.onnx.json"), - new PiperModels("riccardo", "Italian", "low", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/it/it_IT/riccardo/x_low/it_IT-riccardo-x_low.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/it/it_IT/riccardo/x_low/it_IT-riccardo-x_low.onnx.json"), - new PiperModels("natia", "Georgian", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/ka/ka_GE/natia/medium/ka_GE-natia-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/ka/ka_GE/natia/medium/ka_GE-natia-medium.onnx.json"), - new PiperModels("issai", "Kazakh", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/kk/kk_KZ/issai/high/kk_KZ-issai-high.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/kk/kk_KZ/issai/high/kk_KZ-issai-high.onnx.json"), - new PiperModels("marylux", "Luxembourgish", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/lb/lb_LU/marylux/medium/lb_LU-marylux-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/lb/lb_LU/marylux/medium/lb_LU-marylux-medium.onnx.json"), - new PiperModels("google", "Nepali", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/ne/ne_NP/google/medium/ne_NP-google-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/ne/ne_NP/google/medium/ne_NP-google-medium.onnx.json"), - new PiperModels("nathalie", "Dutch BE", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/nl/nl_BE/nathalie/medium/nl_BE-nathalie-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/nl/nl_BE/nathalie/medium/nl_BE-nathalie-medium.onnx.json"), - new PiperModels("rdh", "Dutch BE", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/nl/nl_BE/rdh/medium/nl_BE-rdh-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/nl/nl_BE/rdh/medium/nl_BE-rdh-medium.onnx.json"), - new PiperModels("mls", "Dutch NL", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/nl/nl_NL/mls/medium/nl_NL-mls-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/nl/nl_NL/mls/medium/nl_NL-mls-medium.onnx.json"), - new PiperModels("talesyntese", "Norwegian", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/no/no_NO/talesyntese/medium/no_NO-talesyntese-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/no/no_NO/talesyntese/medium/no_NO-talesyntese-medium.onnx.json"), - new PiperModels("darkman", "Polish", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/pl/pl_PL/darkman/medium/pl_PL-darkman-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/pl/pl_PL/darkman/medium/pl_PL-darkman-medium.onnx.json"), - new PiperModels("gosia", "Polish", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/pl/pl_PL/gosia/medium/pl_PL-gosia-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/pl/pl_PL/gosia/medium/pl_PL-gosia-medium.onnx.json"), - new PiperModels("mc_speech", "Polish", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/pl/pl_PL/mc_speech/medium/pl_PL-mc_speech-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/pl/pl_PL/mc_speech/medium/pl_PL-mc_speech-medium.onnx.json"), - new PiperModels("faber", "Portuguese BR", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/pt/pt_BR/faber/medium/pt_BR-faber-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/pt/pt_BR/faber/medium/pt_BR-faber-medium.onnx.json"), - new PiperModels("tugão", "Portuguese PT", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/pt/pt_PT/tug%C3%A3o/medium/pt_PT-tug%C3%A3o-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/pt/pt_PT/tug%C3%A3o/medium/pt_PT-tug%C3%A3o-medium.onnx.json"), - new PiperModels("mihai", "Romanian", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/ro/ro_RO/mihai/medium/ro_RO-mihai-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/ro/ro_RO/mihai/medium/ro_RO-mihai-medium.onnx.json"), - new PiperModels("dmitri", "Russian", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/ru/ru_RU/dmitri/medium/ru_RU-dmitri-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/ru/ru_RU/dmitri/medium/ru_RU-dmitri-medium.onnx.json"), - new PiperModels("irina", "Serbian", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/ru/ru_RU/irina/medium/ru_RU-irina-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/ru/ru_RU/irina/medium/ru_RU-irina-medium.onnx.json"), - new PiperModels("lili", "Slovak ", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/sr/sr_RS/serbski_institut/medium/sr_RS-serbski_institut-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/sk/sk_SK/lili/medium/sk_SK-lili-medium.onnx.json"), - new PiperModels("artur", "Slovenian ", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/sl/sl_SI/artur/medium/sl_SI-artur-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/sl/sl_SI/artur/medium/sl_SI-artur-medium.onnx.json"), - new PiperModels("serbski_institut", "Serbian ", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/sr/sr_RS/serbski_institut/medium/sr_RS-serbski_institut-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/sr/sr_RS/serbski_institut/medium/sr_RS-serbski_institut-medium.onnx.json"), - new PiperModels("nst", "Swedish", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/sv/sv_SE/nst/medium/sv_SE-nst-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/sv/sv_SE/nst/medium/sv_SE-nst-medium.onnx.json"), - new PiperModels("lanfrica", "Swahili", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/sw/sw_CD/lanfrica/medium/sw_CD-lanfrica-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/sw/sw_CD/lanfrica/medium/sw_CD-lanfrica-medium.onnx.json"), - new PiperModels("fettah", "Turkish", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/tr/tr_TR/fettah/medium/tr_TR-fettah-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/tr/tr_TR/fettah/medium/tr_TR-fettah-medium.onnx.json"), - new PiperModels("ukrainian_tts", "Ukrainian", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/uk/uk_UA/ukrainian_tts/medium/uk_UA-ukrainian_tts-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/uk/uk_UA/ukrainian_tts/medium/uk_UA-ukrainian_tts-medium.onnx.json"), - new PiperModels("vais1000", "Vietnamese", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/vi/vi_VN/vais1000/medium/vi_VN-vais1000-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/vi/vi_VN/vais1000/medium/vi_VN-vais1000-medium.onnx.json"), - new PiperModels("huayan", "Chinese", "medium", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/zh/zh_CN/huayan/medium/zh_CN-huayan-medium.onnx", "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/zh/zh_CN/huayan/medium/zh_CN-huayan-medium.onnx.json"), - }; - - return models.OrderBy(p=>p.ToString()).ToList(); - } - } -} diff --git a/src/ui/Controls/NikseComboBoxCollection.cs b/src/ui/Controls/NikseComboBoxCollection.cs index 1bc96d4ee..43b4f011c 100644 --- a/src/ui/Controls/NikseComboBoxCollection.cs +++ b/src/ui/Controls/NikseComboBoxCollection.cs @@ -23,6 +23,11 @@ namespace Nikse.SubtitleEdit.Controls _items.AddRange(items); } + public void AddRange(string[] items) + { + _items.AddRange(items); + } + public IEnumerator GetEnumerator() { return _items.GetEnumerator(); diff --git a/src/ui/Forms/Tts/TextToSpeech.Designer.cs b/src/ui/Forms/Tts/TextToSpeech.Designer.cs index 722fb7ac4..9358a4f58 100644 --- a/src/ui/Forms/Tts/TextToSpeech.Designer.cs +++ b/src/ui/Forms/Tts/TextToSpeech.Designer.cs @@ -35,23 +35,28 @@ this.progressBar1 = new System.Windows.Forms.ProgressBar(); this.labelEngine = new System.Windows.Forms.Label(); this.groupBoxMsSettings = new System.Windows.Forms.GroupBox(); + this.labelRegion = new System.Windows.Forms.Label(); + this.nikseComboBoxRegion = new Nikse.SubtitleEdit.Controls.NikseComboBox(); + this.labelVoiceCount = new System.Windows.Forms.Label(); this.checkBoxShowPreview = new System.Windows.Forms.CheckBox(); this.labelApiKey = new System.Windows.Forms.Label(); + this.nikseTextBoxApiKey = new Nikse.SubtitleEdit.Controls.NikseTextBox(); + this.TextBoxTest = new Nikse.SubtitleEdit.Controls.NikseTextBox(); this.buttonTestVoice = new System.Windows.Forms.Button(); this.checkBoxAddToVideoFile = new System.Windows.Forms.CheckBox(); this.labelVoice = new System.Windows.Forms.Label(); + this.nikseComboBoxVoice = new Nikse.SubtitleEdit.Controls.NikseComboBox(); + this.contextMenuStripVoices = new System.Windows.Forms.ContextMenuStrip(this.components); + this.refreshVoicesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.nikseComboBoxEngine = new Nikse.SubtitleEdit.Controls.NikseComboBox(); this.listViewActors = new System.Windows.Forms.ListView(); this.columnHeaderActor = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); this.columnHeaderVoice = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); this.contextMenuStripActors = new System.Windows.Forms.ContextMenuStrip(this.components); this.labelActors = new System.Windows.Forms.Label(); this.buttonCancel = new System.Windows.Forms.Button(); - this.labelVoiceCount = new System.Windows.Forms.Label(); - this.nikseTextBoxApiKey = new Nikse.SubtitleEdit.Controls.NikseTextBox(); - this.TextBoxTest = new Nikse.SubtitleEdit.Controls.NikseTextBox(); - this.nikseComboBoxVoice = new Nikse.SubtitleEdit.Controls.NikseComboBox(); - this.nikseComboBoxEngine = new Nikse.SubtitleEdit.Controls.NikseComboBox(); this.groupBoxMsSettings.SuspendLayout(); + this.contextMenuStripVoices.SuspendLayout(); this.SuspendLayout(); // // buttonOK @@ -112,6 +117,8 @@ // this.groupBoxMsSettings.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left))); + this.groupBoxMsSettings.Controls.Add(this.labelRegion); + this.groupBoxMsSettings.Controls.Add(this.nikseComboBoxRegion); this.groupBoxMsSettings.Controls.Add(this.labelVoiceCount); this.groupBoxMsSettings.Controls.Add(this.checkBoxShowPreview); this.groupBoxMsSettings.Controls.Add(this.labelApiKey); @@ -130,6 +137,83 @@ this.groupBoxMsSettings.TabStop = false; this.groupBoxMsSettings.Text = "Settings"; // + // labelRegion + // + this.labelRegion.AutoSize = true; + this.labelRegion.ImeMode = System.Windows.Forms.ImeMode.NoControl; + this.labelRegion.Location = new System.Drawing.Point(14, 267); + this.labelRegion.Name = "labelRegion"; + this.labelRegion.Size = new System.Drawing.Size(41, 13); + this.labelRegion.TabIndex = 32; + this.labelRegion.Text = "Region"; + // + // nikseComboBoxRegion + // + this.nikseComboBoxRegion.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.nikseComboBoxRegion.BackColor = System.Drawing.SystemColors.Window; + this.nikseComboBoxRegion.BackColorDisabled = System.Drawing.Color.FromArgb(((int)(((byte)(240)))), ((int)(((byte)(240)))), ((int)(((byte)(240))))); + this.nikseComboBoxRegion.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(171)))), ((int)(((byte)(173)))), ((int)(((byte)(179))))); + this.nikseComboBoxRegion.BorderColorDisabled = System.Drawing.Color.FromArgb(((int)(((byte)(120)))), ((int)(((byte)(120)))), ((int)(((byte)(120))))); + this.nikseComboBoxRegion.ButtonForeColor = System.Drawing.SystemColors.ControlText; + this.nikseComboBoxRegion.ButtonForeColorDown = System.Drawing.Color.Orange; + this.nikseComboBoxRegion.ButtonForeColorOver = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(215))))); + this.nikseComboBoxRegion.DropDownHeight = 400; + this.nikseComboBoxRegion.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.nikseComboBoxRegion.DropDownWidth = 0; + this.nikseComboBoxRegion.FormattingEnabled = false; + this.nikseComboBoxRegion.Items.AddRange(new string[] { + "australiaeast", + "brazilsouth", + "canadacentral", + "centralus", + "eastasia", + "eastus", + "eastus2", + "francecentral", + "germanywestcentral", + "centralindia", + "japaneast", + "japanwest", + "jioindiawest", + "koreacentral", + "northcentralus", + "northeurope", + "norwayeast", + "southcentralus", + "southeastasia", + "swedencentral", + "switzerlandnorth", + "switzerlandwest", + "uaenorth", + "usgovarizona", + "usgovvirginia", + "uksouth", + "westcentralus", + "westeurope", + "westus", + "westus2", + "westus3"}); + this.nikseComboBoxRegion.Location = new System.Drawing.Point(17, 283); + this.nikseComboBoxRegion.MaxLength = 32767; + this.nikseComboBoxRegion.Name = "nikseComboBoxRegion"; + this.nikseComboBoxRegion.SelectedIndex = -1; + this.nikseComboBoxRegion.SelectedItem = null; + this.nikseComboBoxRegion.SelectedText = ""; + this.nikseComboBoxRegion.Size = new System.Drawing.Size(351, 23); + this.nikseComboBoxRegion.TabIndex = 31; + this.nikseComboBoxRegion.UsePopupWindow = false; + // + // labelVoiceCount + // + this.labelVoiceCount.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.labelVoiceCount.Location = new System.Drawing.Point(268, 84); + this.labelVoiceCount.Name = "labelVoiceCount"; + this.labelVoiceCount.Size = new System.Drawing.Size(100, 23); + this.labelVoiceCount.TabIndex = 29; + this.labelVoiceCount.Text = "255"; + this.labelVoiceCount.TextAlign = System.Drawing.ContentAlignment.BottomRight; + // // checkBoxShowPreview // this.checkBoxShowPreview.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); @@ -147,15 +231,37 @@ // this.labelApiKey.AutoSize = true; this.labelApiKey.ImeMode = System.Windows.Forms.ImeMode.NoControl; - this.labelApiKey.Location = new System.Drawing.Point(20, 242); + this.labelApiKey.Location = new System.Drawing.Point(20, 224); this.labelApiKey.Name = "labelApiKey"; this.labelApiKey.Size = new System.Drawing.Size(44, 13); this.labelApiKey.TabIndex = 28; this.labelApiKey.Text = "API key"; // + // nikseTextBoxApiKey + // + this.nikseTextBoxApiKey.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.nikseTextBoxApiKey.FocusedColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(215))))); + this.nikseTextBoxApiKey.Location = new System.Drawing.Point(17, 240); + this.nikseTextBoxApiKey.Name = "nikseTextBoxApiKey"; + this.nikseTextBoxApiKey.Size = new System.Drawing.Size(351, 20); + this.nikseTextBoxApiKey.TabIndex = 27; + // + // TextBoxTest + // + this.TextBoxTest.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.TextBoxTest.FocusedColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(215))))); + this.TextBoxTest.Location = new System.Drawing.Point(17, 168); + this.TextBoxTest.Name = "TextBoxTest"; + this.TextBoxTest.Size = new System.Drawing.Size(351, 20); + this.TextBoxTest.TabIndex = 20; + this.TextBoxTest.Text = "Hello, how are you?"; + this.TextBoxTest.KeyDown += new System.Windows.Forms.KeyEventHandler(this.TextBoxTest_KeyDown); + // // buttonTestVoice // - this.buttonTestVoice.Location = new System.Drawing.Point(17, 158); + this.buttonTestVoice.Location = new System.Drawing.Point(17, 139); this.buttonTestVoice.Name = "buttonTestVoice"; this.buttonTestVoice.Size = new System.Drawing.Size(150, 23); this.buttonTestVoice.TabIndex = 15; @@ -180,12 +286,80 @@ // this.labelVoice.AutoSize = true; this.labelVoice.ImeMode = System.Windows.Forms.ImeMode.NoControl; - this.labelVoice.Location = new System.Drawing.Point(14, 108); + this.labelVoice.Location = new System.Drawing.Point(14, 94); this.labelVoice.Name = "labelVoice"; this.labelVoice.Size = new System.Drawing.Size(34, 13); this.labelVoice.TabIndex = 16; this.labelVoice.Text = "Voice"; // + // nikseComboBoxVoice + // + this.nikseComboBoxVoice.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.nikseComboBoxVoice.BackColor = System.Drawing.SystemColors.Window; + this.nikseComboBoxVoice.BackColorDisabled = System.Drawing.Color.FromArgb(((int)(((byte)(240)))), ((int)(((byte)(240)))), ((int)(((byte)(240))))); + this.nikseComboBoxVoice.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(171)))), ((int)(((byte)(173)))), ((int)(((byte)(179))))); + this.nikseComboBoxVoice.BorderColorDisabled = System.Drawing.Color.FromArgb(((int)(((byte)(120)))), ((int)(((byte)(120)))), ((int)(((byte)(120))))); + this.nikseComboBoxVoice.ButtonForeColor = System.Drawing.SystemColors.ControlText; + this.nikseComboBoxVoice.ButtonForeColorDown = System.Drawing.Color.Orange; + this.nikseComboBoxVoice.ButtonForeColorOver = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(215))))); + this.nikseComboBoxVoice.ContextMenuStrip = this.contextMenuStripVoices; + this.nikseComboBoxVoice.DropDownHeight = 400; + this.nikseComboBoxVoice.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.nikseComboBoxVoice.DropDownWidth = 0; + this.nikseComboBoxVoice.FormattingEnabled = false; + this.nikseComboBoxVoice.Location = new System.Drawing.Point(17, 110); + this.nikseComboBoxVoice.MaxLength = 32767; + this.nikseComboBoxVoice.Name = "nikseComboBoxVoice"; + this.nikseComboBoxVoice.SelectedIndex = -1; + this.nikseComboBoxVoice.SelectedItem = null; + this.nikseComboBoxVoice.SelectedText = ""; + this.nikseComboBoxVoice.Size = new System.Drawing.Size(351, 23); + this.nikseComboBoxVoice.TabIndex = 10; + this.nikseComboBoxVoice.UsePopupWindow = false; + // + // contextMenuStripVoices + // + this.contextMenuStripVoices.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.refreshVoicesToolStripMenuItem}); + this.contextMenuStripVoices.Name = "contextMenuStripVoices"; + this.contextMenuStripVoices.Size = new System.Drawing.Size(150, 26); + this.contextMenuStripVoices.Opening += new System.ComponentModel.CancelEventHandler(this.contextMenuStripVoices_Opening); + // + // refreshVoicesToolStripMenuItem + // + this.refreshVoicesToolStripMenuItem.Name = "refreshVoicesToolStripMenuItem"; + this.refreshVoicesToolStripMenuItem.Size = new System.Drawing.Size(149, 22); + this.refreshVoicesToolStripMenuItem.Text = "Refresh voices"; + this.refreshVoicesToolStripMenuItem.Click += new System.EventHandler(this.refreshVoicesToolStripMenuItem_Click); + // + // nikseComboBoxEngine + // + this.nikseComboBoxEngine.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.nikseComboBoxEngine.BackColor = System.Drawing.SystemColors.Window; + this.nikseComboBoxEngine.BackColorDisabled = System.Drawing.Color.FromArgb(((int)(((byte)(240)))), ((int)(((byte)(240)))), ((int)(((byte)(240))))); + this.nikseComboBoxEngine.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(171)))), ((int)(((byte)(173)))), ((int)(((byte)(179))))); + this.nikseComboBoxEngine.BorderColorDisabled = System.Drawing.Color.FromArgb(((int)(((byte)(120)))), ((int)(((byte)(120)))), ((int)(((byte)(120))))); + this.nikseComboBoxEngine.ButtonForeColor = System.Drawing.SystemColors.ControlText; + this.nikseComboBoxEngine.ButtonForeColorDown = System.Drawing.Color.Orange; + this.nikseComboBoxEngine.ButtonForeColorOver = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(215))))); + this.nikseComboBoxEngine.DropDownHeight = 400; + this.nikseComboBoxEngine.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDown; + this.nikseComboBoxEngine.DropDownWidth = 391; + this.nikseComboBoxEngine.FormattingEnabled = false; + this.nikseComboBoxEngine.Location = new System.Drawing.Point(17, 40); + this.nikseComboBoxEngine.MaxLength = 32767; + this.nikseComboBoxEngine.Name = "nikseComboBoxEngine"; + this.nikseComboBoxEngine.SelectedIndex = -1; + this.nikseComboBoxEngine.SelectedItem = null; + this.nikseComboBoxEngine.SelectedText = ""; + this.nikseComboBoxEngine.Size = new System.Drawing.Size(351, 23); + this.nikseComboBoxEngine.TabIndex = 5; + this.nikseComboBoxEngine.TabStop = false; + this.nikseComboBoxEngine.Text = "nikseComboBox1"; + this.nikseComboBoxEngine.UsePopupWindow = false; + // // listViewActors // this.listViewActors.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) @@ -243,91 +417,6 @@ this.buttonCancel.UseVisualStyleBackColor = true; this.buttonCancel.Click += new System.EventHandler(this.buttonCancel_Click); // - // labelVoiceCount - // - this.labelVoiceCount.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.labelVoiceCount.Location = new System.Drawing.Point(268, 98); - this.labelVoiceCount.Name = "labelVoiceCount"; - this.labelVoiceCount.Size = new System.Drawing.Size(100, 23); - this.labelVoiceCount.TabIndex = 29; - this.labelVoiceCount.Text = "255"; - this.labelVoiceCount.TextAlign = System.Drawing.ContentAlignment.BottomRight; - // - // nikseTextBoxApiKey - // - this.nikseTextBoxApiKey.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.nikseTextBoxApiKey.FocusedColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(215))))); - this.nikseTextBoxApiKey.Location = new System.Drawing.Point(17, 258); - this.nikseTextBoxApiKey.Name = "nikseTextBoxApiKey"; - this.nikseTextBoxApiKey.Size = new System.Drawing.Size(351, 20); - this.nikseTextBoxApiKey.TabIndex = 27; - // - // TextBoxTest - // - this.TextBoxTest.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.TextBoxTest.FocusedColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(215))))); - this.TextBoxTest.Location = new System.Drawing.Point(17, 187); - this.TextBoxTest.Name = "TextBoxTest"; - this.TextBoxTest.Size = new System.Drawing.Size(351, 20); - this.TextBoxTest.TabIndex = 20; - this.TextBoxTest.Text = "Hello, how are you?"; - this.TextBoxTest.KeyDown += new System.Windows.Forms.KeyEventHandler(this.TextBoxTest_KeyDown); - // - // nikseComboBoxVoice - // - this.nikseComboBoxVoice.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.nikseComboBoxVoice.BackColor = System.Drawing.SystemColors.Window; - this.nikseComboBoxVoice.BackColorDisabled = System.Drawing.Color.FromArgb(((int)(((byte)(240)))), ((int)(((byte)(240)))), ((int)(((byte)(240))))); - this.nikseComboBoxVoice.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(171)))), ((int)(((byte)(173)))), ((int)(((byte)(179))))); - this.nikseComboBoxVoice.BorderColorDisabled = System.Drawing.Color.FromArgb(((int)(((byte)(120)))), ((int)(((byte)(120)))), ((int)(((byte)(120))))); - this.nikseComboBoxVoice.ButtonForeColor = System.Drawing.SystemColors.ControlText; - this.nikseComboBoxVoice.ButtonForeColorDown = System.Drawing.Color.Orange; - this.nikseComboBoxVoice.ButtonForeColorOver = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(215))))); - this.nikseComboBoxVoice.DropDownHeight = 400; - this.nikseComboBoxVoice.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.nikseComboBoxVoice.DropDownWidth = 0; - this.nikseComboBoxVoice.FormattingEnabled = false; - this.nikseComboBoxVoice.Location = new System.Drawing.Point(17, 124); - this.nikseComboBoxVoice.MaxLength = 32767; - this.nikseComboBoxVoice.Name = "nikseComboBoxVoice"; - this.nikseComboBoxVoice.SelectedIndex = -1; - this.nikseComboBoxVoice.SelectedItem = null; - this.nikseComboBoxVoice.SelectedText = ""; - this.nikseComboBoxVoice.Size = new System.Drawing.Size(351, 23); - this.nikseComboBoxVoice.TabIndex = 10; - this.nikseComboBoxVoice.UsePopupWindow = false; - // - // nikseComboBoxEngine - // - this.nikseComboBoxEngine.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.nikseComboBoxEngine.BackColor = System.Drawing.SystemColors.Window; - this.nikseComboBoxEngine.BackColorDisabled = System.Drawing.Color.FromArgb(((int)(((byte)(240)))), ((int)(((byte)(240)))), ((int)(((byte)(240))))); - this.nikseComboBoxEngine.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(171)))), ((int)(((byte)(173)))), ((int)(((byte)(179))))); - this.nikseComboBoxEngine.BorderColorDisabled = System.Drawing.Color.FromArgb(((int)(((byte)(120)))), ((int)(((byte)(120)))), ((int)(((byte)(120))))); - this.nikseComboBoxEngine.ButtonForeColor = System.Drawing.SystemColors.ControlText; - this.nikseComboBoxEngine.ButtonForeColorDown = System.Drawing.Color.Orange; - this.nikseComboBoxEngine.ButtonForeColorOver = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(215))))); - this.nikseComboBoxEngine.DropDownHeight = 400; - this.nikseComboBoxEngine.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDown; - this.nikseComboBoxEngine.DropDownWidth = 391; - this.nikseComboBoxEngine.FormattingEnabled = false; - this.nikseComboBoxEngine.Location = new System.Drawing.Point(17, 40); - this.nikseComboBoxEngine.MaxLength = 32767; - this.nikseComboBoxEngine.Name = "nikseComboBoxEngine"; - this.nikseComboBoxEngine.SelectedIndex = -1; - this.nikseComboBoxEngine.SelectedItem = null; - this.nikseComboBoxEngine.SelectedText = ""; - this.nikseComboBoxEngine.Size = new System.Drawing.Size(351, 23); - this.nikseComboBoxEngine.TabIndex = 5; - this.nikseComboBoxEngine.TabStop = false; - this.nikseComboBoxEngine.Text = "nikseComboBox1"; - this.nikseComboBoxEngine.UsePopupWindow = false; - this.nikseComboBoxEngine.SelectedIndexChanged += new System.EventHandler(this.nikseComboBoxEngine_SelectedIndexChanged); - // // TextToSpeech // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -342,7 +431,7 @@ this.Controls.Add(this.labelProgress); this.Controls.Add(this.buttonOK); this.KeyPreview = true; - this.MinimumSize = new System.Drawing.Size(827, 481); + this.MinimumSize = new System.Drawing.Size(860, 520); this.Name = "TextToSpeech"; this.ShowIcon = false; this.ShowInTaskbar = false; @@ -356,6 +445,7 @@ this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.TextToSpeech_KeyDown); this.groupBoxMsSettings.ResumeLayout(false); this.groupBoxMsSettings.PerformLayout(); + this.contextMenuStripVoices.ResumeLayout(false); this.ResumeLayout(false); this.PerformLayout(); @@ -384,5 +474,9 @@ private System.Windows.Forms.CheckBox checkBoxShowPreview; private System.Windows.Forms.Button buttonCancel; private System.Windows.Forms.Label labelVoiceCount; + private System.Windows.Forms.Label labelRegion; + private Controls.NikseComboBox nikseComboBoxRegion; + private System.Windows.Forms.ContextMenuStrip contextMenuStripVoices; + private System.Windows.Forms.ToolStripMenuItem refreshVoicesToolStripMenuItem; } } \ No newline at end of file diff --git a/src/ui/Forms/Tts/TextToSpeech.cs b/src/ui/Forms/Tts/TextToSpeech.cs index 1997a100e..5b1e0eecc 100644 --- a/src/ui/Forms/Tts/TextToSpeech.cs +++ b/src/ui/Forms/Tts/TextToSpeech.cs @@ -33,7 +33,9 @@ namespace Nikse.SubtitleEdit.Forms.Tts private bool _abort; private readonly List _actors; private readonly List _engines; + private readonly List _piperVoices; private readonly List _elevenLabVoices; + private readonly List _azureVoices; private bool _actorsOn; private bool _converting; @@ -65,6 +67,20 @@ namespace Nikse.SubtitleEdit.Forms.Tts } } + public class AzureVoiceModel + { + public string DisplayName { get; set; } + public string LocalName { get; set; } + public string ShortName { get; set; } + public string Gender { get; set; } + public string Locale { get; set; } + + public override string ToString() + { + return $"{Locale} - {DisplayName} ({Gender})"; + } + } + public enum TextToSpeechEngineId { Piper, @@ -72,6 +88,7 @@ namespace Nikse.SubtitleEdit.Forms.Tts Coqui, MsSpeechSynthesizer, ElevenLabs, + AzureTextToSpeech, } public TextToSpeech(Subtitle subtitle, SubtitleFormat subtitleFormat, string videoFileName, VideoInfo videoInfo) @@ -84,7 +101,9 @@ namespace Nikse.SubtitleEdit.Forms.Tts _subtitleFormat = subtitleFormat; _videoFileName = videoFileName; _videoInfo = videoInfo; + _piperVoices = new List(); _elevenLabVoices = new List(); + _azureVoices = new List(); _actors = _subtitle.Paragraphs .Where(p => !string.IsNullOrEmpty(p.Actor)) .Select(p => p.Actor) @@ -108,13 +127,14 @@ namespace Nikse.SubtitleEdit.Forms.Tts _engines = new List(); _engines.Add(new TextToSpeechEngine(TextToSpeechEngineId.Piper, "Piper (fast/good)", _engines.Count)); - _engines.Add(new TextToSpeechEngine(TextToSpeechEngineId.Tortoise, "Tortoise TTS (very slow/good)", _engines.Count)); + _engines.Add(new TextToSpeechEngine(TextToSpeechEngineId.Tortoise, "Tortoise TTS (slow/good)", _engines.Count)); _engines.Add(new TextToSpeechEngine(TextToSpeechEngineId.Coqui, "Coqui AI TTS (only one voice)", _engines.Count)); if (Configuration.IsRunningOnWindows) { _engines.Add(new TextToSpeechEngine(TextToSpeechEngineId.MsSpeechSynthesizer, "Microsoft SpeechSynthesizer (very fast/robotic)", _engines.Count)); } _engines.Add(new TextToSpeechEngine(TextToSpeechEngineId.ElevenLabs, "ElevenLabs TTS (online/pay/good)", _engines.Count)); + _engines.Add(new TextToSpeechEngine(TextToSpeechEngineId.AzureTextToSpeech, "Microsoft Azure TTS (online/pay/good)", _engines.Count)); _actorAndVoices = new List(); nikseComboBoxEngine.DropDownStyle = ComboBoxStyle.DropDownList; @@ -135,7 +155,6 @@ namespace Nikse.SubtitleEdit.Forms.Tts labelActors.Visible = false; listViewActors.Visible = false; - nikseComboBoxEngine_SelectedIndexChanged(null, null); if (!SubtitleFormatHasActors() || !_actors.Any()) { @@ -150,6 +169,8 @@ namespace Nikse.SubtitleEdit.Forms.Tts Width = w; } + nikseComboBoxEngine_SelectedIndexChanged(null, null); + nikseComboBoxEngine.SelectedIndexChanged += nikseComboBoxEngine_SelectedIndexChanged; nikseComboBoxVoice.Text = Configuration.Settings.Tools.TextToSpeechLastVoice; } @@ -307,6 +328,12 @@ namespace Nikse.SubtitleEdit.Forms.Tts return result; } + if (engine.Id == TextToSpeechEngineId.AzureTextToSpeech) + { + var result = await GenerateParagraphAudioAzure(subtitle, showProgressBar, overrideFileName); + return result; + } + return false; } @@ -594,7 +621,6 @@ namespace Nikse.SubtitleEdit.Forms.Tts progressBar1.Value = 0; progressBar1.Maximum = subtitle.Paragraphs.Count; progressBar1.Visible = showProgressBar; - var voices = PiperModels.GetVoices(); for (var index = 0; index < subtitle.Paragraphs.Count; index++) { @@ -618,13 +644,13 @@ namespace Nikse.SubtitleEdit.Forms.Tts } } - var voice = voices.First(x => x.ToString() == nikseComboBoxVoice.Text); + var voice = _piperVoices.First(x => x.ToString() == nikseComboBoxVoice.Text); if (_actorAndVoices.Count > 0 && !string.IsNullOrEmpty(p.Actor)) { var f = _actorAndVoices.FirstOrDefault(x => x.Actor == p.Actor); if (f != null && !string.IsNullOrEmpty(f.Voice)) { - voice = voices[f.VoiceIndex]; + voice = _piperVoices[f.VoiceIndex]; } } @@ -900,6 +926,171 @@ namespace Nikse.SubtitleEdit.Forms.Tts return true; } + private async Task> GetAzureVoices(bool useCache) + { + var ttsPath = Path.Combine(Configuration.DataDirectory, "TextToSpeech"); + if (!Directory.Exists(ttsPath)) + { + Directory.CreateDirectory(ttsPath); + } + + var azurePath = Path.Combine(ttsPath, "Azure"); + if (!Directory.Exists(azurePath)) + { + Directory.CreateDirectory(azurePath); + } + + var list = new List(); + var jsonFileName = Path.Combine(azurePath, "AzureVoices.json"); + if (!File.Exists(jsonFileName)) + { + var asm = System.Reflection.Assembly.GetExecutingAssembly(); + var stream = asm.GetManifestResourceStream("Nikse.SubtitleEdit.Resources.AzureVoices.zip"); + if (stream != null) + { + using (var zip = ZipExtractor.Open(stream)) + { + var dir = zip.ReadCentralDir(); + foreach (var entry in dir) + { + var fileName = Path.GetFileName(entry.FilenameInZip); + if (!string.IsNullOrEmpty(fileName)) + { + var name = entry.FilenameInZip; + var path = Path.Combine(azurePath, name.Replace('/', Path.DirectorySeparatorChar)); + zip.ExtractFile(entry, path); + } + } + } + } + } + + if (!useCache) + { + var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json"); + httpClient.DefaultRequestHeaders.TryAddWithoutValidation("accept", "application/json"); + httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Ocp-Apim-Subscription-Key", nikseTextBoxApiKey.Text.Trim()); + + var url = $"https://{nikseComboBoxRegion.Text.Trim()}.tts.speech.microsoft.com/cognitiveservices/voices/list"; + var result = await httpClient.GetAsync(new Uri(url), CancellationToken.None); + var bytes = await result.Content.ReadAsByteArrayAsync(); + + if (!result.IsSuccessStatusCode) + { + Cursor = Cursors.Default; + var error = Encoding.UTF8.GetString(bytes).Trim(); + SeLogger.Error($"Failed getting voices form Azure via url \"{url}\" : Status code={result.StatusCode} {error}"); + MessageBox.Show(this, "Calling url: " + url + Environment.NewLine + "Got error: " + error); + return new List(); + } + + File.WriteAllBytes(jsonFileName, bytes); + } + + var json = File.ReadAllText(jsonFileName); + var parser = new SeJsonParser(); + var arr = parser.GetArrayElements(json); + foreach (var item in arr) + { + var displayName = parser.GetFirstObject(item, "DisplayName"); + var localName = parser.GetFirstObject(item, "LocalName"); + var shortName = parser.GetFirstObject(item, "ShortName"); + var gender = parser.GetFirstObject(item, "Gender"); + var locale = parser.GetFirstObject(item, "Locale"); + + list.Add(new AzureVoiceModel + { + DisplayName = displayName, + LocalName = localName, + ShortName = shortName, + Gender = gender, + Locale = locale, + }); + } + + return list; + } + + private async Task GenerateParagraphAudioAzure(Subtitle subtitle, bool showProgressBar, string overrideFileName) + { + if (string.IsNullOrWhiteSpace(nikseTextBoxApiKey.Text)) + { + MessageBox.Show("Please add API key"); + nikseTextBoxApiKey.Focus(); + return false; + } + + if (string.IsNullOrWhiteSpace(nikseComboBoxRegion.Text)) + { + MessageBox.Show("Please add region"); + nikseComboBoxRegion.Focus(); + return false; + } + + var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "ssml+xml"); + httpClient.DefaultRequestHeaders.TryAddWithoutValidation("accept", "audio/mpeg"); + httpClient.DefaultRequestHeaders.TryAddWithoutValidation("X-Microsoft-OutputFormat", "audio-16khz-32kbitrate-mono-mp3"); + httpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "SubtitleEdit"); + httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Ocp-Apim-Subscription-Key", nikseTextBoxApiKey.Text.Trim()); + + progressBar1.Value = 0; + progressBar1.Maximum = subtitle.Paragraphs.Count; + progressBar1.Visible = showProgressBar; + + var voices = _azureVoices; + var v = nikseComboBoxVoice.Text; + + for (var index = 0; index < subtitle.Paragraphs.Count; index++) + { + if (showProgressBar) + { + progressBar1.Value = index + 1; + labelProgress.Text = string.Format(LanguageSettings.Current.TextToSpeech.GeneratingSpeechFromTextXOfY, index + 1, subtitle.Paragraphs.Count); + } + + var p = subtitle.Paragraphs[index]; + var outputFileName = Path.Combine(_waveFolder, string.IsNullOrEmpty(overrideFileName) ? index + ".mp3" : overrideFileName.Replace(".wav", ".mp3")); + + if (_actorAndVoices.Count > 0 && !string.IsNullOrEmpty(p.Actor)) + { + var f = _actorAndVoices.FirstOrDefault(x => x.Actor == p.Actor); + if (f != null && !string.IsNullOrEmpty(f.Voice)) + { + v = f.Voice; + } + } + + var voice = voices.First(x => x.ToString() == v); + + var url = $"https://{nikseComboBoxRegion.Text.Trim()}.tts.speech.microsoft.com/cognitiveservices/v1"; + var data = $"{System.Net.WebUtility.HtmlEncode(p.Text)}"; + var content = new StringContent(data, Encoding.UTF8); + var result = await httpClient.PostAsync(url, content, CancellationToken.None); + var bytes = await result.Content.ReadAsByteArrayAsync(); + + if (!result.IsSuccessStatusCode) + { + var error = Encoding.UTF8.GetString(bytes).Trim(); + SeLogger.Error($"Azure TTS failed calling API on address {url} : Status code={result.StatusCode} {error}" + Environment.NewLine + "Data=" + data); + MessageBox.Show(this, "Calling url: " + url + Environment.NewLine + "With: " + data + Environment.NewLine + Environment.NewLine + "Error: " + error + result); + return false; + } + + File.WriteAllBytes(outputFileName, bytes); + + progressBar1.Refresh(); + labelProgress.Refresh(); + Application.DoEvents(); + } + + progressBar1.Visible = false; + labelProgress.Text = string.Empty; + + return true; + } + private void buttonOK_Click(object sender, EventArgs e) { EditedSubtitle = _subtitle; @@ -913,6 +1104,8 @@ namespace Nikse.SubtitleEdit.Forms.Tts labelApiKey.Visible = false; nikseTextBoxApiKey.Visible = false; + labelRegion.Visible = false; + nikseComboBoxRegion.Visible = false; labelVoice.Text = LanguageSettings.Current.TextToSpeech.Voice; if (SubtitleFormatHasActors() && _actors.Any()) @@ -937,7 +1130,12 @@ namespace Nikse.SubtitleEdit.Forms.Tts if (engine.Id == TextToSpeechEngineId.Piper) { - foreach (var voice in PiperModels.GetVoices()) + if (_piperVoices.Count == 0) + { + _piperVoices.AddRange(GetPiperVoices(true)); + } + + foreach (var voice in _piperVoices) { nikseComboBoxVoice.Items.Add(voice.ToString()); } @@ -983,7 +1181,7 @@ namespace Nikse.SubtitleEdit.Forms.Tts if (_elevenLabVoices.Count == 0) { - _elevenLabVoices.AddRange(GetElevenLabVoices()); + _elevenLabVoices.AddRange(GetElevenLabVoices(true)); } foreach (var voice in _elevenLabVoices) @@ -992,6 +1190,22 @@ namespace Nikse.SubtitleEdit.Forms.Tts } } + if (engine.Id == TextToSpeechEngineId.AzureTextToSpeech) + { + nikseTextBoxApiKey.Text = Configuration.Settings.Tools.TextToSpeechAzureApiKey; + nikseComboBoxRegion.Text = Configuration.Settings.Tools.TextToSpeechAzureRegion; + + labelApiKey.Visible = true; + nikseTextBoxApiKey.Visible = true; + + var azureVoices = GetAzureVoices(true).Result; + _azureVoices.AddRange(azureVoices); + nikseComboBoxVoice.Items.AddRange(_azureVoices.Select(p => p.ToString()).ToArray()); + + labelRegion.Visible = true; + nikseComboBoxRegion.Visible = true; + } + if (nikseComboBoxVoice.Items.Count > 0) { nikseComboBoxVoice.SelectedIndex = 0; @@ -1025,7 +1239,7 @@ namespace Nikse.SubtitleEdit.Forms.Tts if (engine.Id == TextToSpeechEngineId.Piper) { - var voices = PiperModels.GetVoices(); + var voices = _piperVoices; foreach (var voiceLanguage in voices .GroupBy(p => p.Language) .OrderBy(p => p.Key)) @@ -1062,7 +1276,98 @@ namespace Nikse.SubtitleEdit.Forms.Tts parent.DropDownItems.Add(tsi); } - DarkTheme.SetDarkTheme(parent); + if (Configuration.Settings.General.UseDarkTheme) + { + DarkTheme.SetDarkTheme(parent); + } + } + } + } + if (engine.Id == TextToSpeechEngineId.AzureTextToSpeech) + { + var voices = _azureVoices; + foreach (var voiceLanguage in voices + .GroupBy(p => p.Locale.Substring(0, 2)) + .OrderBy(p => p.Key)) + { + if (voiceLanguage.Count() == 1) + { + var voice = voiceLanguage.First(); + var tsi = new ToolStripMenuItem(); + tsi.Tag = new ActorAndVoice { Voice = voice.ToString(), VoiceIndex = voices.IndexOf(voice) }; + tsi.Text = voice.ToString(); + tsi.Click += (x, args) => + { + var a = (ActorAndVoice)(x as ToolStripItem).Tag; + SetActor(a); + }; + contextMenuStripActors.Items.Add(tsi); + } + else + { + if (voiceLanguage.Count() < 30) + { + var parent = new ToolStripMenuItem(); + parent.Text = voiceLanguage.Key; + contextMenuStripActors.Items.Add(parent); + var tsiList = new List(nikseComboBoxVoice.Items.Count); + foreach (var voice in voiceLanguage.OrderBy(p => p.ToString()).ToList()) + { + var tsi = new ToolStripMenuItem(); + tsi.Tag = new ActorAndVoice { Voice = voice.ToString(), VoiceIndex = voices.IndexOf(voice) }; + tsi.Text = voice.ToString(); + tsi.Click += (x, args) => + { + var a = (ActorAndVoice)(x as ToolStripItem).Tag; + SetActor(a); + }; + tsiList.Add(tsi); + } + parent.DropDownItems.AddRange(tsiList.ToArray()); + + if (Configuration.Settings.General.UseDarkTheme) + { + DarkTheme.SetDarkTheme(parent); + } + } + else + { + var parent = new ToolStripMenuItem(); + parent.Text = voiceLanguage.Key; + contextMenuStripActors.Items.Add(parent); + var subGroup = voiceLanguage.GroupBy(p => p.Locale); + foreach (var subGroupElement in subGroup) + { + var groupParent = new ToolStripMenuItem(); + groupParent.Text = subGroupElement.Key; + parent.DropDownItems.Add(groupParent); + var tsiList = new List(subGroupElement.Count()); + foreach (var voice in subGroupElement.OrderBy(p => p.DisplayName).ToList()) + { + var tsi = new ToolStripMenuItem(); + tsi.Tag = new ActorAndVoice { Voice = voice.ToString(), VoiceIndex = voices.IndexOf(voice) }; + tsi.Text = voice.ToString(); + tsi.Click += (x, args) => + { + var a = (ActorAndVoice)(x as ToolStripItem).Tag; + SetActor(a); + }; + tsiList.Add(tsi); + } + + groupParent.DropDownItems.AddRange(tsiList.ToArray()); + + if (Configuration.Settings.General.UseDarkTheme) + { + DarkTheme.SetDarkTheme(groupParent); + } + + } + if (Configuration.Settings.General.UseDarkTheme) + { + DarkTheme.SetDarkTheme(parent); + } + } } } } @@ -1105,12 +1410,16 @@ namespace Nikse.SubtitleEdit.Forms.Tts parent.DropDownItems.Add(tsi); } - DarkTheme.SetDarkTheme(parent); + if (Configuration.Settings.General.UseDarkTheme) + { + DarkTheme.SetDarkTheme(parent); + } } } } else { + var tsiList = new List(nikseComboBoxVoice.Items.Count); for (var index = 0; index < nikseComboBoxVoice.Items.Count; index++) { var item = nikseComboBoxVoice.Items[index]; @@ -1123,8 +1432,10 @@ namespace Nikse.SubtitleEdit.Forms.Tts var a = (ActorAndVoice)(x as ToolStripItem).Tag; SetActor(a); }; - contextMenuStripActors.Items.Add(tsi); + tsiList.Add(tsi); } + + contextMenuStripActors.Items.AddRange(tsiList.ToArray()); } labelActors.Visible = true; @@ -1134,7 +1445,104 @@ namespace Nikse.SubtitleEdit.Forms.Tts } } - private List GetElevenLabVoices() + private List GetPiperVoices(bool useCache) + { + var ttsPath = Path.Combine(Configuration.DataDirectory, "TextToSpeech"); + if (!Directory.Exists(ttsPath)) + { + Directory.CreateDirectory(ttsPath); + } + + var elevenLabsPath = Path.Combine(ttsPath, "Piper"); + if (!Directory.Exists(elevenLabsPath)) + { + Directory.CreateDirectory(elevenLabsPath); + } + + var result = new List(); + + var jsonFileName = Path.Combine(elevenLabsPath, "voices.json"); + + if (!File.Exists(jsonFileName)) + { + var asm = System.Reflection.Assembly.GetExecutingAssembly(); + var stream = asm.GetManifestResourceStream("Nikse.SubtitleEdit.Resources.PiperVoices.zip"); + if (stream != null) + { + using (var zip = ZipExtractor.Open(stream)) + { + var dir = zip.ReadCentralDir(); + foreach (var entry in dir) + { + var fileName = Path.GetFileName(entry.FilenameInZip); + if (!string.IsNullOrEmpty(fileName)) + { + var name = entry.FilenameInZip; + var path = Path.Combine(elevenLabsPath, name.Replace('/', Path.DirectorySeparatorChar)); + zip.ExtractFile(entry, path); + } + } + } + } + } + + if (!useCache) + { + var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json"); + httpClient.DefaultRequestHeaders.TryAddWithoutValidation("accept", "application/json"); + var url = "https://huggingface.co/rhasspy/piper-voices/resolve/main/voices.json?download=true"; + var res = httpClient.GetAsync(new Uri(url), CancellationToken.None).Result; + var bytes = res.Content.ReadAsByteArrayAsync().Result; + + if (!res.IsSuccessStatusCode) + { + var error = Encoding.UTF8.GetString(bytes).Trim(); + SeLogger.Error($"Failed getting voices form Piper via url \"{url}\" : Status code={res.StatusCode} {error}"); + MessageBox.Show(this, "Calling url: " + url + Environment.NewLine + "Got error: " + error + " " + result); + return result; + } + + File.WriteAllBytes(jsonFileName, bytes); + } + + if (File.Exists(jsonFileName)) + { + var json = File.ReadAllText(jsonFileName); + var parser = new SeJsonParser(); + var arr = parser.GetRootElements(json); + + foreach (var element in arr) + { + var elements = parser.GetRootElements(element.Json); + var name = elements.FirstOrDefault(p => p.Name == "name"); + var quality = elements.FirstOrDefault(p => p.Name == "quality"); + var language = elements.FirstOrDefault(p => p.Name == "language"); + var files = elements.FirstOrDefault(p => p.Name == "files"); + + if (name != null && quality != null && language != null && files != null) + { + var languageDisplay = parser.GetFirstObject(language.Json, "name_english"); + var languageFamily = parser.GetFirstObject(language.Json, "family"); + var languageCode = parser.GetFirstObject(language.Json, "code"); + + var filesElements = parser.GetRootElements(files.Json); + var model = filesElements.FirstOrDefault(p => p.Name.EndsWith(".onnx")); + var config = filesElements.FirstOrDefault(p => p.Name.EndsWith("onnx.json")); + if (model != null && config != null) + { + var modelUrl = "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/" + model.Name; + var configUrl = "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/" + config.Name; + result.Add(new PiperModel(name.Json, languageDisplay, quality.Json, modelUrl, configUrl)); + } + } + } + } + + return result; + } + + private List GetElevenLabVoices(bool useCache) { var ttsPath = Path.Combine(Configuration.DataDirectory, "TextToSpeech"); if (!Directory.Exists(ttsPath)) @@ -1175,6 +1583,33 @@ namespace Nikse.SubtitleEdit.Forms.Tts } } + if (!useCache) + { + var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json"); + httpClient.DefaultRequestHeaders.TryAddWithoutValidation("accept", "application/json"); + + if (!string.IsNullOrWhiteSpace(nikseTextBoxApiKey.Text)) + { + httpClient.DefaultRequestHeaders.TryAddWithoutValidation("xi-api-key", nikseTextBoxApiKey.Text.Trim()); + } + + var url = "https://api.elevenlabs.io/v1/voices"; + var res = httpClient.GetAsync(new Uri(url), CancellationToken.None).Result; + var bytes = res.Content.ReadAsByteArrayAsync().Result; + + if (!res.IsSuccessStatusCode) + { + Cursor = Cursors.Default; + var error = Encoding.UTF8.GetString(bytes).Trim(); + SeLogger.Error($"Failed getting voices form ElevenLabs via url \"{url}\" : Status code={res.StatusCode} {error}"); + MessageBox.Show(this, "Calling url: " + url + Environment.NewLine + "Got error: " + error); + return new List(); + } + + File.WriteAllBytes(jsonFileName, bytes); + } + if (File.Exists(jsonFileName)) { var json = File.ReadAllText(jsonFileName); @@ -1332,6 +1767,11 @@ namespace Nikse.SubtitleEdit.Forms.Tts { Configuration.Settings.Tools.TextToSpeechElevenLabsApiKey = nikseTextBoxApiKey.Text; } + else if (engine.Id == TextToSpeechEngineId.AzureTextToSpeech) + { + Configuration.Settings.Tools.TextToSpeechAzureApiKey = nikseTextBoxApiKey.Text; + Configuration.Settings.Tools.TextToSpeechAzureRegion = nikseComboBoxRegion.Text; + } Configuration.Settings.Tools.TextToSpeechEngine = engine.Id.ToString(); Configuration.Settings.Tools.TextToSpeechLastVoice = nikseComboBoxVoice.Text; @@ -1356,21 +1796,26 @@ namespace Nikse.SubtitleEdit.Forms.Tts public string GetParagraphAudio(Paragraph paragraph) { - if (_actorsOn) + if (_actorsOn && _actorAndVoices.Count > 0 && !string.IsNullOrEmpty(paragraph.Actor)) { - var engine = _engines.First(p => p.Index == nikseComboBoxEngine.SelectedIndex); - - if (engine.Id == TextToSpeechEngineId.Piper) + var f = _actorAndVoices.FirstOrDefault(x => x.Actor == paragraph.Actor); + if (f != null && !string.IsNullOrEmpty(f.Voice)) { - var voices = PiperModels.GetVoices(); - var voice = voices.First(x => x.ToString() == nikseComboBoxVoice.Text); - if (_actorAndVoices.Count > 0 && !string.IsNullOrEmpty(paragraph.Actor)) + var engine = _engines.First(p => p.Index == nikseComboBoxEngine.SelectedIndex); + + if (engine.Id == TextToSpeechEngineId.Piper) { - var f = _actorAndVoices.FirstOrDefault(x => x.Actor == paragraph.Actor); - if (f != null && !string.IsNullOrEmpty(f.Voice)) - { - return voices[f.VoiceIndex].Voice; - } + return _piperVoices[f.VoiceIndex].ToString(); + } + + if (engine.Id == TextToSpeechEngineId.AzureTextToSpeech) + { + return _azureVoices[f.VoiceIndex].ToString(); + } + + if (engine.Id == TextToSpeechEngineId.ElevenLabs) + { + return _elevenLabVoices[f.VoiceIndex].ToString(); } } } @@ -1405,5 +1850,79 @@ namespace Nikse.SubtitleEdit.Forms.Tts nikseComboBoxEngine.DropDownWidth = nikseComboBoxEngine.Width; nikseComboBoxVoice.DropDownWidth = nikseComboBoxVoice.Width; } + + private void RefreshVoices() + { + if (nikseTextBoxApiKey.Visible && string.IsNullOrWhiteSpace(nikseTextBoxApiKey.Text)) + { + Cursor = Cursors.Default; + MessageBox.Show("Please add API key"); + nikseTextBoxApiKey.Focus(); + return; + } + + var engine = _engines.First(p => p.Index == nikseComboBoxEngine.SelectedIndex); + if (engine.Id == TextToSpeechEngineId.AzureTextToSpeech) + { + if (string.IsNullOrWhiteSpace(nikseComboBoxRegion.Text)) + { + Cursor = Cursors.Default; + MessageBox.Show("Please add region"); + nikseComboBoxRegion.Focus(); + return; + } + + var _ = GetAzureVoices(false).Result; + nikseComboBoxEngine_SelectedIndexChanged(null, null); + } + else if (engine.Id == TextToSpeechEngineId.ElevenLabs) + { + GetElevenLabVoices(false); + nikseComboBoxEngine_SelectedIndexChanged(null, null); + } + } + + private void contextMenuStripVoices_Opening(object sender, System.ComponentModel.CancelEventArgs e) + { + var engine = _engines.First(p => p.Index == nikseComboBoxEngine.SelectedIndex); + if ( + //engine.Id == TextToSpeechEngineId.AzureTextToSpeech || + engine.Id == TextToSpeechEngineId.ElevenLabs || + engine.Id == TextToSpeechEngineId.Piper + ) + { + return; + } + + e.Cancel = true; + } + + private void refreshVoicesToolStripMenuItem_Click(object sender, EventArgs e) + { + var dr = MessageBox.Show(this, "Download updated voice list from the internet?", "Update voices", MessageBoxButtons.YesNoCancel); + if (dr != DialogResult.Yes) + { + return; + } + + try + { + Cursor = Cursors.WaitCursor; + RefreshVoices(); + Cursor = Cursors.Default; + MessageBox.Show(this, "Voice list downloaded"); + } + catch (Exception ex) + { + Cursor = Cursors.Default; + MessageBox.Show(this, "Voice list download failed!" + Environment.NewLine + + Environment.NewLine + + ex.Message); + } + finally + { + Cursor = Cursors.Default; + } + } } } \ No newline at end of file diff --git a/src/ui/Forms/Tts/TextToSpeech.resx b/src/ui/Forms/Tts/TextToSpeech.resx index dec39f79f..78a54a51c 100644 --- a/src/ui/Forms/Tts/TextToSpeech.resx +++ b/src/ui/Forms/Tts/TextToSpeech.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 200, 17 + 17, 17 diff --git a/src/ui/Resources/AzureVoices.zip b/src/ui/Resources/AzureVoices.zip new file mode 100644 index 000000000..70b9ac9fa Binary files /dev/null and b/src/ui/Resources/AzureVoices.zip differ diff --git a/src/ui/Resources/PiperVoices.zip b/src/ui/Resources/PiperVoices.zip new file mode 100644 index 000000000..d5e4478e8 Binary files /dev/null and b/src/ui/Resources/PiperVoices.zip differ diff --git a/src/ui/SubtitleEdit.csproj b/src/ui/SubtitleEdit.csproj index d8eb752d0..b537a0b4d 100644 --- a/src/ui/SubtitleEdit.csproj +++ b/src/ui/SubtitleEdit.csproj @@ -2454,7 +2454,9 @@ + + Designer