diff --git a/libse/WaveToVisualizer.cs b/libse/WaveToVisualizer.cs index f6781c80e..a634e1047 100644 --- a/libse/WaveToVisualizer.cs +++ b/libse/WaveToVisualizer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Drawing; using System.Globalization; using System.IO; +using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml; @@ -229,6 +230,105 @@ namespace Nikse.SubtitleEdit.Core HighestPeak = abs; } } + + public static WavePeakData FromDisk(string peakFileName) + { + using (var peakGenerator = new WavePeakGenerator(peakFileName)) + { + return peakGenerator.LoadPeaks(); + } + } + } + + public class SpectrogramData : IDisposable + { + private string _loadFromDirectory; + + public SpectrogramData(int fftSize, int imageWidth, double sampleDuration, IList images) + { + FftSize = fftSize; + ImageWidth = imageWidth; + SampleDuration = sampleDuration; + Images = images; + } + + private SpectrogramData(string loadFromDirectory) + { + _loadFromDirectory = loadFromDirectory; + Images = new Bitmap[0]; + } + + public int FftSize { get; private set; } + + public int ImageWidth { get; private set; } + + public double SampleDuration { get; private set; } + + public IList Images { get; private set; } + + public bool IsLoaded + { + get { return _loadFromDirectory == null; } + } + + public void Load() + { + if (_loadFromDirectory == null) + return; + + string directory = _loadFromDirectory; + _loadFromDirectory = null; + + try + { + string xmlInfoFileName = Path.Combine(directory, "Info.xml"); + if (!File.Exists(xmlInfoFileName)) + return; + var doc = new XmlDocument(); + var culture = CultureInfo.InvariantCulture; + doc.Load(xmlInfoFileName); + FftSize = Convert.ToInt32(doc.DocumentElement.SelectSingleNode("NFFT").InnerText, culture); + ImageWidth = Convert.ToInt32(doc.DocumentElement.SelectSingleNode("ImageWidth").InnerText, culture); + SampleDuration = Convert.ToDouble(doc.DocumentElement.SelectSingleNode("SampleDuration").InnerText, culture); + + var images = new List(); + var fileNames = Enumerable.Range(0, int.MaxValue) + .Select(n => Path.Combine(directory, n + ".gif")) + .TakeWhile(p => File.Exists(p)); + foreach (string fileName in fileNames) + { + // important that this does not lock file (do NOT use Image.FromFile(fileName) or alike!!!) + using (var ms = new MemoryStream(File.ReadAllBytes(fileName))) + { + images.Add((Bitmap)Image.FromStream(ms)); + } + } + Images = images; + } + catch + { + } + } + + public void Dispose() + { + foreach (var image in Images) + { + try + { + image.Dispose(); + } + catch + { + } + } + Images = new Bitmap[0]; + } + + public static SpectrogramData FromDisk(string spectrogramDirectory) + { + return new SpectrogramData(spectrogramDirectory); + } } public class WavePeakGenerator : IDisposable @@ -275,7 +375,7 @@ namespace Nikse.SubtitleEdit.Core /// /// Delay in milliseconds (normally zero) /// Path of the output file - public void GeneratePeaks(int delayInMilliseconds, string peakFileName) + public WavePeakData GeneratePeaks(int delayInMilliseconds, string peakFileName) { int peaksPerSecond = Math.Min(Configuration.Settings.VideoControls.WaveformMinimumSampleRate, _header.SampleRate); @@ -356,6 +456,8 @@ namespace Nikse.SubtitleEdit.Core stream.Write(buffer, 0, 4); } } + + return new WavePeakData(peaksPerSecond, peaks); } private static WavePeak CalculatePeak(float[] chunk, int count) @@ -379,7 +481,7 @@ namespace Nikse.SubtitleEdit.Core /// /// Loads previously generated peaks from disk. /// - public WavePeakData LoadPeaks() + internal WavePeakData LoadPeaks() { if (_header.BitsPerSample != 16) throw new Exception("Peaks file must be 16 bits per sample."); @@ -543,7 +645,7 @@ namespace Nikse.SubtitleEdit.Core //////////////////////////////////////// SPECTRUM /////////////////////////////////////////////////////////// - public List GenerateSpectrogram(string spectrogramDirectory, int delayInMilliseconds) + public SpectrogramData GenerateSpectrogram(int delayInMilliseconds, string spectrogramDirectory) { const int fftSize = 256; // image height = fft size / 2 const int imageWidth = 1024; @@ -553,7 +655,7 @@ namespace Nikse.SubtitleEdit.Core // ignore negative delays for now (pretty sure it can't happen in mkv and some places pass in -1 by mistake) delaySampleCount = Math.Max(delaySampleCount, 0); - var bitmaps = new List(); + var images = new List(); var drawer = new SpectrogramDrawer(fftSize); var readSampleDataValue = GetSampleDataReader(); Task saveImageTask = null; @@ -630,7 +732,7 @@ namespace Nikse.SubtitleEdit.Core // generate spectrogram for this chunk Bitmap bmp = drawer.Draw(chunkSamples); - bitmaps.Add(bmp); + images.Add(bmp); // wait for previous image to finish saving if (saveImageTask != null) @@ -650,14 +752,15 @@ namespace Nikse.SubtitleEdit.Core var doc = new XmlDocument(); var culture = CultureInfo.InvariantCulture; + double sampleDuration = (double)fftSize / _header.SampleRate; doc.LoadXml(""); - doc.DocumentElement.SelectSingleNode("SampleDuration").InnerText = ((double)fftSize / _header.SampleRate).ToString(culture); + doc.DocumentElement.SelectSingleNode("SampleDuration").InnerText = sampleDuration.ToString(culture); doc.DocumentElement.SelectSingleNode("NFFT").InnerText = fftSize.ToString(culture); doc.DocumentElement.SelectSingleNode("ImageWidth").InnerText = imageWidth.ToString(culture); doc.DocumentElement.SelectSingleNode("SecondsPerImage").InnerText = ((double)chunkSampleCount / _header.SampleRate).ToString(culture); // currently unused; for backwards compatibility doc.Save(Path.Combine(spectrogramDirectory, "Info.xml")); - return bitmaps; + return new SpectrogramData(fftSize, imageWidth, sampleDuration, images); } private class SpectrogramDrawer diff --git a/src/Controls/AudioVisualizer.cs b/src/Controls/AudioVisualizer.cs index 650192845..06cdb332f 100644 --- a/src/Controls/AudioVisualizer.cs +++ b/src/Controls/AudioVisualizer.cs @@ -4,9 +4,8 @@ using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Globalization; -using System.IO; +using System.Threading.Tasks; using System.Windows.Forms; -using System.Xml; namespace Nikse.SubtitleEdit.Controls { @@ -83,12 +82,8 @@ namespace Nikse.SubtitleEdit.Controls private bool _noClear; private double _gapAtStart = -1; - private List _spectrogramBitmaps = new List(); - private string _spectrogramDirectory; + private SpectrogramData _spectrogram; private const int SpectrogramDisplayHeight = 128; - private double _spectrogramSampleDuration; - private int _spectrogramImageWidth; - private int _spectrogramFftSize; public delegate void ParagraphEventHandler(object sender, ParagraphEventArgs e); public event ParagraphEventHandler OnNewSelectionRightClicked; @@ -107,7 +102,6 @@ namespace Nikse.SubtitleEdit.Controls private double _wholeParagraphMinMilliseconds; private double _wholeParagraphMaxMilliseconds = double.MaxValue; - private System.ComponentModel.BackgroundWorker _spectrogramBackgroundWorker; public Keys InsertAtVideoPositionShortcut = Keys.None; public bool MouseWheelScrollUpIsForward = true; @@ -186,7 +180,7 @@ namespace Nikse.SubtitleEdit.Controls { get { - return _spectrogramBitmaps != null && _spectrogramBitmaps.Count > 0; + return _spectrogram != null && _spectrogram.Images.Count > 0; } } @@ -208,7 +202,6 @@ namespace Nikse.SubtitleEdit.Controls } public bool AllowOverlap { get; set; } - private bool _tempShowSpectrogram; private bool _showWaveform; public bool ShowWaveform @@ -304,23 +297,12 @@ namespace Nikse.SubtitleEdit.Controls } } - public void ResetSpectrogram() + public SpectrogramData Spectrogram { - if (_spectrogramBitmaps != null) + set { - for (int i = 0; i < _spectrogramBitmaps.Count; i++) - { - try - { - Bitmap bmp = _spectrogramBitmaps[i]; - bmp.Dispose(); - } - catch - { - } - } + InitializeSpectrogram(value); } - _spectrogramBitmaps = new List(); } public void ClearSelection() @@ -1711,95 +1693,58 @@ namespace Nikse.SubtitleEdit.Controls ///////////////////////////////////////////////// - public void InitializeSpectrogram(string spectrogramDirectory) + private void InitializeSpectrogram(SpectrogramData spectrogram) { - _spectrogramBitmaps = new List(); - _tempShowSpectrogram = ShowSpectrogram; - ShowSpectrogram = false; - if (Directory.Exists(spectrogramDirectory)) + if (_spectrogram != null) { - _spectrogramDirectory = spectrogramDirectory; - _spectrogramBackgroundWorker = new System.ComponentModel.BackgroundWorker(); - _spectrogramBackgroundWorker.DoWork += LoadSpectrogramBitmapsAsync; - _spectrogramBackgroundWorker.RunWorkerCompleted += LoadSpectrogramBitmapsCompleted; - _spectrogramBackgroundWorker.RunWorkerAsync(); + _spectrogram.Dispose(); + _spectrogram = null; + Invalidate(); } - } - private void LoadSpectrogramBitmapsCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e) - { - LoadSpectrogramInfo(_spectrogramDirectory); - ShowSpectrogram = _tempShowSpectrogram; - if (_spectrogramBackgroundWorker != null) - _spectrogramBackgroundWorker.Dispose(); - } + if (spectrogram == null) + return; - private void LoadSpectrogramBitmapsAsync(object sender, System.ComponentModel.DoWorkEventArgs e) - { - try + if (spectrogram.IsLoaded) { - for (var count = 0; ; count++) + InitializeSpectrogramInternal(spectrogram); + } + else + { + Task.Factory.StartNew(() => { - var fileName = Path.Combine(_spectrogramDirectory, count + ".gif"); - - // important that this does not lock file (do NOT use Image.FromFile(fileName) or alike!!!) - using (var ms = new MemoryStream(File.ReadAllBytes(fileName))) + spectrogram.Load(); + BeginInvoke((Action)(() => { - _spectrogramBitmaps.Add((Bitmap)Image.FromStream(ms)); - } - } - } - catch (FileNotFoundException) - { - // no more files + InitializeSpectrogramInternal(spectrogram); + })); + }); } } - public void InitializeSpectrogram(List spectrogramBitmaps, string spectrogramDirectory) + private void InitializeSpectrogramInternal(SpectrogramData spectrogram) { - _spectrogramBitmaps = spectrogramBitmaps; - LoadSpectrogramInfo(spectrogramDirectory); - } + if (_spectrogram != null) + return; - private void LoadSpectrogramInfo(string spectrogramDirectory) - { - try - { - var doc = new XmlDocument(); - string xmlInfoFileName = Path.Combine(spectrogramDirectory, "Info.xml"); - if (File.Exists(xmlInfoFileName)) - { - doc.Load(xmlInfoFileName); - _spectrogramSampleDuration = Convert.ToDouble(doc.DocumentElement.SelectSingleNode("SampleDuration").InnerText, CultureInfo.InvariantCulture); - _spectrogramFftSize = Convert.ToInt32(doc.DocumentElement.SelectSingleNode("NFFT").InnerText, CultureInfo.InvariantCulture); - _spectrogramImageWidth = Convert.ToInt32(doc.DocumentElement.SelectSingleNode("ImageWidth").InnerText, CultureInfo.InvariantCulture); - ShowSpectrogram = true; - } - else - { - ShowSpectrogram = false; - } - } - catch - { - ShowSpectrogram = false; - } + _spectrogram = spectrogram; + Invalidate(); } private void DrawSpectrogram(Graphics graphics) { - int width = (int)Math.Round((EndPositionSeconds - StartPositionSeconds) / _spectrogramSampleDuration); - using (var bmpCombined = new Bitmap(width, _spectrogramFftSize / 2)) + int width = (int)Math.Round((EndPositionSeconds - StartPositionSeconds) / _spectrogram.SampleDuration); + using (var bmpCombined = new Bitmap(width, _spectrogram.FftSize / 2)) using (var gfxCombined = Graphics.FromImage(bmpCombined)) { - int left = (int)Math.Round(StartPositionSeconds / _spectrogramSampleDuration); + int left = (int)Math.Round(StartPositionSeconds / _spectrogram.SampleDuration); int offset = 0; - int imageIndex = left / _spectrogramImageWidth; - while (offset < width && imageIndex < _spectrogramBitmaps.Count) + int imageIndex = left / _spectrogram.ImageWidth; + while (offset < width && imageIndex < _spectrogram.Images.Count) { - int x = (left + offset) % _spectrogramImageWidth; - int w = Math.Min(_spectrogramImageWidth - x, width - offset); - gfxCombined.DrawImage(_spectrogramBitmaps[imageIndex], offset, 0, new Rectangle(x, 0, w, bmpCombined.Height), GraphicsUnit.Pixel); + int x = (left + offset) % _spectrogram.ImageWidth; + int w = Math.Min(_spectrogram.ImageWidth - x, width - offset); + gfxCombined.DrawImage(_spectrogram.Images[imageIndex], offset, 0, new Rectangle(x, 0, w, bmpCombined.Height), GraphicsUnit.Pixel); offset += w; imageIndex++; } diff --git a/src/Forms/AddWaveForm.cs b/src/Forms/AddWaveForm.cs index 854524dc5..426408561 100644 --- a/src/Forms/AddWaveForm.cs +++ b/src/Forms/AddWaveForm.cs @@ -18,7 +18,8 @@ namespace Nikse.SubtitleEdit.Forms private string _peakWaveFileName; private string _wavFileName; private string _spectrogramDirectory; - public List SpectrogramBitmaps { get; private set; } + public WavePeakData Peaks { get; private set; } + public SpectrogramData Spectrogram { get; private set; } private string _encodeParamters; private const string RetryEncodeParameters = "acodec=s16l"; private int _audioTrackNumber = -1; @@ -236,13 +237,13 @@ namespace Nikse.SubtitleEdit.Forms using (var waveFile = new WavePeakGenerator(targetFile)) { - waveFile.GeneratePeaks(delayInMilliseconds, _peakWaveFileName); + Peaks = waveFile.GeneratePeaks(delayInMilliseconds, _peakWaveFileName); if (Configuration.Settings.VideoControls.GenerateSpectrogram) { labelProgress.Text = Configuration.Settings.Language.AddWaveform.GeneratingSpectrogram; Refresh(); - SpectrogramBitmaps = waveFile.GenerateSpectrogram(_spectrogramDirectory, delayInMilliseconds); + Spectrogram = waveFile.GenerateSpectrogram(delayInMilliseconds, _spectrogramDirectory); } } diff --git a/src/Forms/AddWaveformBatch.cs b/src/Forms/AddWaveformBatch.cs index 13612bace..8bef37716 100644 --- a/src/Forms/AddWaveformBatch.cs +++ b/src/Forms/AddWaveformBatch.cs @@ -333,7 +333,7 @@ namespace Nikse.SubtitleEdit.Forms if (Configuration.Settings.VideoControls.GenerateSpectrogram) { - waveFile.GenerateSpectrogram(Main.GetSpectrogramFolder(videoFileName), delayInMilliseconds); + waveFile.GenerateSpectrogram(delayInMilliseconds, Main.GetSpectrogramFolder(videoFileName)); } } } diff --git a/src/Forms/Main.cs b/src/Forms/Main.cs index 9c06b6186..bf30ff122 100644 --- a/src/Forms/Main.cs +++ b/src/Forms/Main.cs @@ -2552,8 +2552,7 @@ namespace Nikse.SubtitleEdit.Forms _videoAudioTrackNumber = -1; labelVideoInfo.Text = _languageGeneral.NoVideoLoaded; audioVisualizer.WavePeaks = null; - audioVisualizer.ResetSpectrogram(); - audioVisualizer.Invalidate(); + audioVisualizer.Spectrogram = null; } if (Configuration.Settings.General.ShowVideoPlayer || Configuration.Settings.General.ShowAudioVisualizer) @@ -2643,8 +2642,7 @@ namespace Nikse.SubtitleEdit.Forms _videoAudioTrackNumber = -1; labelVideoInfo.Text = _languageGeneral.NoVideoLoaded; audioVisualizer.WavePeaks = null; - audioVisualizer.ResetSpectrogram(); - audioVisualizer.Invalidate(); + audioVisualizer.Spectrogram = null; Configuration.Settings.RecentFiles.Add(fileName, FirstVisibleIndex, FirstSelectedIndex, _videoFileName, _subtitleAlternateFileName); Configuration.Settings.Save(); @@ -3301,8 +3299,7 @@ namespace Nikse.SubtitleEdit.Forms _videoAudioTrackNumber = -1; labelVideoInfo.Text = _languageGeneral.NoVideoLoaded; audioVisualizer.WavePeaks = null; - audioVisualizer.ResetSpectrogram(); - audioVisualizer.Invalidate(); + audioVisualizer.Spectrogram = null; _sourceViewChange = false; @@ -12678,10 +12675,8 @@ namespace Nikse.SubtitleEdit.Forms string spectrogramFolder = GetSpectrogramFolder(fileName); if (File.Exists(peakWaveFileName)) { - using (var peakGenerator = new WavePeakGenerator(peakWaveFileName)) - audioVisualizer.WavePeaks = peakGenerator.LoadPeaks(); - audioVisualizer.ResetSpectrogram(); - audioVisualizer.InitializeSpectrogram(spectrogramFolder); + audioVisualizer.WavePeaks = WavePeakData.FromDisk(peakWaveFileName); + audioVisualizer.Spectrogram = SpectrogramData.FromDisk(spectrogramFolder); toolStripComboBoxWaveform_SelectedIndexChanged(null, null); SetWaveformPosition(0, 0, 0); timerWaveform.Start(); @@ -13424,8 +13419,7 @@ namespace Nikse.SubtitleEdit.Forms if (audioVisualizer.WavePeaks != null) { audioVisualizer.WavePeaks = null; - audioVisualizer.ResetSpectrogram(); - audioVisualizer.Invalidate(); + audioVisualizer.Spectrogram = null; } openFileDialog1.InitialDirectory = Path.GetDirectoryName(openFileDialog1.FileName); if (!panelVideoPlayer.Visible) @@ -14825,10 +14819,8 @@ namespace Nikse.SubtitleEdit.Forms } if (addWaveform.ShowDialog() == DialogResult.OK) { - using (var peakGenerator = new WavePeakGenerator(peakWaveFileName)) - audioVisualizer.WavePeaks = peakGenerator.LoadPeaks(); - if (addWaveform.SpectrogramBitmaps != null) - audioVisualizer.InitializeSpectrogram(addWaveform.SpectrogramBitmaps, spectrogramFolder); + audioVisualizer.WavePeaks = addWaveform.Peaks; + audioVisualizer.Spectrogram = addWaveform.Spectrogram; timerWaveform.Start(); } } @@ -15199,17 +15191,20 @@ namespace Nikse.SubtitleEdit.Forms } if (_videoFileName == null) + { OpenVideo(fileName); + return; + } using (var addWaveform = new AddWaveform()) { - string spectrogramFolder = GetSpectrogramFolder(_videoFileName); string peakWaveFileName = GetPeakWaveFileName(_videoFileName); + string spectrogramFolder = GetSpectrogramFolder(_videoFileName); addWaveform.InitializeViaWaveFile(fileName, peakWaveFileName, spectrogramFolder); if (addWaveform.ShowDialog() == DialogResult.OK) { - using (var peakGenerator = new WavePeakGenerator(peakWaveFileName)) - audioVisualizer.WavePeaks = peakGenerator.LoadPeaks(); + audioVisualizer.WavePeaks = addWaveform.Peaks; + audioVisualizer.Spectrogram = addWaveform.Spectrogram; timerWaveform.Start(); } } @@ -16494,8 +16489,7 @@ namespace Nikse.SubtitleEdit.Forms _videoAudioTrackNumber = -1; labelVideoInfo.Text = _languageGeneral.NoVideoLoaded; audioVisualizer.WavePeaks = null; - audioVisualizer.ResetSpectrogram(); - audioVisualizer.Invalidate(); + audioVisualizer.Spectrogram = null; } private void ToolStripMenuItemVideoDropDownOpening(object sender, EventArgs e)