Work on installer

This commit is contained in:
niksedk 2022-01-01 16:22:50 +01:00
parent 18ddd50e34
commit 9a1a0b0d0f
6 changed files with 590 additions and 0 deletions

View File

@ -185,6 +185,7 @@ COPY /Y /V "libse.dll" "temp_zip\"
COPY /Y /V "zlib.net.dll" "temp_zip\"
COPY /Y /V "NHunspell.dll" "temp_zip\"
COPY /Y /V "UtfUnknown.dll" "temp_zip\"
COPY /Y /V "Vosk.dll" "temp_zip\"
COPY /Y /V "..\..\DLLs\Interop.QuartzTypeLib.dll" "temp_zip\"
COPY /Y /V "System.Net.Http.Extensions.dll" "temp_zip\"
COPY /Y /V "Newtonsoft.Json.dll" "temp_zip\"

View File

@ -173,6 +173,7 @@ COPY /Y /V "libse.dll" "temp_zip\"
COPY /Y /V "zlib.net.dll" "temp_zip\"
COPY /Y /V "NHunspell.dll" "temp_zip\"
COPY /Y /V "UtfUnknown.dll" "temp_zip\"
COPY /Y /V "Vosk.dll" "temp_zip\"
COPY /Y /V "..\..\DLLs\Interop.QuartzTypeLib.dll" "temp_zip\"
COPY /Y /V "System.Net.Http.Extensions.dll" "temp_zip\"
COPY /Y /V "Newtonsoft.Json.dll" "temp_zip\"

View File

