Spectrogram loading cleanups.

This commit is contained in:
J.D. Purcell 2015-10-03 17:41:46 -04:00
parent ed7c351b19
commit c8ed603f9b
5 changed files with 167 additions and 124 deletions

View File

@ -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<Bitmap> 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<Bitmap> 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<Bitmap>();
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
/// </summary>
/// <param name="delayInMilliseconds">Delay in milliseconds (normally zero)</param>
/// <param name="peakFileName">Path of the output file</param>
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
/// <summary>
/// Loads previously generated peaks from disk.
/// </summary>
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<Bitmap> 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<Bitmap>();
var images = new List<Bitmap>();
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("<SpectrogramInfo><SampleDuration/><NFFT/><ImageWidth/><SecondsPerImage/></SpectrogramInfo>");
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

View File

@ -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<Bitmap> _spectrogramBitmaps = new List<Bitmap>();
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<Bitmap>();
}
public void ClearSelection()
@ -1711,95 +1693,58 @@ namespace Nikse.SubtitleEdit.Controls
/////////////////////////////////////////////////
public void InitializeSpectrogram(string spectrogramDirectory)
private void InitializeSpectrogram(SpectrogramData spectrogram)
{
_spectrogramBitmaps = new List<Bitmap>();
_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<Bitmap> 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++;
}

View File

@ -18,7 +18,8 @@ namespace Nikse.SubtitleEdit.Forms
private string _peakWaveFileName;
private string _wavFileName;
private string _spectrogramDirectory;
public List<Bitmap> 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);
}
}

View File

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

View File

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