This commit is contained in:
Nikolaj Olsson 2024-04-10 20:54:23 +02:00
parent c732bc3f4d
commit 502409a14e
9 changed files with 580 additions and 91 deletions

View File

@ -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<PiperModels> GetVoices()

View File

@ -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;

View File

@ -0,0 +1,85 @@
namespace Nikse.SubtitleEdit.Forms.Tts
{
sealed partial class PiperDownload
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}

View File

@ -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<float>((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);
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -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);

View File

@ -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<ActorAndVoice> _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<ActorAndVoice>();
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<ActorAndVoice>();
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();
}
}
}

View File

@ -680,10 +680,16 @@
<Compile Include="Forms\SeJobs\SeJobExport.Designer.cs">
<DependentUpon>SeJobExport.cs</DependentUpon>
</Compile>
<Compile Include="Forms\TextToSpeech.cs">
<Compile Include="Forms\Tts\PiperDownload.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Forms\TextToSpeech.Designer.cs">
<Compile Include="Forms\Tts\PiperDownload.Designer.cs">
<DependentUpon>PiperDownload.cs</DependentUpon>
</Compile>
<Compile Include="Forms\Tts\TextToSpeech.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Forms\Tts\TextToSpeech.Designer.cs">
<DependentUpon>TextToSpeech.cs</DependentUpon>
</Compile>
<Compile Include="Forms\TimedTextPropertiesImsc11.cs">
@ -1951,7 +1957,10 @@
<EmbeddedResource Include="Forms\ShotChanges\AdjustTimingViaShotChanges.resx">
<DependentUpon>AdjustTimingViaShotChanges.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Forms\TextToSpeech.resx">
<EmbeddedResource Include="Forms\Tts\PiperDownload.resx">
<DependentUpon>PiperDownload.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Forms\Tts\TextToSpeech.resx">
<DependentUpon>TextToSpeech.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Forms\TimedTextPropertiesImsc11.resx">
@ -2725,6 +2734,7 @@
<Visible>false</Visible>
</HunspellAssemblies>
</ItemGroup>
<ItemGroup />
<Target Name="BeforeCompile">
<XmlPeek XmlInputPath="$(ProjectDir)packages.config" Query="//package[@id='NHunspell']/@version">
<Output TaskParameter="Result" PropertyName="HunspellVersion" />