@ -304,6 +304,7 @@ Source: {#bindir}\libse.dll; DestDir: {app};
Source: {#bindir}\zlib.net.dll; DestDir: {app}; Flags: ignoreversion; Components: main
Source: {#bindir}\NHunspell.dll; DestDir: {app}; Flags: ignoreversion; Components: main
Source: {#bindir}\UtfUnknown.dll; DestDir: {app}; Flags: ignoreversion; Components: main
Source: {#bindir}\Vosk.dll; DestDir: {app}; Flags: ignoreversion; Components: main
Source: ..\src\ui\DLLs\Interop.QuartzTypeLib.dll; DestDir: {app}; Flags: ignoreversion; Components: main
Source: {#bindir}\Newtonsoft.Json.dll; DestDir: {app}; Flags: ignoreversion; Components: main
Source: {#bindir}\System.Net.Http.Extensions.dll; DestDir: {app}; Flags: ignoreversion; Components: main
@ -341,6 +342,7 @@ Type: files; Name: {app}\libse.dll; Check: IsU
Type: files; Name: {app}\zlib.net.dll; Check: IsUpgrade()
Type: files; Name: {app}\NHunspell.dll; Check: IsUpgrade()
Type: files; Name: {app}\UtfUnknown.dll; Check: IsUpgrade()
Type: files; Name: {app}\Vosk.dll; Check: IsUpgrade()
Type: files; Name: {app}\Interop.QuartzTypeLib.dll; Check: IsUpgrade()
Type: files; Name: {app}\Newtonsoft.Json.dll; Check: IsUpgrade()
Type: files; Name: {app}\System.Net.Http.Extensions.dll; Check: IsUpgrade()

208
src/ui/Forms/AudioToText.Designer.cs generated Normal file
View File

@ -0,0 +1,208 @@
namespace Nikse.SubtitleEdit.Forms
{
partial class AudioToText
{
/// <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.buttonCancel = new System.Windows.Forms.Button();
this.buttonGenerate = new System.Windows.Forms.Button();
this.progressBar1 = new System.Windows.Forms.ProgressBar();
this.labelProgress = new System.Windows.Forms.Label();
this.textBoxLog = new System.Windows.Forms.TextBox();
this.labelInfo = new System.Windows.Forms.Label();
this.groupBoxModels = new System.Windows.Forms.GroupBox();
this.labelModel = new System.Windows.Forms.Label();
this.comboBoxModels = new System.Windows.Forms.ComboBox();
this.linkLabelVoskWebSite = new System.Windows.Forms.LinkLabel();
this.linkLabelOpenModelFolder = new System.Windows.Forms.LinkLabel();
this.groupBoxModels.SuspendLayout();
this.SuspendLayout();
//
// 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(529, 176);
this.buttonCancel.Name = "buttonCancel";
this.buttonCancel.Size = new System.Drawing.Size(75, 23);
this.buttonCancel.TabIndex = 112;
this.buttonCancel.Text = "C&ancel";
this.buttonCancel.UseVisualStyleBackColor = true;
this.buttonCancel.Click += new System.EventHandler(this.buttonCancel_Click);
//
// buttonGenerate
//
this.buttonGenerate.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.buttonGenerate.ImeMode = System.Windows.Forms.ImeMode.NoControl;
this.buttonGenerate.Location = new System.Drawing.Point(398, 176);
this.buttonGenerate.Name = "buttonGenerate";
this.buttonGenerate.Size = new System.Drawing.Size(125, 23);
this.buttonGenerate.TabIndex = 111;
this.buttonGenerate.Text = "&Generate";
this.buttonGenerate.UseVisualStyleBackColor = true;
this.buttonGenerate.Click += new System.EventHandler(this.ButtonGenerate_Click);
//
// progressBar1
//
this.progressBar1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.progressBar1.Location = new System.Drawing.Point(12, 186);
this.progressBar1.Name = "progressBar1";
this.progressBar1.Size = new System.Drawing.Size(380, 13);
this.progressBar1.TabIndex = 114;
this.progressBar1.Visible = false;
//
// labelProgress
//
this.labelProgress.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.labelProgress.AutoSize = true;
this.labelProgress.Location = new System.Drawing.Point(12, 170);
this.labelProgress.Name = "labelProgress";
this.labelProgress.Size = new System.Drawing.Size(70, 13);
this.labelProgress.TabIndex = 115;
this.labelProgress.Text = "labelProgress";
//
// textBoxLog
//
this.textBoxLog.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBoxLog.Location = new System.Drawing.Point(15, 9);
this.textBoxLog.Multiline = true;
this.textBoxLog.Name = "textBoxLog";
this.textBoxLog.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.textBoxLog.Size = new System.Drawing.Size(589, 158);
this.textBoxLog.TabIndex = 116;
//
// labelInfo
//
this.labelInfo.AutoSize = true;
this.labelInfo.Location = new System.Drawing.Point(12, 9);
this.labelInfo.Name = "labelInfo";
this.labelInfo.Size = new System.Drawing.Size(288, 13);
this.labelInfo.TabIndex = 118;
this.labelInfo.Text = "Generate text from audio via Vosk/Kaldi speech recognition";
//
// groupBoxModels
//
this.groupBoxModels.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.groupBoxModels.Controls.Add(this.linkLabelOpenModelFolder);
this.groupBoxModels.Controls.Add(this.labelModel);
this.groupBoxModels.Controls.Add(this.comboBoxModels);
this.groupBoxModels.Location = new System.Drawing.Point(15, 66);
this.groupBoxModels.Name = "groupBoxModels";
this.groupBoxModels.Size = new System.Drawing.Size(589, 84);
this.groupBoxModels.TabIndex = 120;
this.groupBoxModels.TabStop = false;
this.groupBoxModels.Text = "Models";
//
// labelModel
//
this.labelModel.AutoSize = true;
this.labelModel.Location = new System.Drawing.Point(16, 29);
this.labelModel.Name = "labelModel";
this.labelModel.Size = new System.Drawing.Size(167, 13);
this.labelModel.TabIndex = 121;
this.labelModel.Text = "Choose speech recognition model";
//
// comboBoxModels
//
this.comboBoxModels.FormattingEnabled = true;
this.comboBoxModels.Location = new System.Drawing.Point(19, 45);
this.comboBoxModels.Name = "comboBoxModels";
this.comboBoxModels.Size = new System.Drawing.Size(240, 21);
this.comboBoxModels.TabIndex = 120;
//
// linkLabelVoskWebSite
//
this.linkLabelVoskWebSite.AutoSize = true;
this.linkLabelVoskWebSite.Location = new System.Drawing.Point(12, 22);
this.linkLabelVoskWebSite.Name = "linkLabelVoskWebSite";
this.linkLabelVoskWebSite.Size = new System.Drawing.Size(70, 13);
this.linkLabelVoskWebSite.TabIndex = 121;
this.linkLabelVoskWebSite.TabStop = true;
this.linkLabelVoskWebSite.Text = "Vosk website";
this.linkLabelVoskWebSite.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabelVoskWebsite_LinkClicked);
//
// linkLabelOpenModelFolder
//
this.linkLabelOpenModelFolder.AutoSize = true;
this.linkLabelOpenModelFolder.Location = new System.Drawing.Point(265, 50);
this.linkLabelOpenModelFolder.Name = "linkLabelOpenModelFolder";
this.linkLabelOpenModelFolder.Size = new System.Drawing.Size(93, 13);
this.linkLabelOpenModelFolder.TabIndex = 122;
this.linkLabelOpenModelFolder.TabStop = true;
this.linkLabelOpenModelFolder.Text = "Open model folder";
this.linkLabelOpenModelFolder.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabelOpenModelFolder_LinkClicked);
//
// AudioToText
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(616, 211);
this.Controls.Add(this.linkLabelVoskWebSite);
this.Controls.Add(this.groupBoxModels);
this.Controls.Add(this.labelInfo);
this.Controls.Add(this.textBoxLog);
this.Controls.Add(this.labelProgress);
this.Controls.Add(this.progressBar1);
this.Controls.Add(this.buttonCancel);
this.Controls.Add(this.buttonGenerate);
this.KeyPreview = true;
this.MinimumSize = new System.Drawing.Size(575, 250);
this.Name = "AudioToText";
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Video/audio to text";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.AudioToText_FormClosing);
this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.AudioToText_KeyDown);
this.groupBoxModels.ResumeLayout(false);
this.groupBoxModels.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button buttonCancel;
private System.Windows.Forms.Button buttonGenerate;
private System.Windows.Forms.ProgressBar progressBar1;
private System.Windows.Forms.Label labelProgress;
private System.Windows.Forms.TextBox textBoxLog;
private System.Windows.Forms.Label labelInfo;
private System.Windows.Forms.GroupBox groupBoxModels;
private System.Windows.Forms.LinkLabel linkLabelVoskWebSite;
private System.Windows.Forms.Label labelModel;
private System.Windows.Forms.ComboBox comboBoxModels;
private System.Windows.Forms.LinkLabel linkLabelOpenModelFolder;
}
}

315
src/ui/Forms/AudioToText.cs Normal file
View File

@ -0,0 +1,315 @@
using Nikse.SubtitleEdit.Core.AudioToText;
using Nikse.SubtitleEdit.Core.Common;
using Nikse.SubtitleEdit.Logic;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Windows.Forms;
using Vosk;
namespace Nikse.SubtitleEdit.Forms
{
public partial class AudioToText : Form
{
private readonly string _videoFileName;
private readonly string _voskFolder;
private bool _cancel;
public Subtitle TranscribedSubtitle { get; private set; }
public AudioToText(string videoFileName)
{
UiUtil.PreInitialize(this);
InitializeComponent();
UiUtil.FixFonts(this);
UiUtil.FixLargeFonts(this, buttonGenerate);
_videoFileName = videoFileName;
_voskFolder = Path.Combine(Configuration.DataDirectory, "Vosk");
if (!Directory.Exists(_voskFolder))
{
Directory.CreateDirectory(_voskFolder);
}
comboBoxModels.Items.Clear();
foreach (var directory in Directory.GetDirectories(_voskFolder))
{
var name = Path.GetFileName(directory);
if (!Directory.Exists(Path.Combine(directory, "conf")))
{
continue;
}
comboBoxModels.Items.Add(name);
if (name == Configuration.Settings.Tools.VoskModel)
{
comboBoxModels.SelectedIndex = comboBoxModels.Items.Count - 1;
}
}
if (comboBoxModels.SelectedIndex < 0 && comboBoxModels.Items.Count > 0)
{
comboBoxModels.SelectedIndex = 0;
}
textBoxLog.Visible = false;
labelProgress.Text = string.Empty;
}
private void ButtonGenerate_Click(object sender, EventArgs e)
{
if (comboBoxModels.Items.Count == 0)
{
MessageBox.Show(string.Format("Please download an audio-to-text model from the Vosk website and unpack to {0}", _voskFolder));
return;
}
progressBar1.Maximum = 100;
progressBar1.Value = 0;
progressBar1.Visible = true;
var modelFileName = Path.Combine(_voskFolder, comboBoxModels.Text);
buttonGenerate.Enabled = false;
var waveFileName = GenerateWavFile(_videoFileName, 0);
textBoxLog.AppendText("Wav file name: " + waveFileName);
textBoxLog.AppendText(Environment.NewLine);
progressBar1.Style = ProgressBarStyle.Blocks;
var transcript = TranscribeViaVosk(waveFileName, modelFileName);
if (_cancel)
{
DialogResult = DialogResult.Cancel;
return;
}
var subtitleGenerator = new Core.AudioToText.Vosk.SubtitleGenerator(transcript);
TranscribedSubtitle = subtitleGenerator.Generate("en");
DialogResult = DialogResult.OK;
}
public List<ResultText> TranscribeViaVosk(string waveFileName, string modelFileName)
{
labelProgress.Text = "Loading Vosk speach model...";
labelProgress.Refresh();
Application.DoEvents();
Vosk.Vosk.SetLogLevel(0);
var model = new Model(modelFileName);
var rec = new VoskRecognizer(model, 16000.0f);
rec.SetMaxAlternatives(0);
rec.SetWords(true);
var list = new List<ResultText>();
labelProgress.Text = "Transcribing audio to text...";
labelProgress.Refresh();
Application.DoEvents();
var totalRead = 0;
var buffer = new byte[4096];
var totalLength = new FileInfo(waveFileName).Length;
using (var source = File.OpenRead(waveFileName))
{
int bytesRead;
while ((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0)
{
totalRead += bytesRead;
progressBar1.Value = (int)(totalRead * 100.0 / totalLength);
progressBar1.Refresh();
Application.DoEvents();
if (rec.AcceptWaveform(buffer, bytesRead))
{
var res = rec.Result();
var results = ParseJsonToResult(res);
list.AddRange(results);
}
else
{
var res = rec.PartialResult();
textBoxLog.AppendText(res.RemoveChar('\r', '\n'));
}
}
if (_cancel)
{
return null;
}
}
var finalResult = rec.FinalResult();
var finalResults = ParseJsonToResult(finalResult);
list.AddRange(finalResults);
return list;
}
private static List<ResultText> ParseJsonToResult(string result)
{
var list = new List<ResultText>();
var jsonParser = new SeJsonParser();
var root = jsonParser.GetArrayElementsByName(result, "result");
foreach (var item in root)
{
var conf = jsonParser.GetFirstObject(item, "conf");
var start = jsonParser.GetFirstObject(item, "start");
var end = jsonParser.GetFirstObject(item, "end");
var word = jsonParser.GetFirstObject(item, "word");
if (!string.IsNullOrWhiteSpace(word) &&
decimal.TryParse(conf, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var confidence) &&
decimal.TryParse(start, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var startSeconds) &&
decimal.TryParse(end, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var endSeconds))
{
var rt = new ResultText { Confidence = confidence, Text = word, Start = startSeconds, End = endSeconds };
list.Add(rt);
}
}
return list;
}
private string GenerateWavFile(string videoFileName, int audioTrackNumber)
{
var outWaveFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".wav");
var process = GetFfmpegProcess(videoFileName, audioTrackNumber, outWaveFile);
process.Start();
progressBar1.Style = ProgressBarStyle.Marquee;
progressBar1.Visible = true;
double seconds = 0;
buttonCancel.Visible = true;
try
{
process.PriorityClass = ProcessPriorityClass.Normal;
}
catch
{
// ignored
}
_cancel = false;
string targetDriveLetter = null;
if (Configuration.IsRunningOnWindows)
{
var root = Path.GetPathRoot(outWaveFile);
if (root.Length > 1 && root[1] == ':')
{
targetDriveLetter = root.Remove(1);
}
}
while (!process.HasExited)
{
Application.DoEvents();
System.Threading.Thread.Sleep(100);
seconds += 0.1;
if (seconds < 60)
{
labelProgress.Text = string.Format(LanguageSettings.Current.AddWaveform.ExtractingSeconds, seconds);
}
else
{
labelProgress.Text = string.Format(LanguageSettings.Current.AddWaveform.ExtractingMinutes, (int)(seconds / 60), (int)(seconds % 60));
}
Refresh();
if (_cancel)
{
process.Kill();
progressBar1.Visible = false;
buttonCancel.Visible = false;
DialogResult = DialogResult.Cancel;
return null;
}
if (targetDriveLetter != null && seconds > 1 && Convert.ToInt32(seconds) % 10 == 0)
{
try
{
var drive = new DriveInfo(targetDriveLetter);
if (drive.IsReady)
{
if (drive.AvailableFreeSpace < 50 * 1000000) // 50 mb
{
labelInfo.ForeColor = Color.Red;
labelInfo.Text = LanguageSettings.Current.AddWaveform.LowDiskSpace;
}
}
}
catch
{
// ignored
}
}
}
return outWaveFile;
}
private static Process GetFfmpegProcess(string videoFileName, int audioTrackNumber, string outWaveFile)
{
if (!File.Exists(Configuration.Settings.General.FFmpegLocation) && Configuration.IsRunningOnWindows)
{
return null;
}
string audioParameter = string.Empty;
if (audioTrackNumber > 0)
{
audioParameter = $"-map 0:a:{audioTrackNumber}";
}
const string fFmpegWaveTranscodeSettings = "-i \"{0}\" -vn -ar 16000 -ac 1 -ab 128 -vol 448 -f wav {2} \"{1}\"";
//-i indicates the input
//-vn means no video ouput
//-ar 44100 indicates the sampling frequency.
//-ab indicates the bit rate (in this example 160kb/s)
//-vol 448 will boot volume... 256 is normal
//-ac 2 means 2 channels
// "-map 0:a:0" is the first audio stream, "-map 0:a:1" is the second audio stream
var exeFilePath = Configuration.Settings.General.FFmpegLocation;
if (!Configuration.IsRunningOnWindows)
{
exeFilePath = "ffmpeg";
}
var parameters = string.Format(fFmpegWaveTranscodeSettings, videoFileName, outWaveFile, audioParameter);
return new Process { StartInfo = new ProcessStartInfo(exeFilePath, parameters) { WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true } };
}
private void buttonCancel_Click(object sender, EventArgs e)
{
DialogResult = DialogResult.Cancel;
}
private void linkLabelVoskWebsite_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
UiUtil.OpenUrl("https://alphacephei.com/vosk/models");
}
private void AudioToText_FormClosing(object sender, FormClosingEventArgs e)
{
Configuration.Settings.Tools.VoskModel = comboBoxModels.Text;
}
private void AudioToText_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.F2)
{
if (textBoxLog.Visible)
{
textBoxLog.Visible = true;
textBoxLog.BringToFront();
}
else
{
textBoxLog.Visible = false;
}
e.SuppressKeyPress = true;
}
}
private void linkLabelOpenModelFolder_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
UiUtil.OpenFolder(_voskFolder);
}
}
}

