mirror of
https://github.com/SubtitleEdit/subtitleedit.git
synced 2024-10-27 22:42:38 +01:00
Merge pull request #1343 from jdpurcell/patch6
Waveform - show actual peak values
This commit is contained in:
commit
738589f899
@ -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;
|
||||
@ -141,12 +142,13 @@ namespace Nikse.SubtitleEdit.Core
|
||||
}
|
||||
}
|
||||
|
||||
internal static void WriteHeader(Stream toStream, int sampleRate, int numberOfChannels, int bitsPerSample, int dataSize)
|
||||
internal static void WriteHeader(Stream toStream, int sampleRate, int numberOfChannels, int bitsPerSample, int sampleCount)
|
||||
{
|
||||
const int headerSize = 44;
|
||||
int bytesPerSample = (bitsPerSample + 7) / 8;
|
||||
int blockAlign = numberOfChannels * bytesPerSample;
|
||||
int byteRate = sampleRate * blockAlign;
|
||||
int dataSize = sampleCount * bytesPerSample * numberOfChannels;
|
||||
byte[] header = new byte[headerSize];
|
||||
WriteStringToByteArray(header, 0, "RIFF");
|
||||
WriteInt32ToByteArray(header, 4, headerSize + dataSize - 8); // size of RIFF chunk's data
|
||||
@ -183,47 +185,168 @@ namespace Nikse.SubtitleEdit.Core
|
||||
}
|
||||
}
|
||||
|
||||
public struct WavePeak
|
||||
{
|
||||
public readonly short Max;
|
||||
public readonly short Min;
|
||||
|
||||
public WavePeak(short max, short min)
|
||||
{
|
||||
Max = max;
|
||||
Min = min;
|
||||
}
|
||||
|
||||
public int Abs
|
||||
{
|
||||
get { return Math.Max(Math.Abs((int)Max), Math.Abs((int)Min)); }
|
||||
}
|
||||
}
|
||||
|
||||
public class WavePeakData
|
||||
{
|
||||
public WavePeakData(int sampleRate, IList<WavePeak> peaks)
|
||||
{
|
||||
SampleRate = sampleRate;
|
||||
LengthInSeconds = (double)peaks.Count / sampleRate;
|
||||
Peaks = peaks;
|
||||
CalculateHighestPeak();
|
||||
}
|
||||
|
||||
public int SampleRate { get; private set; }
|
||||
|
||||
public double LengthInSeconds { get; private set; }
|
||||
|
||||
public IList<WavePeak> Peaks { get; private set; }
|
||||
|
||||
public int HighestPeak { get; private set; }
|
||||
|
||||
private void CalculateHighestPeak()
|
||||
{
|
||||
HighestPeak = 0;
|
||||
foreach (var peak in Peaks)
|
||||
{
|
||||
int abs = peak.Abs;
|
||||
if (abs > HighestPeak)
|
||||
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
|
||||
{
|
||||
private Stream _stream;
|
||||
private byte[] _data;
|
||||
private WaveHeader _header;
|
||||
|
||||
private delegate int ReadSampleDataValueDelegate(ref int index);
|
||||
private delegate int ReadSampleDataValue(byte[] data, ref int index);
|
||||
|
||||
public WaveHeader Header { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Lowest data value
|
||||
/// </summary>
|
||||
public int DataMinValue { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Highest data value
|
||||
/// </summary>
|
||||
public int DataMaxValue { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of peaks per second (should be divideable by SampleRate)
|
||||
/// </summary>
|
||||
public int PeaksPerSecond { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of all peak samples (channels are merged)
|
||||
/// </summary>
|
||||
public List<int> PeakSamples { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of all samples (channels are merged)
|
||||
/// </summary>
|
||||
public List<int> AllSamples { get; private set; }
|
||||
private delegate void WriteSampleDataValue(byte[] buffer, int offset, int value);
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="fileName">Wave file name</param>
|
||||
public WavePeakGenerator(string fileName)
|
||||
: this(File.OpenRead(fileName))
|
||||
{
|
||||
Initialize(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -232,214 +355,252 @@ namespace Nikse.SubtitleEdit.Core
|
||||
/// <param name="stream">Stream of a wave file</param>
|
||||
public WavePeakGenerator(Stream stream)
|
||||
{
|
||||
Initialize(stream);
|
||||
_stream = stream;
|
||||
_header = new WaveHeader(_stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the current wave file can be processed. Compressed wave files or 32-bit files are not supported
|
||||
/// Returns true if the current wave file can be processed. Compressed wave files are not supported.
|
||||
/// </summary>
|
||||
public bool IsSupported
|
||||
{
|
||||
get
|
||||
{
|
||||
return Header.AudioFormat == WaveHeader.AudioFormatPcm && Header.Format == "WAVE" && Header.BytesPerSample < 4;
|
||||
return _header.AudioFormat == WaveHeader.AudioFormatPcm && _header.Format == "WAVE";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate peaks (samples with some interval) for an uncompressed wave file
|
||||
/// Generates peaks and saves them to disk.
|
||||
/// </summary>
|
||||
/// <param name="delayInMilliseconds">Delay in milliseconds (normally zero)</param>
|
||||
public void GeneratePeakSamples(int delayInMilliseconds)
|
||||
/// <param name="peakFileName">Path of the output file</param>
|
||||
public WavePeakData GeneratePeaks(int delayInMilliseconds, string peakFileName)
|
||||
{
|
||||
if (Header.BytesPerSample == 4)
|
||||
int peaksPerSecond = Math.Min(Configuration.Settings.VideoControls.WaveformMinimumSampleRate, _header.SampleRate);
|
||||
|
||||
// ensure that peaks per second is a factor of the sample rate
|
||||
while (_header.SampleRate % peaksPerSecond != 0)
|
||||
peaksPerSecond++;
|
||||
|
||||
int delaySampleCount = (int)(_header.SampleRate * (delayInMilliseconds / TimeCode.BaseUnit));
|
||||
|
||||
// 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 peaks = new List<WavePeak>();
|
||||
var readSampleDataValue = GetSampleDataReader();
|
||||
float sampleAndChannelScale = (float)GetSampleAndChannelScale();
|
||||
long fileSampleCount = _header.LengthInSamples;
|
||||
long fileSampleOffset = -delaySampleCount;
|
||||
int chunkSampleCount = _header.SampleRate / peaksPerSecond;
|
||||
byte[] data = new byte[chunkSampleCount * _header.BlockAlign];
|
||||
float[] chunkSamples = new float[chunkSampleCount];
|
||||
|
||||
_stream.Seek(_header.DataStartPosition, SeekOrigin.Begin);
|
||||
|
||||
// for negative delays, skip samples at the beginning
|
||||
if (fileSampleOffset > 0)
|
||||
{
|
||||
// Can't handle 32-bit samples due to the way the channel averaging is done
|
||||
throw new Exception("32-bit samples are unsupported.");
|
||||
_stream.Seek(fileSampleOffset * _header.BlockAlign, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
PeaksPerSecond = Math.Min(Configuration.Settings.VideoControls.WaveformMinimumSampleRate, Header.SampleRate);
|
||||
|
||||
// Ensure that peaks per second is a factor of the sample rate
|
||||
while (Header.SampleRate % PeaksPerSecond != 0)
|
||||
PeaksPerSecond++;
|
||||
|
||||
ReadSampleDataValueDelegate readSampleDataValue = GetSampleDataReader();
|
||||
DataMinValue = int.MaxValue;
|
||||
DataMaxValue = int.MinValue;
|
||||
PeakSamples = new List<int>();
|
||||
|
||||
if (delayInMilliseconds > 0)
|
||||
while (fileSampleOffset < fileSampleCount)
|
||||
{
|
||||
for (int i = 0; i < PeaksPerSecond * delayInMilliseconds / 1000; i++)
|
||||
PeakSamples.Add(0);
|
||||
}
|
||||
|
||||
int bytesInterval = (int)Header.BytesPerSecond / PeaksPerSecond;
|
||||
_data = new byte[Header.BytesPerSecond];
|
||||
_stream.Position = Header.DataStartPosition;
|
||||
int bytesRead = _stream.Read(_data, 0, _data.Length);
|
||||
while (bytesRead > 0)
|
||||
{
|
||||
for (int i = 0; i < bytesRead; i += bytesInterval)
|
||||
// calculate how many samples to skip at the beginning (for positive delays)
|
||||
int startSkipSampleCount = 0;
|
||||
if (fileSampleOffset < 0)
|
||||
{
|
||||
int index = i;
|
||||
int value = 0;
|
||||
for (int channelNumber = 0; channelNumber < Header.NumberOfChannels; channelNumber++)
|
||||
{
|
||||
value += readSampleDataValue.Invoke(ref index);
|
||||
}
|
||||
value /= Header.NumberOfChannels;
|
||||
if (value < DataMinValue)
|
||||
DataMinValue = value;
|
||||
if (value > DataMaxValue)
|
||||
DataMaxValue = value;
|
||||
PeakSamples.Add(value);
|
||||
startSkipSampleCount = (int)Math.Min(-fileSampleOffset, chunkSampleCount);
|
||||
fileSampleOffset += startSkipSampleCount;
|
||||
}
|
||||
bytesRead = _stream.Read(_data, 0, _data.Length);
|
||||
|
||||
// calculate how many samples to read from the file
|
||||
long fileSamplesRemaining = fileSampleCount - Math.Max(fileSampleOffset, 0);
|
||||
int fileReadSampleCount = (int)Math.Min(fileSamplesRemaining, chunkSampleCount - startSkipSampleCount);
|
||||
|
||||
// read samples from the file
|
||||
if (fileReadSampleCount > 0)
|
||||
{
|
||||
int fileReadByteCount = fileReadSampleCount * _header.BlockAlign;
|
||||
_stream.Read(data, 0, fileReadByteCount);
|
||||
fileSampleOffset += fileReadSampleCount;
|
||||
|
||||
int chunkSampleOffset = 0;
|
||||
int dataByteOffset = 0;
|
||||
while (dataByteOffset < fileReadByteCount)
|
||||
{
|
||||
float value = 0F;
|
||||
for (int iChannel = 0; iChannel < _header.NumberOfChannels; iChannel++)
|
||||
{
|
||||
value += readSampleDataValue(data, ref dataByteOffset);
|
||||
}
|
||||
chunkSamples[chunkSampleOffset] = value * sampleAndChannelScale;
|
||||
chunkSampleOffset += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// calculate peaks
|
||||
peaks.Add(CalculatePeak(chunkSamples, fileReadSampleCount));
|
||||
}
|
||||
|
||||
// save results to file
|
||||
using (var stream = File.Create(peakFileName))
|
||||
{
|
||||
WaveHeader.WriteHeader(stream, peaksPerSecond, 2, 16, peaks.Count);
|
||||
byte[] buffer = new byte[4];
|
||||
foreach (var peak in peaks)
|
||||
{
|
||||
WriteValue16Bit(buffer, 0, peak.Max);
|
||||
WriteValue16Bit(buffer, 2, peak.Min);
|
||||
stream.Write(buffer, 0, 4);
|
||||
}
|
||||
}
|
||||
|
||||
return new WavePeakData(peaksPerSecond, peaks);
|
||||
}
|
||||
|
||||
public void GenerateAllSamples()
|
||||
private static WavePeak CalculatePeak(float[] chunk, int count)
|
||||
{
|
||||
if (Header.BytesPerSample == 4)
|
||||
{
|
||||
// Can't handle 32-bit samples due to the way the channel averaging is done
|
||||
throw new Exception("32-bit samples are unsupported.");
|
||||
}
|
||||
if (count == 0)
|
||||
return new WavePeak();
|
||||
|
||||
// determine how to read sample values
|
||||
ReadSampleDataValueDelegate readSampleDataValue = GetSampleDataReader();
|
||||
float max = chunk[0];
|
||||
float min = chunk[0];
|
||||
for (int i = 1; i < count; i++)
|
||||
{
|
||||
float value = chunk[i];
|
||||
if (value > max)
|
||||
max = value;
|
||||
if (value < min)
|
||||
min = value;
|
||||
}
|
||||
return new WavePeak((short)(short.MaxValue * max), (short)(short.MaxValue * min));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads previously generated peaks from disk.
|
||||
/// </summary>
|
||||
internal WavePeakData LoadPeaks()
|
||||
{
|
||||
if (_header.BitsPerSample != 16)
|
||||
throw new Exception("Peaks file must be 16 bits per sample.");
|
||||
|
||||
if (_header.NumberOfChannels != 1 && _header.NumberOfChannels != 2)
|
||||
throw new Exception("Peaks file must have 1 or 2 channels.");
|
||||
|
||||
// load data
|
||||
_data = new byte[Header.DataChunkSize];
|
||||
_stream.Position = Header.DataStartPosition;
|
||||
_stream.Read(_data, 0, _data.Length);
|
||||
byte[] data = new byte[_header.DataChunkSize];
|
||||
_stream.Position = _header.DataStartPosition;
|
||||
_stream.Read(data, 0, data.Length);
|
||||
|
||||
// read sample values
|
||||
DataMinValue = int.MaxValue;
|
||||
DataMaxValue = int.MinValue;
|
||||
AllSamples = new List<int>();
|
||||
int index = 0;
|
||||
while (index < Header.DataChunkSize)
|
||||
// read peak values
|
||||
WavePeak[] peaks = new WavePeak[_header.LengthInSamples];
|
||||
int peakIndex = 0;
|
||||
if (_header.NumberOfChannels == 2)
|
||||
{
|
||||
int value = 0;
|
||||
for (int channelNumber = 0; channelNumber < Header.NumberOfChannels; channelNumber++)
|
||||
// max value in left channel, min value in right channel
|
||||
int byteIndex = 0;
|
||||
while (byteIndex < data.Length)
|
||||
{
|
||||
value += readSampleDataValue.Invoke(ref index);
|
||||
short max = (short)ReadValue16Bit(data, ref byteIndex);
|
||||
short min = (short)ReadValue16Bit(data, ref byteIndex);
|
||||
peaks[peakIndex++] = new WavePeak(max, min);
|
||||
}
|
||||
value /= Header.NumberOfChannels;
|
||||
if (value < DataMinValue)
|
||||
DataMinValue = value;
|
||||
if (value > DataMaxValue)
|
||||
DataMaxValue = value;
|
||||
AllSamples.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void WritePeakSamples(string fileName)
|
||||
{
|
||||
using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
|
||||
else if (_header.NumberOfChannels == 1)
|
||||
{
|
||||
WritePeakSamples(fs);
|
||||
// single sample value (for backwards compatibility)
|
||||
int byteIndex = 0;
|
||||
while (byteIndex < data.Length)
|
||||
{
|
||||
short value = (short)ReadValue16Bit(data, ref byteIndex);
|
||||
if (value == short.MinValue)
|
||||
value = -short.MaxValue;
|
||||
value = Math.Abs(value);
|
||||
peaks[peakIndex++] = new WavePeak(value, (short)-value);
|
||||
}
|
||||
}
|
||||
|
||||
return new WavePeakData(_header.SampleRate, peaks);
|
||||
}
|
||||
|
||||
public void WritePeakSamples(Stream stream)
|
||||
private static int ReadValue8Bit(byte[] data, ref int index)
|
||||
{
|
||||
WaveHeader.WriteHeader(stream, PeaksPerSecond, 1, Header.BytesPerSample * 8, PeakSamples.Count * Header.BytesPerSample);
|
||||
WritePeakData(stream);
|
||||
stream.Flush();
|
||||
}
|
||||
|
||||
private void WritePeakData(Stream stream)
|
||||
{
|
||||
var writeSample = GetSampleDataWriter();
|
||||
byte[] buffer = new byte[4];
|
||||
int bytesPerSample = Header.BytesPerSample;
|
||||
foreach (var value in PeakSamples)
|
||||
{
|
||||
writeSample(buffer, value);
|
||||
stream.Write(buffer, 0, bytesPerSample);
|
||||
}
|
||||
}
|
||||
|
||||
private void Initialize(Stream stream)
|
||||
{
|
||||
_stream = stream;
|
||||
Header = new WaveHeader(_stream);
|
||||
}
|
||||
|
||||
private int ReadValue8Bit(ref int index)
|
||||
{
|
||||
int result = sbyte.MinValue + _data[index];
|
||||
int result = sbyte.MinValue + data[index];
|
||||
index += 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
private int ReadValue16Bit(ref int index)
|
||||
private static int ReadValue16Bit(byte[] data, ref int index)
|
||||
{
|
||||
int result = (short)
|
||||
((_data[index ] ) |
|
||||
(_data[index + 1] << 8));
|
||||
((data[index ] ) |
|
||||
(data[index + 1] << 8));
|
||||
index += 2;
|
||||
return result;
|
||||
}
|
||||
|
||||
private int ReadValue24Bit(ref int index)
|
||||
private static int ReadValue24Bit(byte[] data, ref int index)
|
||||
{
|
||||
int result =
|
||||
((_data[index ] << 8) |
|
||||
(_data[index + 1] << 16) |
|
||||
(_data[index + 2] << 24)) >> 8;
|
||||
((data[index ] << 8) |
|
||||
(data[index + 1] << 16) |
|
||||
(data[index + 2] << 24)) >> 8;
|
||||
index += 3;
|
||||
return result;
|
||||
}
|
||||
|
||||
private int ReadValue32Bit(ref int index)
|
||||
private static int ReadValue32Bit(byte[] data, ref int index)
|
||||
{
|
||||
int result =
|
||||
(_data[index ] ) |
|
||||
(_data[index + 1] << 8) |
|
||||
(_data[index + 2] << 16) |
|
||||
(_data[index + 3] << 24);
|
||||
(data[index ] ) |
|
||||
(data[index + 1] << 8) |
|
||||
(data[index + 2] << 16) |
|
||||
(data[index + 3] << 24);
|
||||
index += 4;
|
||||
return result;
|
||||
}
|
||||
|
||||
private void WriteValue8Bit(byte[] buffer, int value)
|
||||
private static void WriteValue8Bit(byte[] buffer, int offset, int value)
|
||||
{
|
||||
buffer[0] = (byte)(value - sbyte.MinValue);
|
||||
buffer[offset] = (byte)(value - sbyte.MinValue);
|
||||
}
|
||||
|
||||
private void WriteValue16Bit(byte[] buffer, int value)
|
||||
private static void WriteValue16Bit(byte[] buffer, int offset, int value)
|
||||
{
|
||||
buffer[0] = (byte)value;
|
||||
buffer[1] = (byte)(value >> 8);
|
||||
buffer[offset ] = (byte)value;
|
||||
buffer[offset + 1] = (byte)(value >> 8);
|
||||
}
|
||||
|
||||
private void WriteValue24Bit(byte[] buffer, int value)
|
||||
private static void WriteValue24Bit(byte[] buffer, int offset, int value)
|
||||
{
|
||||
buffer[0] = (byte)value;
|
||||
buffer[1] = (byte)(value >> 8);
|
||||
buffer[2] = (byte)(value >> 16);
|
||||
buffer[offset ] = (byte)value;
|
||||
buffer[offset + 1] = (byte)(value >> 8);
|
||||
buffer[offset + 2] = (byte)(value >> 16);
|
||||
}
|
||||
|
||||
private void WriteValue32Bit(byte[] buffer, int value)
|
||||
private static void WriteValue32Bit(byte[] buffer, int offset, int value)
|
||||
{
|
||||
buffer[0] = (byte)value;
|
||||
buffer[1] = (byte)(value >> 8);
|
||||
buffer[2] = (byte)(value >> 16);
|
||||
buffer[3] = (byte)(value >> 24);
|
||||
buffer[offset ] = (byte)value;
|
||||
buffer[offset + 1] = (byte)(value >> 8);
|
||||
buffer[offset + 2] = (byte)(value >> 16);
|
||||
buffer[offset + 3] = (byte)(value >> 24);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine how to read sample values
|
||||
/// </summary>
|
||||
/// <returns>Sample data reader that matches bits per sample</returns>
|
||||
private ReadSampleDataValueDelegate GetSampleDataReader()
|
||||
private double GetSampleScale()
|
||||
{
|
||||
switch (Header.BytesPerSample)
|
||||
return (1.0 / Math.Pow(2.0, _header.BytesPerSample * 8 - 1));
|
||||
}
|
||||
|
||||
private double GetSampleAndChannelScale()
|
||||
{
|
||||
return GetSampleScale() / _header.NumberOfChannels;
|
||||
}
|
||||
|
||||
private ReadSampleDataValue GetSampleDataReader()
|
||||
{
|
||||
switch (_header.BytesPerSample)
|
||||
{
|
||||
case 1:
|
||||
return ReadValue8Bit;
|
||||
@ -450,13 +611,13 @@ namespace Nikse.SubtitleEdit.Core
|
||||
case 4:
|
||||
return ReadValue32Bit;
|
||||
default:
|
||||
throw new InvalidDataException("Cannot read bits per sample of " + Header.BitsPerSample);
|
||||
throw new InvalidDataException("Cannot read bits per sample of " + _header.BitsPerSample);
|
||||
}
|
||||
}
|
||||
|
||||
private Action<byte[], int> GetSampleDataWriter()
|
||||
private WriteSampleDataValue GetSampleDataWriter()
|
||||
{
|
||||
switch (Header.BytesPerSample)
|
||||
switch (_header.BytesPerSample)
|
||||
{
|
||||
case 1:
|
||||
return WriteValue8Bit;
|
||||
@ -467,7 +628,7 @@ namespace Nikse.SubtitleEdit.Core
|
||||
case 4:
|
||||
return WriteValue32Bit;
|
||||
default:
|
||||
throw new InvalidDataException("Cannot write bits per sample of " + Header.BitsPerSample);
|
||||
throw new InvalidDataException("Cannot write bits per sample of " + _header.BitsPerSample);
|
||||
}
|
||||
}
|
||||
|
||||
@ -484,42 +645,36 @@ namespace Nikse.SubtitleEdit.Core
|
||||
|
||||
//////////////////////////////////////// SPECTRUM ///////////////////////////////////////////////////////////
|
||||
|
||||
public List<Bitmap> GenerateFourierData(int nfft, string spectrogramDirectory, int delayInMilliseconds)
|
||||
public SpectrogramData GenerateSpectrogram(int delayInMilliseconds, string spectrogramDirectory)
|
||||
{
|
||||
if (Header.BytesPerSample == 4)
|
||||
{
|
||||
// Can't handle 32-bit samples due to the way the channel averaging is done
|
||||
throw new Exception("32-bit samples are unsupported.");
|
||||
}
|
||||
const int fftSize = 256; // image height = fft size / 2
|
||||
const int imageWidth = 1024;
|
||||
|
||||
const int bitmapWidth = 1024;
|
||||
int delaySampleCount = (int)(_header.SampleRate * (delayInMilliseconds / TimeCode.BaseUnit));
|
||||
|
||||
List<Bitmap> bitmaps = new List<Bitmap>();
|
||||
SpectrogramDrawer drawer = new SpectrogramDrawer(nfft);
|
||||
Task saveImageTask = null;
|
||||
ReadSampleDataValueDelegate readSampleDataValue = GetSampleDataReader();
|
||||
double sampleScale = 1.0 / (Math.Pow(2.0, Header.BytesPerSample * 8 - 1) * Header.NumberOfChannels);
|
||||
|
||||
int delaySampleCount = (int)(Header.SampleRate * (delayInMilliseconds / TimeCode.BaseUnit));
|
||||
|
||||
// other code (e.g. generating peaks) doesn't handle negative delays, so we'll do the same for now
|
||||
// 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);
|
||||
|
||||
long fileSampleCount = Header.LengthInSamples;
|
||||
var images = new List<Bitmap>();
|
||||
var drawer = new SpectrogramDrawer(fftSize);
|
||||
var readSampleDataValue = GetSampleDataReader();
|
||||
Task saveImageTask = null;
|
||||
double sampleAndChannelScale = GetSampleAndChannelScale();
|
||||
long fileSampleCount = _header.LengthInSamples;
|
||||
long fileSampleOffset = -delaySampleCount;
|
||||
int chunkSampleCount = nfft * bitmapWidth;
|
||||
int chunkSampleCount = fftSize * imageWidth;
|
||||
int chunkCount = (int)Math.Ceiling((double)(fileSampleCount + delaySampleCount) / chunkSampleCount);
|
||||
byte[] data = new byte[chunkSampleCount * _header.BlockAlign];
|
||||
double[] chunkSamples = new double[chunkSampleCount];
|
||||
|
||||
Directory.CreateDirectory(spectrogramDirectory);
|
||||
|
||||
_data = new byte[chunkSampleCount * Header.BlockAlign];
|
||||
_stream.Seek(Header.DataStartPosition, SeekOrigin.Begin);
|
||||
_stream.Seek(_header.DataStartPosition, SeekOrigin.Begin);
|
||||
|
||||
// for negative delays, skip samples at the beginning
|
||||
if (fileSampleOffset > 0)
|
||||
{
|
||||
_stream.Seek(fileSampleOffset * Header.BlockAlign, SeekOrigin.Current);
|
||||
_stream.Seek(fileSampleOffset * _header.BlockAlign, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
for (int iChunk = 0; iChunk < chunkCount; iChunk++)
|
||||
@ -551,19 +706,19 @@ namespace Nikse.SubtitleEdit.Core
|
||||
// read samples from the file
|
||||
if (fileReadSampleCount > 0)
|
||||
{
|
||||
int fileReadByteCount = fileReadSampleCount * Header.BlockAlign;
|
||||
_stream.Read(_data, 0, fileReadByteCount);
|
||||
int fileReadByteCount = fileReadSampleCount * _header.BlockAlign;
|
||||
_stream.Read(data, 0, fileReadByteCount);
|
||||
fileSampleOffset += fileReadSampleCount;
|
||||
|
||||
int dataByteOffset = 0;
|
||||
while (dataByteOffset < fileReadByteCount)
|
||||
{
|
||||
int value = 0;
|
||||
for (int iChannel = 0; iChannel < Header.NumberOfChannels; iChannel++)
|
||||
double value = 0D;
|
||||
for (int iChannel = 0; iChannel < _header.NumberOfChannels; iChannel++)
|
||||
{
|
||||
value += readSampleDataValue(ref dataByteOffset);
|
||||
value += readSampleDataValue(data, ref dataByteOffset);
|
||||
}
|
||||
chunkSamples[chunkSampleOffset] = value * sampleScale;
|
||||
chunkSamples[chunkSampleOffset] = value * sampleAndChannelScale;
|
||||
chunkSampleOffset += 1;
|
||||
}
|
||||
}
|
||||
@ -577,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)
|
||||
@ -597,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)nfft / Header.SampleRate).ToString(culture);
|
||||
doc.DocumentElement.SelectSingleNode("NFFT").InnerText = nfft.ToString(culture);
|
||||
doc.DocumentElement.SelectSingleNode("ImageWidth").InnerText = bitmapWidth.ToString(culture);
|
||||
doc.DocumentElement.SelectSingleNode("SecondsPerImage").InnerText = ((double)chunkSampleCount / Header.SampleRate).ToString(culture); // currently unused; for backwards compatibility
|
||||
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
|
||||
|
@ -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
|
||||
{
|
||||
@ -78,17 +77,13 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
private Paragraph _nextParagraph;
|
||||
private bool _firstMove = true;
|
||||
private double _currentVideoPositionSeconds = -1;
|
||||
private WavePeakGenerator _wavePeaks;
|
||||
private WavePeakData _wavePeaks;
|
||||
private Subtitle _subtitle;
|
||||
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 _sampleDuration;
|
||||
private double _imageWidth;
|
||||
private int _nfft;
|
||||
|
||||
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;
|
||||
|
||||
@ -136,8 +130,8 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
}
|
||||
|
||||
public const double VerticalZoomMinimum = 1.0;
|
||||
public const double VerticalZoomMaximum = 40.0;
|
||||
private double _verticalZoomFactor = 2.0; // 1.0=no zoom
|
||||
public const double VerticalZoomMaximum = 20.0;
|
||||
private double _verticalZoomFactor = 1.0; // 1.0=no zoom
|
||||
public double VerticalZoomFactor
|
||||
{
|
||||
get
|
||||
@ -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
|
||||
@ -238,9 +231,9 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
{
|
||||
if (_wavePeaks != null)
|
||||
{
|
||||
double endPositionSeconds = value + ((double)Width / _wavePeaks.Header.SampleRate) / _zoomFactor;
|
||||
if (endPositionSeconds > _wavePeaks.Header.LengthInSeconds)
|
||||
value -= endPositionSeconds - _wavePeaks.Header.LengthInSeconds;
|
||||
double endPositionSeconds = value + ((double)Width / _wavePeaks.SampleRate) / _zoomFactor;
|
||||
if (endPositionSeconds > _wavePeaks.LengthInSeconds)
|
||||
value -= endPositionSeconds - _wavePeaks.LengthInSeconds;
|
||||
}
|
||||
if (value < 0)
|
||||
value = 0;
|
||||
@ -275,11 +268,11 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
{
|
||||
get
|
||||
{
|
||||
return XPositionToSeconds(Width);
|
||||
return RelativeXPositionToSeconds(Width);
|
||||
}
|
||||
}
|
||||
|
||||
public WavePeakGenerator WavePeaks
|
||||
public WavePeakData WavePeaks
|
||||
{
|
||||
get
|
||||
{
|
||||
@ -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()
|
||||
@ -404,23 +386,20 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
private static int CalculateHeight(double value, int imageHeight, int maxHeight)
|
||||
{
|
||||
double percentage = value / maxHeight;
|
||||
var result = (int)Math.Round((percentage / 2.0 + 0.5) * imageHeight);
|
||||
return imageHeight - result;
|
||||
}
|
||||
|
||||
private class IsSelectedHelper
|
||||
{
|
||||
private readonly List<SelectionRange> _ranges = new List<SelectionRange>();
|
||||
private int _lastPosition = int.MaxValue;
|
||||
private SelectionRange _nextSelection;
|
||||
|
||||
public IsSelectedHelper(IEnumerable<Paragraph> paragraphs, Func<double, int> secondsToPosition)
|
||||
public IsSelectedHelper(IEnumerable<Paragraph> paragraphs, int sampleRate)
|
||||
{
|
||||
foreach (Paragraph p in paragraphs)
|
||||
_ranges.Add(new SelectionRange(secondsToPosition(p.StartTime.TotalSeconds), secondsToPosition(p.EndTime.TotalSeconds)));
|
||||
{
|
||||
int start = (int)Math.Round(p.StartTime.TotalSeconds * sampleRate);
|
||||
int end = (int)Math.Round(p.EndTime.TotalSeconds * sampleRate);
|
||||
_ranges.Add(new SelectionRange(start, end));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSelected(int position)
|
||||
@ -459,7 +438,7 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
internal void WaveformPaint(object sender, PaintEventArgs e)
|
||||
{
|
||||
Graphics graphics = e.Graphics;
|
||||
if (_wavePeaks != null && _wavePeaks.AllSamples != null)
|
||||
if (_wavePeaks != null)
|
||||
{
|
||||
bool showSpectrogram = IsSpectrogramAvailable && ShowSpectrogram;
|
||||
bool showSpectrogramOnly = showSpectrogram && !ShowWaveform;
|
||||
@ -477,7 +456,7 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
// spectrogram
|
||||
if (showSpectrogram)
|
||||
{
|
||||
DrawSpectrogramBitmap(StartPositionSeconds, graphics);
|
||||
DrawSpectrogram(graphics);
|
||||
}
|
||||
|
||||
// waveform
|
||||
@ -486,23 +465,35 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
using (var penNormal = new Pen(Color))
|
||||
using (var penSelected = new Pen(SelectedColor)) // selected paragraph
|
||||
{
|
||||
var pen = penNormal;
|
||||
var isSelectedHelper = new IsSelectedHelper(_allSelectedParagraphs, SecondsToXPositionNoZoom);
|
||||
int maxHeight = (int)(Math.Max(Math.Abs(_wavePeaks.DataMinValue), Math.Abs(_wavePeaks.DataMaxValue)) / VerticalZoomFactor);
|
||||
int start = SecondsToXPositionNoZoom(StartPositionSeconds);
|
||||
float xPrev = 0;
|
||||
int yPrev = Height / 2;
|
||||
float x = 0;
|
||||
int y;
|
||||
for (int i = 0; i < _wavePeaks.AllSamples.Count - start && x < Width; i++)
|
||||
var isSelectedHelper = new IsSelectedHelper(_allSelectedParagraphs, _wavePeaks.SampleRate);
|
||||
int baseHeight = (int)(_wavePeaks.HighestPeak / VerticalZoomFactor);
|
||||
int halfWaveformHeight = waveformHeight / 2;
|
||||
Func<float, float> calculateY = (value) =>
|
||||
{
|
||||
int n = start + i;
|
||||
x = (float)(_zoomFactor * i);
|
||||
y = CalculateHeight(_wavePeaks.AllSamples[n], waveformHeight, maxHeight);
|
||||
graphics.DrawLine(pen, xPrev, yPrev, x, y);
|
||||
xPrev = x;
|
||||
yPrev = y;
|
||||
pen = isSelectedHelper.IsSelected(n) ? penSelected : penNormal;
|
||||
float offset = (value / baseHeight) * halfWaveformHeight;
|
||||
if (offset > halfWaveformHeight)
|
||||
offset = halfWaveformHeight;
|
||||
if (offset < -halfWaveformHeight)
|
||||
offset = -halfWaveformHeight;
|
||||
return halfWaveformHeight - offset;
|
||||
};
|
||||
for (int x = 0; x < Width; x++)
|
||||
{
|
||||
float pos = (float)RelativeXPositionToSeconds(x) * _wavePeaks.SampleRate;
|
||||
int pos0 = (int)pos;
|
||||
int pos1 = pos0 + 1;
|
||||
if (pos1 >= _wavePeaks.Peaks.Count)
|
||||
break;
|
||||
float pos1Weight = pos - pos0;
|
||||
float pos0Weight = 1F - pos1Weight;
|
||||
var peak0 = _wavePeaks.Peaks[pos0];
|
||||
var peak1 = _wavePeaks.Peaks[pos1];
|
||||
float max = peak0.Max * pos0Weight + peak1.Max * pos1Weight;
|
||||
float min = peak0.Min * pos0Weight + peak1.Min * pos1Weight;
|
||||
float yMax = calculateY(max);
|
||||
float yMin = Math.Max(calculateY(min), yMax + 0.1F);
|
||||
var pen = isSelectedHelper.IsSelected(pos0) ? penSelected : penNormal;
|
||||
graphics.DrawLine(pen, x, yMax, x, yMin);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -561,7 +552,7 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
if (currentRegionWidth > 40)
|
||||
{
|
||||
using (var brush = new SolidBrush(Color.Turquoise))
|
||||
graphics.DrawString(string.Format("{0:0.###} {1}", ((double)currentRegionWidth / _wavePeaks.Header.SampleRate / _zoomFactor), Configuration.Settings.Language.Waveform.Seconds), Font, brush, new PointF(currentRegionLeft + 3, Height - 32));
|
||||
graphics.DrawString(string.Format("{0:0.###} {1}", ((double)currentRegionWidth / _wavePeaks.SampleRate / _zoomFactor), Configuration.Settings.Language.Waveform.Seconds), Font, brush, new PointF(currentRegionLeft + 3, Height - 32));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -617,8 +608,8 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
else
|
||||
{
|
||||
double interval = ZoomFactor >= 0.4 ?
|
||||
0.1 * _wavePeaks.Header.SampleRate * _zoomFactor : // a pixel is 0.1 second
|
||||
1.0 * _wavePeaks.Header.SampleRate * _zoomFactor; // a pixel is 1.0 second
|
||||
0.1 * _wavePeaks.SampleRate * _zoomFactor : // a pixel is 0.1 second
|
||||
1.0 * _wavePeaks.SampleRate * _zoomFactor; // a pixel is 1.0 second
|
||||
using (var pen = new Pen(new SolidBrush(GridColor)))
|
||||
{
|
||||
for (double i = SecondsToXPosition(StartPositionSeconds) % ((int)Math.Round(interval)); i < Width; i += interval)
|
||||
@ -638,14 +629,14 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
private void DrawTimeLine(Graphics graphics, int imageHeight)
|
||||
{
|
||||
double seconds = Math.Ceiling(StartPositionSeconds) - StartPositionSeconds;
|
||||
float position = SecondsToXPosition(seconds);
|
||||
int position = SecondsToXPosition(seconds);
|
||||
using (var pen = new Pen(TextColor))
|
||||
using (var textBrush = new SolidBrush(TextColor))
|
||||
using (var textFont = new Font(Font.FontFamily, 7))
|
||||
{
|
||||
while (position < Width)
|
||||
{
|
||||
var n = _zoomFactor * _wavePeaks.Header.SampleRate;
|
||||
var n = _zoomFactor * _wavePeaks.SampleRate;
|
||||
if (n > 38 || (int)Math.Round(StartPositionSeconds + seconds) % 5 == 0)
|
||||
{
|
||||
graphics.DrawLine(pen, position, imageHeight, position, imageHeight - 10);
|
||||
@ -707,7 +698,7 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
};
|
||||
|
||||
const int padding = 3;
|
||||
double n = _zoomFactor * _wavePeaks.Header.SampleRate;
|
||||
double n = _zoomFactor * _wavePeaks.SampleRate;
|
||||
|
||||
// paragraph text
|
||||
if (n > 80)
|
||||
@ -733,19 +724,24 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
}
|
||||
}
|
||||
|
||||
private double XPositionToSeconds(double x)
|
||||
private double RelativeXPositionToSeconds(int x)
|
||||
{
|
||||
return StartPositionSeconds + (x / _wavePeaks.Header.SampleRate) / _zoomFactor;
|
||||
return StartPositionSeconds + ((double)x / _wavePeaks.SampleRate) / _zoomFactor;
|
||||
}
|
||||
|
||||
private int SecondsToXPosition(double seconds)
|
||||
{
|
||||
return (int)Math.Round(seconds * _wavePeaks.Header.SampleRate * _zoomFactor);
|
||||
return (int)Math.Round(seconds * _wavePeaks.SampleRate * _zoomFactor);
|
||||
}
|
||||
|
||||
private int SecondsToXPositionNoZoom(double seconds)
|
||||
private int SecondsToSampleIndex(double seconds)
|
||||
{
|
||||
return (int)Math.Round(seconds * _wavePeaks.Header.SampleRate);
|
||||
return (int)Math.Round(seconds * _wavePeaks.SampleRate);
|
||||
}
|
||||
|
||||
private double SampleIndexToSeconds(int index)
|
||||
{
|
||||
return (double)index / _wavePeaks.SampleRate;
|
||||
}
|
||||
|
||||
private void WaveformMouseDown(object sender, MouseEventArgs e)
|
||||
@ -762,7 +758,7 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
_buttonDownTimeTicks = DateTime.Now.Ticks;
|
||||
|
||||
Cursor = Cursors.VSplit;
|
||||
double seconds = XPositionToSeconds(e.X);
|
||||
double seconds = RelativeXPositionToSeconds(e.X);
|
||||
var milliseconds = (int)(seconds * TimeCode.BaseUnit);
|
||||
|
||||
if (SetParagrapBorderHit(milliseconds, NewSelectionParagraph))
|
||||
@ -830,7 +826,7 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
_mouseDownParagraph = p;
|
||||
oldMouseDownParagraph = new Paragraph(_mouseDownParagraph);
|
||||
_mouseDownParagraphType = MouseDownParagraphType.Whole;
|
||||
_moveWholeStartDifferenceMilliseconds = (XPositionToSeconds(e.X) * TimeCode.BaseUnit) - p.StartTime.TotalMilliseconds;
|
||||
_moveWholeStartDifferenceMilliseconds = (RelativeXPositionToSeconds(e.X) * TimeCode.BaseUnit) - p.StartTime.TotalMilliseconds;
|
||||
Cursor = Cursors.Hand;
|
||||
SetMinAndMax();
|
||||
}
|
||||
@ -872,13 +868,11 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
{
|
||||
if (e.Button == MouseButtons.Right)
|
||||
{
|
||||
double seconds = XPositionToSeconds(e.X);
|
||||
double seconds = RelativeXPositionToSeconds(e.X);
|
||||
var milliseconds = (int)(seconds * TimeCode.BaseUnit);
|
||||
|
||||
double currentRegionLeft = Math.Min(_mouseMoveStartX, _mouseMoveEndX);
|
||||
double currentRegionRight = Math.Max(_mouseMoveStartX, _mouseMoveEndX);
|
||||
currentRegionLeft = XPositionToSeconds(currentRegionLeft);
|
||||
currentRegionRight = XPositionToSeconds(currentRegionRight);
|
||||
double currentRegionLeft = RelativeXPositionToSeconds(Math.Min(_mouseMoveStartX, _mouseMoveEndX));
|
||||
double currentRegionRight = RelativeXPositionToSeconds(Math.Max(_mouseMoveStartX, _mouseMoveEndX));
|
||||
|
||||
if (OnNewSelectionRightClicked != null && seconds > currentRegionLeft && seconds < currentRegionRight)
|
||||
{
|
||||
@ -1095,7 +1089,7 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
if (_mouseDownParagraph == null)
|
||||
{
|
||||
_mouseMoveEndX = 0;
|
||||
_mouseMoveStartX += (int)(_wavePeaks.Header.SampleRate * 0.1);
|
||||
_mouseMoveStartX += (int)(_wavePeaks.SampleRate * 0.1);
|
||||
OnPositionSelected.Invoke(this, new ParagraphEventArgs(StartPositionSeconds, null));
|
||||
}
|
||||
}
|
||||
@ -1103,7 +1097,7 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
Invalidate();
|
||||
return;
|
||||
}
|
||||
if (e.X > Width && StartPositionSeconds + 0.1 < _wavePeaks.Header.LengthInSeconds && _mouseDown)
|
||||
if (e.X > Width && StartPositionSeconds + 0.1 < _wavePeaks.LengthInSeconds && _mouseDown)
|
||||
{
|
||||
//if (e.X > _mouseMoveLastX) // not much room for moving mouse cursor, so just scroll right
|
||||
{
|
||||
@ -1111,7 +1105,7 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
if (_mouseDownParagraph == null)
|
||||
{
|
||||
_mouseMoveEndX = Width;
|
||||
_mouseMoveStartX -= (int)(_wavePeaks.Header.SampleRate * 0.1);
|
||||
_mouseMoveStartX -= (int)(_wavePeaks.SampleRate * 0.1);
|
||||
OnPositionSelected.Invoke(this, new ParagraphEventArgs(StartPositionSeconds, null));
|
||||
}
|
||||
}
|
||||
@ -1126,7 +1120,7 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
|
||||
if (e.Button == MouseButtons.None)
|
||||
{
|
||||
double seconds = XPositionToSeconds(e.X);
|
||||
double seconds = RelativeXPositionToSeconds(e.X);
|
||||
var milliseconds = (int)(seconds * TimeCode.BaseUnit);
|
||||
|
||||
if (IsParagrapBorderHit(milliseconds, NewSelectionParagraph))
|
||||
@ -1150,7 +1144,7 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
{
|
||||
if (_mouseDownParagraph != null)
|
||||
{
|
||||
double seconds = XPositionToSeconds(e.X);
|
||||
double seconds = RelativeXPositionToSeconds(e.X);
|
||||
var milliseconds = (int)(seconds * TimeCode.BaseUnit);
|
||||
var subtitleIndex = _subtitle.GetIndex(_mouseDownParagraph);
|
||||
_prevParagraph = _subtitle.GetParagraphOrDefault(subtitleIndex - 1);
|
||||
@ -1292,8 +1286,8 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
int start = Math.Min(_mouseMoveStartX, _mouseMoveEndX);
|
||||
int end = Math.Max(_mouseMoveStartX, _mouseMoveEndX);
|
||||
|
||||
var startTotalSeconds = XPositionToSeconds(start);
|
||||
var endTotalSeconds = XPositionToSeconds(end);
|
||||
var startTotalSeconds = RelativeXPositionToSeconds(start);
|
||||
var endTotalSeconds = RelativeXPositionToSeconds(end);
|
||||
|
||||
if (PreventOverlap && endTotalSeconds * TimeCode.BaseUnit >= _wholeParagraphMaxMilliseconds)
|
||||
{
|
||||
@ -1375,7 +1369,7 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
|
||||
private void WaveformMouseEnter(object sender, EventArgs e)
|
||||
{
|
||||
if (_wavePeaks == null || _wavePeaks.Header == null)
|
||||
if (_wavePeaks == null)
|
||||
return;
|
||||
|
||||
if (_noClear)
|
||||
@ -1413,7 +1407,7 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
{
|
||||
if (OnPause != null)
|
||||
OnPause.Invoke(sender, null);
|
||||
double seconds = XPositionToSeconds(e.X);
|
||||
double seconds = RelativeXPositionToSeconds(e.X);
|
||||
var milliseconds = (int)(seconds * TimeCode.BaseUnit);
|
||||
|
||||
Paragraph p = GetParagraphAtMilliseconds(milliseconds);
|
||||
@ -1455,7 +1449,7 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
{
|
||||
if (ModifierKeys == Keys.Shift && _selectedParagraph != null)
|
||||
{
|
||||
double seconds = XPositionToSeconds(e.X);
|
||||
double seconds = RelativeXPositionToSeconds(e.X);
|
||||
var milliseconds = (int)(seconds * TimeCode.BaseUnit);
|
||||
if (_mouseDownParagraphType == MouseDownParagraphType.None || _mouseDownParagraphType == MouseDownParagraphType.Whole)
|
||||
{
|
||||
@ -1472,7 +1466,7 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
}
|
||||
if (ModifierKeys == Keys.Control && _selectedParagraph != null)
|
||||
{
|
||||
double seconds = XPositionToSeconds(e.X);
|
||||
double seconds = RelativeXPositionToSeconds(e.X);
|
||||
var milliseconds = (int)(seconds * TimeCode.BaseUnit);
|
||||
if (_mouseDownParagraphType == MouseDownParagraphType.None || _mouseDownParagraphType == MouseDownParagraphType.Whole)
|
||||
{
|
||||
@ -1489,7 +1483,7 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
}
|
||||
if (ModifierKeys == (Keys.Control | Keys.Shift) && _selectedParagraph != null)
|
||||
{
|
||||
double seconds = XPositionToSeconds(e.X);
|
||||
double seconds = RelativeXPositionToSeconds(e.X);
|
||||
if (_mouseDownParagraphType == MouseDownParagraphType.None || _mouseDownParagraphType == MouseDownParagraphType.Whole)
|
||||
{
|
||||
_oldParagraph = new Paragraph(_selectedParagraph);
|
||||
@ -1501,7 +1495,7 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
}
|
||||
if (ModifierKeys == Keys.Alt && _selectedParagraph != null)
|
||||
{
|
||||
double seconds = XPositionToSeconds(e.X);
|
||||
double seconds = RelativeXPositionToSeconds(e.X);
|
||||
var milliseconds = (int)(seconds * TimeCode.BaseUnit);
|
||||
if (_mouseDownParagraphType == MouseDownParagraphType.None || _mouseDownParagraphType == MouseDownParagraphType.Whole)
|
||||
{
|
||||
@ -1517,7 +1511,7 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
}
|
||||
|
||||
if (_mouseDownParagraphType == MouseDownParagraphType.None || _mouseDownParagraphType == MouseDownParagraphType.Whole)
|
||||
OnSingleClick.Invoke(this, new ParagraphEventArgs(XPositionToSeconds(e.X), null));
|
||||
OnSingleClick.Invoke(this, new ParagraphEventArgs(RelativeXPositionToSeconds(e.X), null));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1535,6 +1529,12 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
{
|
||||
ZoomOut();
|
||||
}
|
||||
else if (e.Modifiers == Keys.Control && e.KeyCode == Keys.D0)
|
||||
{
|
||||
ZoomFactor = 1.0;
|
||||
if (OnZoomedChanged != null)
|
||||
OnZoomedChanged.Invoke(this, null);
|
||||
}
|
||||
else if (e.Modifiers == Keys.None && e.KeyCode == Keys.Z)
|
||||
{
|
||||
if (StartPositionSeconds > 0.1)
|
||||
@ -1547,7 +1547,7 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
}
|
||||
else if (e.Modifiers == Keys.None && e.KeyCode == Keys.X)
|
||||
{
|
||||
if (StartPositionSeconds + 0.1 < _wavePeaks.Header.LengthInSeconds)
|
||||
if (StartPositionSeconds + 0.1 < _wavePeaks.LengthInSeconds)
|
||||
{
|
||||
StartPositionSeconds += 0.1;
|
||||
OnPositionSelected.Invoke(this, new ParagraphEventArgs(StartPositionSeconds, null));
|
||||
@ -1571,21 +1571,22 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
}
|
||||
}
|
||||
|
||||
public double FindDataBelowThreshold(int threshold, double durationInSeconds)
|
||||
public double FindDataBelowThreshold(int thresholdPercent, double durationInSeconds)
|
||||
{
|
||||
int begin = SecondsToXPosition(_currentVideoPositionSeconds + 1);
|
||||
int length = SecondsToXPosition(durationInSeconds);
|
||||
int begin = SecondsToSampleIndex(_currentVideoPositionSeconds + 1);
|
||||
int length = SecondsToSampleIndex(durationInSeconds);
|
||||
int threshold = (int)(thresholdPercent / 100.0 * _wavePeaks.HighestPeak);
|
||||
|
||||
int hitCount = 0;
|
||||
for (int i = begin; i < _wavePeaks.AllSamples.Count; i++)
|
||||
for (int i = begin; i < _wavePeaks.Peaks.Count; i++)
|
||||
{
|
||||
if (i > 0 && i < _wavePeaks.AllSamples.Count && Math.Abs(_wavePeaks.AllSamples[i]) <= threshold)
|
||||
if (i > 0 && i < _wavePeaks.Peaks.Count && _wavePeaks.Peaks[i].Abs <= threshold)
|
||||
hitCount++;
|
||||
else
|
||||
hitCount = 0;
|
||||
if (hitCount > length)
|
||||
{
|
||||
double seconds = ((i - (length / 2)) / (double)_wavePeaks.Header.SampleRate) / _zoomFactor;
|
||||
double seconds = SampleIndexToSeconds(i - (length / 2));
|
||||
if (seconds >= 0)
|
||||
{
|
||||
StartPositionSeconds = seconds;
|
||||
@ -1600,21 +1601,22 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
return -1;
|
||||
}
|
||||
|
||||
public double FindDataBelowThresholdBack(int threshold, double durationInSeconds)
|
||||
public double FindDataBelowThresholdBack(int thresholdPercent, double durationInSeconds)
|
||||
{
|
||||
int begin = SecondsToXPosition(_currentVideoPositionSeconds - 1);
|
||||
int length = SecondsToXPosition(durationInSeconds);
|
||||
int begin = SecondsToSampleIndex(_currentVideoPositionSeconds - 1);
|
||||
int length = SecondsToSampleIndex(durationInSeconds);
|
||||
int threshold = (int)(thresholdPercent / 100.0 * _wavePeaks.HighestPeak);
|
||||
|
||||
int hitCount = 0;
|
||||
for (int i = begin; i > 0; i--)
|
||||
{
|
||||
if (i > 0 && i < _wavePeaks.AllSamples.Count && Math.Abs(_wavePeaks.AllSamples[i]) <= threshold)
|
||||
if (i > 0 && i < _wavePeaks.Peaks.Count && _wavePeaks.Peaks[i].Abs <= threshold)
|
||||
hitCount++;
|
||||
else
|
||||
hitCount = 0;
|
||||
if (hitCount > length)
|
||||
{
|
||||
double seconds = (i + (length / 2)) / (double)_wavePeaks.Header.SampleRate / _zoomFactor;
|
||||
double seconds = SampleIndexToSeconds(i + (length / 2));
|
||||
if (seconds >= 0)
|
||||
{
|
||||
StartPositionSeconds = seconds;
|
||||
@ -1701,126 +1703,78 @@ 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;
|
||||
|
||||
_spectrogram = spectrogram;
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
private void LoadSpectrogramInfo(string spectrogramDirectory)
|
||||
private void DrawSpectrogram(Graphics graphics)
|
||||
{
|
||||
try
|
||||
int width = (int)Math.Round((EndPositionSeconds - StartPositionSeconds) / _spectrogram.SampleDuration);
|
||||
using (var bmpCombined = new Bitmap(width, _spectrogram.FftSize / 2))
|
||||
using (var gfxCombined = Graphics.FromImage(bmpCombined))
|
||||
{
|
||||
var doc = new XmlDocument();
|
||||
string xmlInfoFileName = Path.Combine(spectrogramDirectory, "Info.xml");
|
||||
if (File.Exists(xmlInfoFileName))
|
||||
int left = (int)Math.Round(StartPositionSeconds / _spectrogram.SampleDuration);
|
||||
int offset = 0;
|
||||
int imageIndex = left / _spectrogram.ImageWidth;
|
||||
while (offset < width && imageIndex < _spectrogram.Images.Count)
|
||||
{
|
||||
doc.Load(xmlInfoFileName);
|
||||
_sampleDuration = Convert.ToDouble(doc.DocumentElement.SelectSingleNode("SampleDuration").InnerText, CultureInfo.InvariantCulture);
|
||||
_nfft = Convert.ToInt32(doc.DocumentElement.SelectSingleNode("NFFT").InnerText, CultureInfo.InvariantCulture);
|
||||
_imageWidth = Convert.ToInt32(doc.DocumentElement.SelectSingleNode("ImageWidth").InnerText, CultureInfo.InvariantCulture);
|
||||
ShowSpectrogram = true;
|
||||
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++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowSpectrogram = false;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
ShowSpectrogram = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSpectrogramBitmap(double seconds, Graphics graphics)
|
||||
{
|
||||
double duration = EndPositionSeconds - StartPositionSeconds;
|
||||
var width = (int)(duration / _sampleDuration);
|
||||
|
||||
using (var bmpDestination = new Bitmap(width, _nfft / 2)) //calculate width
|
||||
{
|
||||
using (var gfx = Graphics.FromImage(bmpDestination))
|
||||
{
|
||||
double startRow = seconds / (_sampleDuration * _imageWidth);
|
||||
var bitmapIndex = (int)startRow;
|
||||
var subtractValue = (int)Math.Round((startRow - bitmapIndex) * _imageWidth);
|
||||
|
||||
int i = 0;
|
||||
while (i * _imageWidth < width && i + bitmapIndex < _spectrogramBitmaps.Count)
|
||||
{
|
||||
var bmp = _spectrogramBitmaps[i + bitmapIndex];
|
||||
gfx.DrawImageUnscaled(bmp, new Point(bmp.Width * i - subtractValue, 0));
|
||||
i++;
|
||||
}
|
||||
if (i + bitmapIndex < _spectrogramBitmaps.Count && subtractValue > 0)
|
||||
{
|
||||
var bmp = _spectrogramBitmaps[i + bitmapIndex];
|
||||
gfx.DrawImageUnscaled(bmp, new Point(bmp.Width * i - subtractValue, 0));
|
||||
}
|
||||
}
|
||||
if (ShowWaveform)
|
||||
graphics.DrawImage(bmpDestination, new Rectangle(0, Height - SpectrogramDisplayHeight, Width, SpectrogramDisplayHeight));
|
||||
else
|
||||
graphics.DrawImage(bmpDestination, new Rectangle(0, 0, Width, Height));
|
||||
int displayHeight = ShowWaveform ? SpectrogramDisplayHeight : Height;
|
||||
graphics.DrawImage(bmpCombined, new Rectangle(0, Height - displayHeight, Width, displayHeight));
|
||||
}
|
||||
}
|
||||
|
||||
private double GetAverageVolumeForNextMilliseconds(int sampleIndex, int milliseconds)
|
||||
{
|
||||
int length = SecondsToXPosition(milliseconds / TimeCode.BaseUnit);
|
||||
int length = SecondsToSampleIndex(milliseconds / TimeCode.BaseUnit);
|
||||
if (length < 9)
|
||||
length = 9;
|
||||
double v = 0;
|
||||
int count = 0;
|
||||
for (int i = sampleIndex; i < sampleIndex + length; i++)
|
||||
{
|
||||
if (i > 0 && i < _wavePeaks.AllSamples.Count)
|
||||
if (i > 0 && i < _wavePeaks.Peaks.Count)
|
||||
{
|
||||
v += Math.Abs(_wavePeaks.AllSamples[i]);
|
||||
v += _wavePeaks.Peaks[i].Abs;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
@ -1829,31 +1783,31 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
return v / count;
|
||||
}
|
||||
|
||||
internal void GenerateTimeCodes(double startFromSeconds, int minimumVolumePercent, int maximumVolumePercent, int defaultMilliseconds)
|
||||
internal void GenerateTimeCodes(Subtitle subtitle, double startFromSeconds, int blockSizeMilliseconds, int minimumVolumePercent, int maximumVolumePercent, int defaultMilliseconds)
|
||||
{
|
||||
int begin = SecondsToXPosition(startFromSeconds);
|
||||
int begin = SecondsToSampleIndex(startFromSeconds);
|
||||
|
||||
double average = 0;
|
||||
for (int k = begin; k < _wavePeaks.AllSamples.Count; k++)
|
||||
average += Math.Abs(_wavePeaks.AllSamples[k]);
|
||||
average = average / (_wavePeaks.AllSamples.Count - begin);
|
||||
for (int k = begin; k < _wavePeaks.Peaks.Count; k++)
|
||||
average += _wavePeaks.Peaks[k].Abs;
|
||||
average /= _wavePeaks.Peaks.Count - begin;
|
||||
|
||||
var maxThreshold = (int)(_wavePeaks.DataMaxValue * (maximumVolumePercent / 100.0));
|
||||
var maxThreshold = (int)(_wavePeaks.HighestPeak * (maximumVolumePercent / 100.0));
|
||||
var silenceThreshold = (int)(average * (minimumVolumePercent / 100.0));
|
||||
|
||||
int length50Ms = SecondsToXPosition(0.050);
|
||||
int length50Ms = SecondsToSampleIndex(0.050);
|
||||
double secondsPerParagraph = defaultMilliseconds / TimeCode.BaseUnit;
|
||||
int minBetween = SecondsToXPosition(Configuration.Settings.General.MinimumMillisecondsBetweenLines / TimeCode.BaseUnit);
|
||||
int minBetween = SecondsToSampleIndex(Configuration.Settings.General.MinimumMillisecondsBetweenLines / TimeCode.BaseUnit);
|
||||
bool subtitleOn = false;
|
||||
int i = begin;
|
||||
while (i < _wavePeaks.AllSamples.Count)
|
||||
while (i < _wavePeaks.Peaks.Count)
|
||||
{
|
||||
if (subtitleOn)
|
||||
{
|
||||
var currentLengthInSeconds = XPositionToSeconds(i - begin) - StartPositionSeconds;
|
||||
var currentLengthInSeconds = SampleIndexToSeconds(i - begin);
|
||||
if (currentLengthInSeconds > 1.0)
|
||||
{
|
||||
subtitleOn = EndParagraphDueToLowVolume(silenceThreshold, begin, true, i);
|
||||
subtitleOn = EndParagraphDueToLowVolume(subtitle, blockSizeMilliseconds, silenceThreshold, begin, true, i);
|
||||
if (!subtitleOn)
|
||||
{
|
||||
begin = i + minBetween;
|
||||
@ -1864,7 +1818,7 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
{
|
||||
for (int j = 0; j < 20; j++)
|
||||
{
|
||||
subtitleOn = EndParagraphDueToLowVolume(silenceThreshold, begin, true, i + (j * length50Ms));
|
||||
subtitleOn = EndParagraphDueToLowVolume(subtitle, blockSizeMilliseconds, silenceThreshold, begin, true, i + (j * length50Ms));
|
||||
if (!subtitleOn)
|
||||
{
|
||||
i += (j * length50Ms);
|
||||
@ -1876,8 +1830,8 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
|
||||
if (subtitleOn) // force break
|
||||
{
|
||||
var p = new Paragraph(string.Empty, (XPositionToSeconds(begin) - StartPositionSeconds) * TimeCode.BaseUnit, (XPositionToSeconds(i) - StartPositionSeconds) * TimeCode.BaseUnit);
|
||||
_subtitle.Paragraphs.Add(p);
|
||||
var p = new Paragraph(string.Empty, SampleIndexToSeconds(begin) * TimeCode.BaseUnit, SampleIndexToSeconds(i) * TimeCode.BaseUnit);
|
||||
subtitle.Paragraphs.Add(p);
|
||||
begin = i + minBetween;
|
||||
i = begin;
|
||||
}
|
||||
@ -1885,7 +1839,7 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
}
|
||||
else
|
||||
{
|
||||
double avgVol = GetAverageVolumeForNextMilliseconds(i, 100);
|
||||
double avgVol = GetAverageVolumeForNextMilliseconds(i, blockSizeMilliseconds);
|
||||
if (avgVol > silenceThreshold)
|
||||
{
|
||||
if (avgVol < maxThreshold)
|
||||
@ -1897,15 +1851,17 @@ namespace Nikse.SubtitleEdit.Controls
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
subtitle.Renumber();
|
||||
}
|
||||
|
||||
private bool EndParagraphDueToLowVolume(double silenceThreshold, int begin, bool subtitleOn, int i)
|
||||
private bool EndParagraphDueToLowVolume(Subtitle subtitle, int blockSizeMilliseconds, double silenceThreshold, int begin, bool subtitleOn, int i)
|
||||
{
|
||||
double avgVol = GetAverageVolumeForNextMilliseconds(i, 100);
|
||||
double avgVol = GetAverageVolumeForNextMilliseconds(i, blockSizeMilliseconds);
|
||||
if (avgVol < silenceThreshold)
|
||||
{
|
||||
var p = new Paragraph(string.Empty, (XPositionToSeconds(begin) - StartPositionSeconds) * TimeCode.BaseUnit, (XPositionToSeconds(i) - StartPositionSeconds) * TimeCode.BaseUnit);
|
||||
_subtitle.Paragraphs.Add(p);
|
||||
var p = new Paragraph(string.Empty, SampleIndexToSeconds(begin) * TimeCode.BaseUnit, SampleIndexToSeconds(i) * TimeCode.BaseUnit);
|
||||
subtitle.Paragraphs.Add(p);
|
||||
subtitleOn = false;
|
||||
}
|
||||
return subtitleOn;
|
||||
|
@ -15,9 +15,11 @@ namespace Nikse.SubtitleEdit.Forms
|
||||
{
|
||||
public string SourceVideoFileName { get; private set; }
|
||||
private bool _cancel;
|
||||
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;
|
||||
@ -31,10 +33,9 @@ namespace Nikse.SubtitleEdit.Forms
|
||||
labelInfo.Text = string.Empty;
|
||||
}
|
||||
|
||||
public WavePeakGenerator WavePeak { get; private set; }
|
||||
|
||||
public void Initialize(string videoFile, string spectrogramDirectory, int audioTrackNumber)
|
||||
public void Initialize(string videoFile, string peakWaveFileName, string spectrogramDirectory, int audioTrackNumber)
|
||||
{
|
||||
_peakWaveFileName = peakWaveFileName;
|
||||
_audioTrackNumber = audioTrackNumber;
|
||||
if (_audioTrackNumber < 0)
|
||||
_audioTrackNumber = 0;
|
||||
@ -236,16 +237,14 @@ namespace Nikse.SubtitleEdit.Forms
|
||||
|
||||
using (var waveFile = new WavePeakGenerator(targetFile))
|
||||
{
|
||||
waveFile.GeneratePeakSamples(delayInMilliseconds);
|
||||
Peaks = waveFile.GeneratePeaks(delayInMilliseconds, _peakWaveFileName);
|
||||
|
||||
if (Configuration.Settings.VideoControls.GenerateSpectrogram)
|
||||
{
|
||||
labelProgress.Text = Configuration.Settings.Language.AddWaveform.GeneratingSpectrogram;
|
||||
Refresh();
|
||||
SpectrogramBitmaps = waveFile.GenerateFourierData(256, _spectrogramDirectory, delayInMilliseconds); // image height = nfft / 2
|
||||
Spectrogram = waveFile.GenerateSpectrogram(delayInMilliseconds, _spectrogramDirectory);
|
||||
}
|
||||
|
||||
WavePeak = waveFile;
|
||||
}
|
||||
|
||||
labelPleaseWait.Visible = false;
|
||||
@ -379,8 +378,9 @@ namespace Nikse.SubtitleEdit.Forms
|
||||
_cancel = true;
|
||||
}
|
||||
|
||||
internal void InitializeViaWaveFile(string fileName, string spectrogramFolder)
|
||||
internal void InitializeViaWaveFile(string fileName, string peakWaveFileName, string spectrogramFolder)
|
||||
{
|
||||
_peakWaveFileName = peakWaveFileName;
|
||||
_wavFileName = fileName;
|
||||
_spectrogramDirectory = spectrogramFolder;
|
||||
}
|
||||
|
@ -329,12 +329,11 @@ namespace Nikse.SubtitleEdit.Forms
|
||||
{
|
||||
using (var waveFile = new WavePeakGenerator(targetFile))
|
||||
{
|
||||
waveFile.GeneratePeakSamples(delayInMilliseconds);
|
||||
waveFile.WritePeakSamples(Main.GetPeakWaveFileName(videoFileName));
|
||||
waveFile.GeneratePeaks(delayInMilliseconds, Main.GetPeakWaveFileName(videoFileName));
|
||||
|
||||
if (Configuration.Settings.VideoControls.GenerateSpectrogram)
|
||||
{
|
||||
waveFile.GenerateFourierData(256, Main.GetSpectrogramFolder(videoFileName), delayInMilliseconds); // image height = nfft / 2
|
||||
waveFile.GenerateSpectrogram(delayInMilliseconds, Main.GetSpectrogramFolder(videoFileName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
@ -12679,12 +12676,9 @@ namespace Nikse.SubtitleEdit.Forms
|
||||
string spectrogramFolder = GetSpectrogramFolder(fileName);
|
||||
if (File.Exists(peakWaveFileName))
|
||||
{
|
||||
audioVisualizer.WavePeaks = new WavePeakGenerator(peakWaveFileName);
|
||||
audioVisualizer.ResetSpectrogram();
|
||||
audioVisualizer.InitializeSpectrogram(spectrogramFolder);
|
||||
audioVisualizer.WavePeaks = WavePeakData.FromDisk(peakWaveFileName);
|
||||
audioVisualizer.Spectrogram = SpectrogramData.FromDisk(spectrogramFolder);
|
||||
toolStripComboBoxWaveform_SelectedIndexChanged(null, null);
|
||||
audioVisualizer.WavePeaks.GenerateAllSamples();
|
||||
audioVisualizer.WavePeaks.Close();
|
||||
SetWaveformPosition(0, 0, 0);
|
||||
timerWaveform.Start();
|
||||
}
|
||||
@ -13426,8 +13420,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)
|
||||
@ -14819,21 +14812,16 @@ namespace Nikse.SubtitleEdit.Forms
|
||||
|
||||
if (IsFileValidForVisualizer(_videoFileName))
|
||||
{
|
||||
addWaveform.InitializeViaWaveFile(_videoFileName, spectrogramFolder);
|
||||
addWaveform.InitializeViaWaveFile(_videoFileName, peakWaveFileName, spectrogramFolder);
|
||||
}
|
||||
else
|
||||
{
|
||||
addWaveform.Initialize(_videoFileName, spectrogramFolder, _videoAudioTrackNumber);
|
||||
addWaveform.Initialize(_videoFileName, peakWaveFileName, spectrogramFolder, _videoAudioTrackNumber);
|
||||
}
|
||||
if (addWaveform.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
addWaveform.WavePeak.WritePeakSamples(peakWaveFileName);
|
||||
var audioPeakWave = new WavePeakGenerator(peakWaveFileName);
|
||||
audioPeakWave.GenerateAllSamples();
|
||||
audioPeakWave.Close();
|
||||
audioVisualizer.WavePeaks = audioPeakWave;
|
||||
if (addWaveform.SpectrogramBitmaps != null)
|
||||
audioVisualizer.InitializeSpectrogram(addWaveform.SpectrogramBitmaps, spectrogramFolder);
|
||||
audioVisualizer.WavePeaks = addWaveform.Peaks;
|
||||
audioVisualizer.Spectrogram = addWaveform.Spectrogram;
|
||||
timerWaveform.Start();
|
||||
}
|
||||
}
|
||||
@ -15204,19 +15192,20 @@ namespace Nikse.SubtitleEdit.Forms
|
||||
}
|
||||
|
||||
if (_videoFileName == null)
|
||||
{
|
||||
OpenVideo(fileName);
|
||||
return;
|
||||
}
|
||||
|
||||
using (var addWaveform = new AddWaveform())
|
||||
{
|
||||
string peakWaveFileName = GetPeakWaveFileName(_videoFileName);
|
||||
string spectrogramFolder = GetSpectrogramFolder(_videoFileName);
|
||||
addWaveform.InitializeViaWaveFile(fileName, spectrogramFolder);
|
||||
addWaveform.InitializeViaWaveFile(fileName, peakWaveFileName, spectrogramFolder);
|
||||
if (addWaveform.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
string peakWaveFileName = GetPeakWaveFileName(_videoFileName);
|
||||
addWaveform.WavePeak.WritePeakSamples(peakWaveFileName);
|
||||
var audioPeakWave = new WavePeakGenerator(peakWaveFileName);
|
||||
audioPeakWave.GenerateAllSamples();
|
||||
audioVisualizer.WavePeaks = audioPeakWave;
|
||||
audioVisualizer.WavePeaks = addWaveform.Peaks;
|
||||
audioVisualizer.Spectrogram = addWaveform.Spectrogram;
|
||||
timerWaveform.Start();
|
||||
}
|
||||
}
|
||||
@ -16501,8 +16490,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)
|
||||
@ -16540,7 +16528,7 @@ namespace Nikse.SubtitleEdit.Forms
|
||||
}
|
||||
}
|
||||
|
||||
if (mediaPlayer.VideoPlayer != null && audioVisualizer != null && audioVisualizer.WavePeaks != null && audioVisualizer.WavePeaks.AllSamples.Count > 0)
|
||||
if (mediaPlayer.VideoPlayer != null && audioVisualizer != null && audioVisualizer.WavePeaks != null && audioVisualizer.WavePeaks.Peaks.Count > 0)
|
||||
{
|
||||
toolStripMenuItemImportSceneChanges.Visible = true;
|
||||
toolStripMenuItemRemoveSceneChanges.Visible = audioVisualizer.SceneChanges.Count > 0;
|
||||
@ -18451,9 +18439,9 @@ namespace Nikse.SubtitleEdit.Forms
|
||||
{
|
||||
MakeHistoryForUndoOnlyIfNotResent(string.Format(_language.BeforeGuessingTimeCodes));
|
||||
|
||||
double startFrom = 0;
|
||||
double startFromSeconds = 0;
|
||||
if (form.StartFromVideoPosition)
|
||||
startFrom = mediaPlayer.CurrentPosition;
|
||||
startFromSeconds = mediaPlayer.CurrentPosition;
|
||||
|
||||
if (form.DeleteAll)
|
||||
{
|
||||
@ -18463,11 +18451,11 @@ namespace Nikse.SubtitleEdit.Forms
|
||||
{
|
||||
for (int i = _subtitle.Paragraphs.Count - 1; i > 0; i--)
|
||||
{
|
||||
if (_subtitle.Paragraphs[i].EndTime.TotalSeconds + 1 > startFrom)
|
||||
if (_subtitle.Paragraphs[i].EndTime.TotalSeconds + 1 > startFromSeconds)
|
||||
_subtitle.Paragraphs.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
audioVisualizer.GenerateTimeCodes(form.BlockSize, form.VolumeMinimum, form.VolumeMaximum, form.DefaultMilliseconds);
|
||||
audioVisualizer.GenerateTimeCodes(_subtitle, startFromSeconds, form.BlockSize, form.VolumeMinimum, form.VolumeMaximum, form.DefaultMilliseconds);
|
||||
if (IsFramesRelevant && CurrentFrameRate > 0)
|
||||
_subtitle.CalculateFrameNumbersFromTimeCodesNoCheck(CurrentFrameRate);
|
||||
SubtitleListview1.Fill(_subtitle, _subtitleAlternate);
|
||||
|
Loading…
Reference in New Issue
Block a user