diff --git a/src/StreamShorts.Console/Commands/DefaultCommand.cs b/src/StreamShorts.Console/Commands/DefaultCommand.cs index 6fe0ea1..3780956 100644 --- a/src/StreamShorts.Console/Commands/DefaultCommand.cs +++ b/src/StreamShorts.Console/Commands/DefaultCommand.cs @@ -52,7 +52,7 @@ internal class DefaultCommand( .Spinner(Spinner.Known.Dots) .StartAsync("Extracting audio...", async ctx => { - audioStream = await _audioExtractor.ExtractMp3FromMp4Async(videoStream).ConfigureAwait(false); + audioStream = await _audioExtractor.ExtractMp3FromMp4Async(videoStream); }); if (audioStream is null) diff --git a/src/StreamShorts.Library/Media/IAudioService.cs b/src/StreamShorts.Library/Media/IAudioService.cs index fee0713..8711fe7 100644 --- a/src/StreamShorts.Library/Media/IAudioService.cs +++ b/src/StreamShorts.Library/Media/IAudioService.cs @@ -11,6 +11,9 @@ internal interface IAudioService /// /// The input MP3 stream. /// The output WAV stream. + /// Thrown when the MP3 stream is null. + /// Thrown when the MP3 stream is not readable or seekable. + /// The method will preserve the passed MP3 stream's data and position. @@ -19,6 +22,9 @@ internal interface IAudioService /// param name="wavStream">The input WAV stream. /// The duration of each segment. /// The number of segments. + /// Thrown when the WAV stream is null. + /// Thrown when the WAV stream is not readable or seekable. + /// The method will preserve the passed WAV stream's data and position. int GetNumberOfWavSegments(Stream wavStream, TimeSpan segmentDuration); /// @@ -28,5 +34,8 @@ internal interface IAudioService /// The segment number to retrieve. /// The duration of each segment. /// The segment stream. + /// Thrown when the WAV stream is null. + /// Thrown when the WAV stream is not readable or seekable. + /// The method will preserve the passed WAV stream's data and position. Stream GetWavSegment(Stream wavStream, int segmentNumber, TimeSpan segmentDuration); } \ No newline at end of file diff --git a/src/StreamShorts.Library/Media/NAudioService.cs b/src/StreamShorts.Library/Media/NAudioService.cs index e715c5e..431ef32 100644 --- a/src/StreamShorts.Library/Media/NAudioService.cs +++ b/src/StreamShorts.Library/Media/NAudioService.cs @@ -11,35 +11,77 @@ internal class NAudioService : IAudioService { public Stream ConvertMp3ToWav16(Stream mp3) { - using var reader = new Mp3FileReader(mp3); - var outFormat = new WaveFormat(16000, reader.WaveFormat.Channels); - using var resampler = new MediaFoundationResampler(reader, outFormat); - var waveStream = new MemoryStream(); - WaveFileWriter.WriteWavFileToStream(waveStream, resampler); - waveStream.Position = 0; - return waveStream; + return UseStream(mp3, stream => + { + using var reader = new Mp3FileReader(mp3); + var outFormat = new WaveFormat(16000, reader.WaveFormat.Channels); + using var resampler = new MediaFoundationResampler(reader, outFormat); + var waveStream = new MemoryStream(); + WaveFileWriter.WriteWavFileToStream(waveStream, resampler); + waveStream.Position = 0; + return waveStream; + }); } public int GetNumberOfWavSegments(Stream wavStream, TimeSpan segmentDuration) { - using var waveReader = new WaveFileReader(wavStream); - var totalDuration = waveReader.TotalTime; - var segmentCount = (int)Math.Ceiling(totalDuration.TotalMilliseconds / segmentDuration.TotalMilliseconds); - wavStream.Position = 0; - return segmentCount; + return UseStream(wavStream, stream => + { + using var waveReader = new WaveFileReader(wavStream); + var totalDuration = waveReader.TotalTime; + var segmentCount = (int)Math.Ceiling(totalDuration.TotalMilliseconds / segmentDuration.TotalMilliseconds); + return segmentCount; + }); } public Stream GetWavSegment(Stream wavStream, int segmentNumber, TimeSpan segmentDuration) { - using var segmentWaveReader = new WaveFileReader(wavStream); - var segment = segmentWaveReader.ToSampleProvider() - .Skip(segmentNumber * segmentDuration) - .Take(segmentDuration); - var segmentProvider = segment.ToWaveProvider16(); - var segmentStream = new MemoryStream(); - WaveFileWriter.WriteWavFileToStream(segmentStream, segmentProvider); - segmentStream.Position = 0; - wavStream.Position = 0; - return segmentStream; + return UseStream(wavStream, stream => + { + using var segmentWaveReader = new WaveFileReader(wavStream); + var segment = segmentWaveReader.ToSampleProvider() + .Skip(segmentNumber * segmentDuration) + .Take(segmentDuration); + var segmentProvider = segment.ToWaveProvider16(); + var segmentStream = new MemoryStream(); + WaveFileWriter.WriteWavFileToStream(segmentStream, segmentProvider); + segmentStream.Position = 0; + return segmentStream; + }); + } + + private static T UseStream(Stream stream, Func 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)); + } } } \ No newline at end of file