feat: refactor audio extraction to reusable class
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user