View File

@ -32867,5 +32867,68 @@ namespace Nikse.SubtitleEdit.Forms
}
}
}
private void videoaudioToTextToolStripMenuItem_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(VideoFileName))
{
MessageBox.Show(LanguageSettings.Current.General.NoVideoLoaded);
return;
}
if (!ContinueNewOrExit())
{
return;
}
var ffmpegFullPath = Path.Combine(Configuration.DataDirectory, "ffmpeg", "ffmpeg.exe");
if (Configuration.IsRunningOnWindows && string.IsNullOrWhiteSpace(Configuration.Settings.General.FFmpegLocation) && File.Exists(ffmpegFullPath))
{
Configuration.Settings.General.FFmpegLocation = ffmpegFullPath;
}
if (Configuration.IsRunningOnWindows && (string.IsNullOrWhiteSpace(Configuration.Settings.General.FFmpegLocation) || !File.Exists(Configuration.Settings.General.FFmpegLocation)))
{
if (MessageBox.Show(LanguageSettings.Current.Settings.DownloadFFmpeg, "Subtitle Edit", MessageBoxButtons.YesNoCancel) != DialogResult.Yes)
{
return;
}
using (var form = new DownloadFfmpeg())
{
if (form.ShowDialog(this) == DialogResult.OK && !string.IsNullOrEmpty(form.FFmpegPath))
{
Configuration.Settings.General.FFmpegLocation = form.FFmpegPath;
Configuration.Settings.General.UseFFmpegForWaveExtraction = true;
}
else
{
return;
}
}
}
using (var form = new AudioToText(VideoFileName))
{
var result = form.ShowDialog(this);
if (result != DialogResult.OK)
{
return;
}
if (form.TranscribedSubtitle.Paragraphs.Count == 0)
{
MessageBox.Show("No text found!");
return;
}
_subtitle.Paragraphs.Clear();
_subtitle.Paragraphs.AddRange(form.TranscribedSubtitle.Paragraphs);
var idx = FirstSelectedIndex;
SubtitleListview1.Fill(_subtitle, _subtitleOriginal);
_subtitleListViewIndex = -1;
SubtitleListview1.SelectIndexAndEnsureVisibleFaster(idx);
}
}
}
}