fix: maintain passed in streams position

This commit is contained in:
Stevan Freeborn
2025-07-29 18:21:17 -05:00
parent ef45a4098a
commit 07190acb65
3 changed files with 74 additions and 23 deletions
@@ -52,7 +52,7 @@ internal class DefaultCommand(
.Spinner(Spinner.Known.Dots) .Spinner(Spinner.Known.Dots)
.StartAsync("Extracting audio...", async ctx => .StartAsync("Extracting audio...", async ctx =>
{ {
audioStream = await _audioExtractor.ExtractMp3FromMp4Async(videoStream).ConfigureAwait(false); audioStream = await _audioExtractor.ExtractMp3FromMp4Async(videoStream);
}); });
if (audioStream is null) if (audioStream is null)
@@ -11,6 +11,9 @@ internal interface IAudioService
/// </summary> /// </summary>
/// <param name="mp3">The input MP3 stream.</param> /// <param name="mp3">The input MP3 stream.</param>
/// <returns>The output WAV stream.</returns> /// <returns>The output WAV stream.</returns>
/// <exception cref="ArgumentNullException">Thrown when the MP3 stream is null.</exception>
/// <exception cref="ArgumentException">Thrown when the MP3 stream is not readable or seekable.</exception>
/// <remarks>The method will preserve the passed MP3 stream's data and position.</remarks
Stream ConvertMp3ToWav16(Stream mp3); Stream ConvertMp3ToWav16(Stream mp3);
/// <summary> /// <summary>
@@ -19,6 +22,9 @@ internal interface IAudioService
/// param name="wavStream">The input WAV stream.</param> /// param name="wavStream">The input WAV stream.</param>
/// <param name="segmentDuration">The duration of each segment.</param> /// <param name="segmentDuration">The duration of each segment.</param>
/// <returns>The number of segments.</returns> /// <returns>The number of segments.</returns>
/// <exception cref="ArgumentNullException">Thrown when the WAV stream is null.</exception>
/// <exception cref="ArgumentException">Thrown when the WAV stream is not readable or seekable.</exception>
/// <remarks>The method will preserve the passed WAV stream's data and position.</remarks>
int GetNumberOfWavSegments(Stream wavStream, TimeSpan segmentDuration); int GetNumberOfWavSegments(Stream wavStream, TimeSpan segmentDuration);
/// <summary> /// <summary>
@@ -28,5 +34,8 @@ internal interface IAudioService
/// <param name="segmentNumber">The segment number to retrieve.</param> /// <param name="segmentNumber">The segment number to retrieve.</param>
/// <param name="segmentDuration">The duration of each segment.</param> /// <param name="segmentDuration">The duration of each segment.</param>
/// <returns>The segment stream.</returns> /// <returns>The segment stream.</returns>
/// <exception cref="ArgumentNullException">Thrown when the WAV stream is null.</exception>
/// <exception cref="ArgumentException">Thrown when the WAV stream is not readable or seekable.</exception>
/// <remarks>The method will preserve the passed WAV stream's data and position.</remarks>
Stream GetWavSegment(Stream wavStream, int segmentNumber, TimeSpan segmentDuration); Stream GetWavSegment(Stream wavStream, int segmentNumber, TimeSpan segmentDuration);
} }
@@ -10,6 +10,8 @@ namespace StreamShorts.Library.Media;
internal class NAudioService : IAudioService internal class NAudioService : IAudioService
{ {
public Stream ConvertMp3ToWav16(Stream mp3) public Stream ConvertMp3ToWav16(Stream mp3)
{
return UseStream(mp3, stream =>
{ {
using var reader = new Mp3FileReader(mp3); using var reader = new Mp3FileReader(mp3);
var outFormat = new WaveFormat(16000, reader.WaveFormat.Channels); var outFormat = new WaveFormat(16000, reader.WaveFormat.Channels);
@@ -18,18 +20,23 @@ internal class NAudioService : IAudioService
WaveFileWriter.WriteWavFileToStream(waveStream, resampler); WaveFileWriter.WriteWavFileToStream(waveStream, resampler);
waveStream.Position = 0; waveStream.Position = 0;
return waveStream; return waveStream;
});
} }
public int GetNumberOfWavSegments(Stream wavStream, TimeSpan segmentDuration) public int GetNumberOfWavSegments(Stream wavStream, TimeSpan segmentDuration)
{
return UseStream(wavStream, stream =>
{ {
using var waveReader = new WaveFileReader(wavStream); using var waveReader = new WaveFileReader(wavStream);
var totalDuration = waveReader.TotalTime; var totalDuration = waveReader.TotalTime;
var segmentCount = (int)Math.Ceiling(totalDuration.TotalMilliseconds / segmentDuration.TotalMilliseconds); var segmentCount = (int)Math.Ceiling(totalDuration.TotalMilliseconds / segmentDuration.TotalMilliseconds);
wavStream.Position = 0;
return segmentCount; return segmentCount;
});
} }
public Stream GetWavSegment(Stream wavStream, int segmentNumber, TimeSpan segmentDuration) public Stream GetWavSegment(Stream wavStream, int segmentNumber, TimeSpan segmentDuration)
{
return UseStream(wavStream, stream =>
{ {
using var segmentWaveReader = new WaveFileReader(wavStream); using var segmentWaveReader = new WaveFileReader(wavStream);
var segment = segmentWaveReader.ToSampleProvider() var segment = segmentWaveReader.ToSampleProvider()
@@ -39,7 +46,42 @@ internal class NAudioService : IAudioService
var segmentStream = new MemoryStream(); var segmentStream = new MemoryStream();
WaveFileWriter.WriteWavFileToStream(segmentStream, segmentProvider); WaveFileWriter.WriteWavFileToStream(segmentStream, segmentProvider);
segmentStream.Position = 0; segmentStream.Position = 0;
wavStream.Position = 0;
return segmentStream; return segmentStream;
});
}
private static T UseStream<T>(Stream stream, Func<Stream, T> action)
{
ValidateStream(stream);
var originalPosition = stream.Position;
try
{
stream.Position = 0;
return action(stream);
}
finally
{
stream.Position = originalPosition;
}
}
private static void ValidateStream(Stream stream)
{
if (stream == null)
{
throw new ArgumentNullException(nameof(stream), $"{nameof(stream)} cannot be null");
}
if (stream.CanRead is false)
{
throw new ArgumentException($"{nameof(stream)} must be readable", nameof(stream));
}
if (stream.CanSeek is false)
{
throw new ArgumentException($"{nameof(stream)} must be seekable", nameof(stream));
}
} }
} }