feat: refactor audio extraction to reusable class

This commit is contained in:
Stevan Freeborn
2025-07-24 11:22:15 -05:00
parent 310f56fb84
commit 0a661d6970
14 changed files with 354 additions and 181 deletions
@@ -0,0 +1,63 @@
using FFMpegCore;
using FFMpegCore.Pipes;
using Channel = FFMpegCore.Enums.Channel;
namespace StreamShorts.Library.Media.Audio;
/// <inheritdoc/>
public class AudioExtractor : IAudioExtractor
{
public async Task<Stream> ExtractMp3FromMp4Async(Stream video)
{
if (video is null)
{
throw new ArgumentNullException(nameof(video), "Video stream cannot be null");
}
if (video.CanRead is false)
{
throw new ArgumentException("Video stream must be readable", nameof(video));
}
if (video.CanSeek is false)
{
throw new ArgumentException("Video stream must be seekable", nameof(video));
}
var originalPosition = video.Position;
try
{
using var mp3Stream = new MemoryStream();
using var mp4Stream = new MemoryStream();
await video.CopyToAsync(mp4Stream).ConfigureAwait(false);
mp4Stream.Position = 0;
var wasExtracted = await FFMpegArguments
.FromPipeInput(new StreamPipeSource(mp4Stream))
.OutputToPipe(
new StreamPipeSink(mp3Stream),
o => o.DisableChannel(Channel.Video).ForceFormat("mp3")
)
.ProcessAsynchronously()
.ConfigureAwait(false);
if (wasExtracted is false)
{
throw new FailedAudioExtractionException("Failed to extract audio from the video stream.");
}
return mp3Stream;
}
catch (Exception e) when (e is not FailedAudioExtractionException)
{
throw new FailedAudioExtractionException("Failed to extract audio", e);
}
finally
{
video.Position = originalPosition;
}
}
}
@@ -0,0 +1,16 @@
namespace StreamShorts.Library.Media.Audio;
public class FailedAudioExtractionException : Exception
{
public FailedAudioExtractionException() : base()
{
}
public FailedAudioExtractionException(string message) : base(message)
{
}
public FailedAudioExtractionException(string message, Exception innerException) : base(message, innerException)
{
}
}
@@ -0,0 +1,19 @@
namespace StreamShorts.Library.Media.Audio;
/// <summary>
/// Represents an interface for extracting audio from video streams.
/// </summary>
public interface IAudioExtractor
{
/// <summary>
/// Extracts MP3 audio from an MP4 video stream.
/// </summary>
/// <param name="video">The input video stream.</param>
/// <returns>A stream containing the extracted MP3 audio.</returns>
/// <exception cref="ArgumentNullException">Thrown when the video stream is null.</exception>
/// <exception cref="ArgumentException">Thrown when the video stream is not readable.</exception>
/// <exception cref="ArgumentException">Thrown when the video stream is not seekable.</exception>
/// <exception cref="FailedAudioExtractionException">Thrown when the audio extraction fails.</exception>
/// <remarks>The method will preserve the passed video stream's data and position.</remarks>
Task<Stream> ExtractMp3FromMp4Async(Stream video);
}
@@ -1,3 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="FFMpegCore" />
</ItemGroup>
</Project>