diff --git a/src/libse/TextToSpeech/PiperModels.cs b/src/libse/TextToSpeech/PiperModels.cs index 56bef096d..f1a0eb688 100644 --- a/src/libse/TextToSpeech/PiperModels.cs +++ b/src/libse/TextToSpeech/PiperModels.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; namespace Nikse.SubtitleEdit.Core.TextToSpeech { @@ -7,12 +9,24 @@ namespace Nikse.SubtitleEdit.Core.TextToSpeech 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() diff --git a/src/ui/Forms/Main.cs b/src/ui/Forms/Main.cs index e8b2058f1..cae5a2425 100644 --- a/src/ui/Forms/Main.cs +++ b/src/ui/Forms/Main.cs @@ -47,6 +47,7 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; +using Nikse.SubtitleEdit.Forms.Tts; using static System.Windows.Forms.VisualStyles.VisualStyleElement.TrackBar; using CheckForUpdatesHelper = Nikse.SubtitleEdit.Logic.CheckForUpdatesHelper; using MessageBox = Nikse.SubtitleEdit.Forms.SeMsgBox.MessageBox; diff --git a/src/ui/Forms/Tts/PiperDownload.Designer.cs b/src/ui/Forms/Tts/PiperDownload.Designer.cs new file mode 100644 index 000000000..210e843ea --- /dev/null +++ b/src/ui/Forms/Tts/PiperDownload.Designer.cs @@ -0,0 +1,85 @@ +namespace Nikse.SubtitleEdit.Forms.Tts +{ + sealed partial class PiperDownload + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.labelPleaseWait = new System.Windows.Forms.Label(); + this.buttonCancel = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // labelPleaseWait + // + this.labelPleaseWait.AutoSize = true; + this.labelPleaseWait.Location = new System.Drawing.Point(12, 24); + this.labelPleaseWait.Name = "labelPleaseWait"; + this.labelPleaseWait.Size = new System.Drawing.Size(70, 13); + this.labelPleaseWait.TabIndex = 17; + this.labelPleaseWait.Text = "Please wait..."; + // + // buttonCancel + // + this.buttonCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.buttonCancel.ImeMode = System.Windows.Forms.ImeMode.NoControl; + this.buttonCancel.Location = new System.Drawing.Point(252, 74); + this.buttonCancel.Name = "buttonCancel"; + this.buttonCancel.Size = new System.Drawing.Size(75, 23); + this.buttonCancel.TabIndex = 16; + this.buttonCancel.Text = "C&ancel"; + this.buttonCancel.UseVisualStyleBackColor = true; + this.buttonCancel.Click += new System.EventHandler(this.buttonCancel_Click); + // + // DownloadFfmpeg + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(339, 107); + this.Controls.Add(this.labelPleaseWait); + this.Controls.Add(this.buttonCancel); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.KeyPreview = true; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "DownloadFfmpeg"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "DownloadFfmpeg"; + this.Shown += new System.EventHandler(this.DownloadFfmpeg_Shown); + this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.DownloadFfmpeg_KeyDown); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label labelPleaseWait; + private System.Windows.Forms.Button buttonCancel; + } +} \ No newline at end of file diff --git a/src/ui/Forms/Tts/PiperDownload.cs b/src/ui/Forms/Tts/PiperDownload.cs new file mode 100644 index 000000000..74a2ba2ad --- /dev/null +++ b/src/ui/Forms/Tts/PiperDownload.cs @@ -0,0 +1,172 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Windows.Forms; +using Nikse.SubtitleEdit.Core.Common; +using Nikse.SubtitleEdit.Core.Http; +using Nikse.SubtitleEdit.Logic; +using MessageBox = Nikse.SubtitleEdit.Forms.SeMsgBox.MessageBox; + +namespace Nikse.SubtitleEdit.Forms.Tts +{ + public sealed partial class PiperDownload : Form + { + public bool AutoClose { get; internal set; } + public string ModelUrl { get; internal set; } + public string ModelFileName { get; internal set; } + public string PiperPath { get; set; } + + private readonly CancellationTokenSource _cancellationTokenSource; + private readonly string _title; + + public PiperDownload(string title) + { + UiUtil.PreInitialize(this); + InitializeComponent(); + UiUtil.FixFonts(this); + _title = title; + Text = string.Format(LanguageSettings.Current.Settings.DownloadX, title); + buttonCancel.Text = LanguageSettings.Current.General.Cancel; + UiUtil.FixLargeFonts(this, buttonCancel); + _cancellationTokenSource = new CancellationTokenSource(); + } + + private void DownloadFfmpeg_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Escape) + { + DialogResult = DialogResult.Cancel; + } + } + + private void buttonCancel_Click(object sender, EventArgs e) + { + if (buttonCancel.Text == LanguageSettings.Current.General.Ok) + { + DialogResult = DialogResult.OK; + return; + } + + _cancellationTokenSource.Cancel(); + DialogResult = DialogResult.Cancel; + } + + private void DownloadFfmpeg_Shown(object sender, EventArgs e) + { + var url = "https://github.com/SubtitleEdit/support-files/releases/download/piper-2023.11.14-2/piper2023.11.14-2.zip"; + if (!string.IsNullOrEmpty(ModelUrl)) + { + url = ModelUrl; + } + + try + { + labelPleaseWait.Text = LanguageSettings.Current.General.PleaseWait; + Cursor = Cursors.WaitCursor; + var httpClient = DownloaderFactory.MakeHttpClient(); + using (var downloadStream = new MemoryStream()) + { + var downloadTask = httpClient.DownloadAsync(url, downloadStream, new Progress((progress) => + { + var pct = (int)Math.Round(progress * 100.0, MidpointRounding.AwayFromZero); + labelPleaseWait.Text = LanguageSettings.Current.General.PleaseWait + " " + pct + "%"; + }), _cancellationTokenSource.Token); + + while (!downloadTask.IsCompleted && !downloadTask.IsCanceled) + { + Application.DoEvents(); + } + + if (downloadTask.IsCanceled) + { + DialogResult = DialogResult.Cancel; + labelPleaseWait.Refresh(); + return; + } + + CompleteDownload(downloadStream); + } + } + catch (Exception exception) + { + labelPleaseWait.Text = string.Empty; + Cursor = Cursors.Default; + MessageBox.Show($"Unable to download {url}!" + Environment.NewLine + Environment.NewLine + + exception.Message + Environment.NewLine + Environment.NewLine + exception.StackTrace); + DialogResult = DialogResult.Cancel; + } + } + + private void CompleteDownload(MemoryStream downloadStream) + { + if (downloadStream.Length == 0) + { + throw new Exception("No content downloaded - missing file or no internet connection!"); + } + + var sha512Hashes = new[] + { + "b09e87706e5194b320f28a13298e92e7c2e9acd6eecc8f22ba268d93b554e5c83373e8648b88dcb53756271a71571fd2ae689b3a09596c5313ad955bcea6bbfb", // Piper2023.11.14-2 + }; + var hash = Utilities.GetSha512Hash(downloadStream.ToArray()); + if (string.IsNullOrEmpty(ModelUrl) && !sha512Hashes.Contains(hash)) + { + MessageBox.Show("piper SHA 512 hash does not match - download aborted!"); ; + DialogResult = DialogResult.Cancel; + return; + } + + if (!Directory.Exists(PiperPath)) + { + Directory.CreateDirectory(PiperPath); + } + + downloadStream.Position = 0; + if (!string.IsNullOrEmpty(ModelFileName)) + { + using (var fileStream = File.Create(ModelFileName)) + { + downloadStream.CopyTo(fileStream); + } + } + else + { + using (var zip = ZipExtractor.Open(downloadStream)) + { + 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(PiperPath, name.Replace('/', Path.DirectorySeparatorChar)); + zip.ExtractFile(entry, path); + } + else + { + var p = Path.Combine(PiperPath, entry.FilenameInZip.TrimEnd('/')); + if (!Directory.Exists(p)) + { + Directory.CreateDirectory(p); + } + } + } + } + } + + Cursor = Cursors.Default; + labelPleaseWait.Text = string.Empty; + + if (AutoClose) + { + DialogResult = DialogResult.OK; + return; + } + + buttonCancel.Text = LanguageSettings.Current.General.Ok; + labelPleaseWait.Text = string.Format(LanguageSettings.Current.SettingsFfmpeg.XDownloadOk, _title); + } + } +} diff --git a/src/ui/Forms/Tts/PiperDownload.resx b/src/ui/Forms/Tts/PiperDownload.resx new file mode 100644 index 000000000..1af7de150 --- /dev/null +++ b/src/ui/Forms/Tts/PiperDownload.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/ui/Forms/TextToSpeech.Designer.cs b/src/ui/Forms/Tts/TextToSpeech.Designer.cs similarity index 97% rename from src/ui/Forms/TextToSpeech.Designer.cs rename to src/ui/Forms/Tts/TextToSpeech.Designer.cs index c48b95df7..982f07833 100644 --- a/src/ui/Forms/TextToSpeech.Designer.cs +++ b/src/ui/Forms/Tts/TextToSpeech.Designer.cs @@ -1,4 +1,4 @@ -namespace Nikse.SubtitleEdit.Forms +namespace Nikse.SubtitleEdit.Forms.Tts { partial class TextToSpeech { @@ -36,12 +36,12 @@ this.labelEngine = new System.Windows.Forms.Label(); this.groupBoxMsSettings = new System.Windows.Forms.GroupBox(); this.labelMsVoice = new System.Windows.Forms.Label(); + this.nikseComboBoxVoice = new Nikse.SubtitleEdit.Controls.NikseComboBox(); this.checkBoxAddToVideoFile = new System.Windows.Forms.CheckBox(); this.listView1 = new System.Windows.Forms.ListView(); this.columnHeader1 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); this.columnHeader2 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); this.contextMenuStripActors = new System.Windows.Forms.ContextMenuStrip(this.components); - this.nikseComboBoxVoice = new Nikse.SubtitleEdit.Controls.NikseComboBox(); this.nikseComboBoxEngine = new Nikse.SubtitleEdit.Controls.NikseComboBox(); this.groupBoxMsSettings.SuspendLayout(); this.SuspendLayout(); @@ -125,6 +125,30 @@ this.labelMsVoice.TabIndex = 16; this.labelMsVoice.Text = "Voice"; // + // nikseComboBoxVoice + // + this.nikseComboBoxVoice.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | 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, 41); + 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 = 15; + this.nikseComboBoxVoice.UsePopupWindow = false; + // // checkBoxAddToVideoFile // this.checkBoxAddToVideoFile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); @@ -170,30 +194,6 @@ this.contextMenuStripActors.Name = "contextMenuStripActors"; this.contextMenuStripActors.Size = new System.Drawing.Size(61, 4); // - // nikseComboBoxVoice - // - this.nikseComboBoxVoice.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | 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 = 195; - this.nikseComboBoxVoice.FormattingEnabled = false; - this.nikseComboBoxVoice.Location = new System.Drawing.Point(17, 41); - 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 = 15; - this.nikseComboBoxVoice.UsePopupWindow = false; - // // nikseComboBoxEngine // this.nikseComboBoxEngine.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); @@ -206,7 +206,7 @@ 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 = 195; + this.nikseComboBoxEngine.DropDownWidth = 391; this.nikseComboBoxEngine.FormattingEnabled = false; this.nikseComboBoxEngine.Location = new System.Drawing.Point(451, 28); this.nikseComboBoxEngine.MaxLength = 32767; @@ -242,6 +242,9 @@ this.ShowInTaskbar = false; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "Text to speach"; + this.Load += new System.EventHandler(this.TextToSpeech_Load); + this.ResizeEnd += new System.EventHandler(this.TextToSpeech_ResizeEnd); + this.SizeChanged += new System.EventHandler(this.TextToSpeech_SizeChanged); this.groupBoxMsSettings.ResumeLayout(false); this.groupBoxMsSettings.PerformLayout(); this.ResumeLayout(false); diff --git a/src/ui/Forms/TextToSpeech.cs b/src/ui/Forms/Tts/TextToSpeech.cs similarity index 82% rename from src/ui/Forms/TextToSpeech.cs rename to src/ui/Forms/Tts/TextToSpeech.cs index 73ddfcbee..498381b45 100644 --- a/src/ui/Forms/TextToSpeech.cs +++ b/src/ui/Forms/Tts/TextToSpeech.cs @@ -1,16 +1,17 @@ -using Nikse.SubtitleEdit.Core.Common; -using Nikse.SubtitleEdit.Logic; -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Speech.Synthesis; using System.Windows.Forms; +using Nikse.SubtitleEdit.Core.Common; using Nikse.SubtitleEdit.Core.SubtitleFormats; using Nikse.SubtitleEdit.Core.TextToSpeech; +using Nikse.SubtitleEdit.Logic; +using MessageBox = Nikse.SubtitleEdit.Forms.SeMsgBox.MessageBox; -namespace Nikse.SubtitleEdit.Forms +namespace Nikse.SubtitleEdit.Forms.Tts { public partial class TextToSpeech : Form { @@ -19,6 +20,7 @@ namespace Nikse.SubtitleEdit.Forms private readonly VideoInfo _videoInfo; private string _waveFolder; private readonly List _actorAndVoices; + private readonly SubtitleFormat _subtitleFormat; public class ActorAndVoice { @@ -35,6 +37,7 @@ namespace Nikse.SubtitleEdit.Forms UiUtil.FixFonts(this); _subtitle = subtitle; + _subtitleFormat = subtitleFormat; _videoFileName = videoFileName; _videoInfo = videoInfo; @@ -44,60 +47,19 @@ namespace Nikse.SubtitleEdit.Forms progressBar1.Visible = false; labelProgress.Text = string.Empty; + _actorAndVoices = new List(); nikseComboBoxEngine.DropDownStyle = ComboBoxStyle.DropDownList; nikseComboBoxEngine.Items.Clear(); - nikseComboBoxEngine.Items.Add("Microsoft SpeechSynthesizer (fast/robotic)"); - nikseComboBoxEngine.Items.Add("Piper"); + nikseComboBoxEngine.Items.Add("Microsoft SpeechSynthesizer (very fast/robotic)"); + nikseComboBoxEngine.Items.Add("Piper (fast/good)"); nikseComboBoxEngine.Items.Add("Tortoise TTS (very slow/very good)"); - nikseComboBoxEngine.Items.Add("Mimic3"); + //nikseComboBoxEngine.Items.Add("Mimic3"); nikseComboBoxEngine.SelectedIndex = 0; - _actorAndVoices = new List(); listView1.Visible = false; - if (subtitleFormat.GetType() == typeof(AdvancedSubStationAlpha)) - { - var actors = _subtitle.Paragraphs - .Where(p => !string.IsNullOrEmpty(p.Actor)) - .Select(p => p.Actor) - .Distinct() - .ToList(); - if (actors.Any()) - { - foreach (var actor in actors) - { - var actorAndVoice = new ActorAndVoice - { - Actor = actor, - UseCount = _subtitle.Paragraphs.Count(p => p.Actor == actor), - }; - - _actorAndVoices.Add(actorAndVoice); - } - - FillActorListView(); - - for (var index = 0; index < nikseComboBoxVoice.Items.Count; index++) - { - var item = nikseComboBoxVoice.Items[index]; - - var tsi = new ToolStripMenuItem(); - tsi.Tag = new ActorAndVoice { Voice = item.ToString(), VoiceIndex = index }; - tsi.Text = item.ToString(); - tsi.Click += (sender, args) => - { - var a = (ActorAndVoice)(sender as ToolStripItem).Tag; - SetActor(a); - }; - contextMenuStripActors.Items.Add(tsi); - - listView1.Visible = true; - - } - } - } + nikseComboBoxEngine_SelectedIndexChanged(null, null); } - private void SetActor(ActorAndVoice actor) { foreach (int index in listView1.SelectedIndices) @@ -152,12 +114,13 @@ namespace Nikse.SubtitleEdit.Forms var fileNames = FixParagraphAudioSpeed(); - // rename result file var tempAudioFile = MergeAudioParagraphs(fileNames); + + // rename result file var resultAudioFileName = Path.Combine(Path.GetDirectoryName(tempAudioFile), Path.GetFileNameWithoutExtension(_videoFileName) + ".wav"); File.Move(tempAudioFile, resultAudioFileName); - // Cleanup(_waveFolder, resultAudioFileName); + Cleanup(_waveFolder, resultAudioFileName); if (checkBoxAddToVideoFile.Checked) { @@ -177,7 +140,7 @@ namespace Nikse.SubtitleEdit.Forms labelProgress.Text = "Add audtio to video file..."; var outputFileName = Path.Combine(_waveFolder, Path.GetFileNameWithoutExtension(audioFileName) + videoExt); - Process addAudioProcess = VideoPreviewGenerator.AddAudioTrack(_videoFileName, audioFileName, outputFileName); + var addAudioProcess = VideoPreviewGenerator.AddAudioTrack(_videoFileName, audioFileName, outputFileName); addAudioProcess.Start(); addAudioProcess.WaitForExit(); @@ -351,11 +314,40 @@ namespace Nikse.SubtitleEdit.Forms private bool GenerateParagraphAudioPiperTts() { - var piperExe = "C:\\data\\piper\\piper_windows_amd64\\piper\\piper.exe"; + var ttsPath = Path.Combine(Configuration.DataDirectory, "TextToSpeech"); + if (!Directory.Exists(ttsPath)) + { + Directory.CreateDirectory(ttsPath); + } + + var piperPath = Path.Combine(ttsPath, "Piper"); + if (!Directory.Exists(piperPath)) + { + Directory.CreateDirectory(piperPath); + } + + var piperExe = Path.Combine(piperPath, "piper.exe"); + + if (!File.Exists(piperExe)) + { + if (MessageBox.Show(string.Format(LanguageSettings.Current.Settings.DownloadX, "Piper Text To Speech"), "Subtitle Edit", MessageBoxButtons.YesNoCancel) != DialogResult.Yes) + { + return false; + } + + using (var form = new PiperDownload("Piper TextToSpeech") { AutoClose = true, PiperPath = piperPath }) + { + if (form.ShowDialog(this) != DialogResult.OK) + { + return false; + } + } + } progressBar1.Value = 0; progressBar1.Maximum = _subtitle.Paragraphs.Count; progressBar1.Visible = true; + var voices = PiperModels.GetVoices(); for (var index = 0; index < _subtitle.Paragraphs.Count; index++) { @@ -364,27 +356,61 @@ namespace Nikse.SubtitleEdit.Forms var p = _subtitle.Paragraphs[index]; var outputFileName = Path.Combine(_waveFolder, index + ".wav"); + var voice = voices.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]; + } + } + + var modelFileName = Path.Combine(piperPath, voice.ModelShort); + if (!File.Exists(modelFileName)) + { + using (var form = new PiperDownload("Piper TextToSpeech Voice") { AutoClose = true, ModelUrl = voice.Model, ModelFileName = modelFileName, PiperPath = piperPath }) + { + if (form.ShowDialog(this) != DialogResult.OK) + { + return false; + } + } + } + + var configFileName = Path.Combine(piperPath, voice.ConfigShort); + if (!File.Exists(configFileName)) + { + using (var form = new PiperDownload("Piper TextToSpeech Voice") { AutoClose = true, ModelUrl = voice.Config, ModelFileName = configFileName, PiperPath = piperPath }) + { + if (form.ShowDialog(this) != DialogResult.OK) + { + return false; + } + } + } + var processPiper = new Process { StartInfo = { - WorkingDirectory = Path.GetDirectoryName(piperExe), + WorkingDirectory = piperPath, FileName = piperExe, - Arguments = $"-m en_US-amy-medium.onnx -c en_US_amy_medium.onnx.json -f out.wav", + Arguments = $"-m \"{voice.ModelShort}\" -c \"{voice.ConfigShort}\" -f out.wav", UseShellExecute = false, CreateNoWindow = true, RedirectStandardInput = true, } }; - processPiper.Start(); + processPiper.Start(); var streamWriter = processPiper.StandardInput; streamWriter.Write(p.Text); streamWriter.Flush(); streamWriter.Close(); processPiper.WaitForExit(); - var inputFile = Path.Combine(Path.GetDirectoryName(piperExe), "out.wav"); + var inputFile = Path.Combine(piperPath, "out.wav"); File.Move(inputFile, outputFileName); progressBar1.Refresh(); @@ -398,7 +424,6 @@ namespace Nikse.SubtitleEdit.Forms return true; } - private bool GenerateParagraphAudioTortoiseTts() { var pythonFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), @@ -506,7 +531,7 @@ namespace Nikse.SubtitleEdit.Forms { foreach (var voice in PiperModels.GetVoices()) { - nikseComboBoxVoice.Items.Add(voice.Voice + " - " + voice.Language); + nikseComboBoxVoice.Items.Add(voice.ToString()); } } @@ -539,6 +564,65 @@ namespace Nikse.SubtitleEdit.Forms { nikseComboBoxVoice.SelectedIndex = 0; } + + _actorAndVoices.Clear(); + + if (_subtitleFormat.GetType() == typeof(AdvancedSubStationAlpha)) + { + var actors = _subtitle.Paragraphs + .Where(p => !string.IsNullOrEmpty(p.Actor)) + .Select(p => p.Actor) + .Distinct() + .ToList(); + if (actors.Any()) + { + foreach (var actor in actors) + { + var actorAndVoice = new ActorAndVoice + { + Actor = actor, + UseCount = _subtitle.Paragraphs.Count(p => p.Actor == actor), + }; + + _actorAndVoices.Add(actorAndVoice); + } + + FillActorListView(); + + contextMenuStripActors.Items.Clear(); + for (var index = 0; index < nikseComboBoxVoice.Items.Count; index++) + { + var item = nikseComboBoxVoice.Items[index]; + + var tsi = new ToolStripMenuItem(); + tsi.Tag = new ActorAndVoice { Voice = item.ToString(), VoiceIndex = index }; + tsi.Text = item.ToString(); + tsi.Click += (x, args) => + { + var a = (ActorAndVoice)(x as ToolStripItem).Tag; + SetActor(a); + }; + contextMenuStripActors.Items.Add(tsi); + + listView1.Visible = true; + } + } + } + } + + private void TextToSpeech_ResizeEnd(object sender, EventArgs e) + { + listView1.AutoSizeLastColumn(); + } + + private void TextToSpeech_Load(object sender, EventArgs e) + { + listView1.AutoSizeLastColumn(); + } + + private void TextToSpeech_SizeChanged(object sender, EventArgs e) + { + listView1.AutoSizeLastColumn(); } } } \ No newline at end of file diff --git a/src/ui/Forms/TextToSpeech.resx b/src/ui/Forms/Tts/TextToSpeech.resx similarity index 100% rename from src/ui/Forms/TextToSpeech.resx rename to src/ui/Forms/Tts/TextToSpeech.resx diff --git a/src/ui/SubtitleEdit.csproj b/src/ui/SubtitleEdit.csproj index b0d7bb4b0..4c74837c0 100644 --- a/src/ui/SubtitleEdit.csproj +++ b/src/ui/SubtitleEdit.csproj @@ -680,10 +680,16 @@ SeJobExport.cs - + Form - + + PiperDownload.cs + + + Form + + TextToSpeech.cs @@ -1951,7 +1957,10 @@ AdjustTimingViaShotChanges.cs - + + PiperDownload.cs + + TextToSpeech.cs @@ -2725,6 +2734,7 @@ false +