feat: enable processing of large video files over 2GB
Update audio extraction to use file-based streaming instead of in-memory buffers to support processing video files larger than 2GB. **Changes:** - Modified `IAudioExtractor.ExtractMp3FromMp4Async()` to accept an output audio stream parameter instead of creating a MemoryStream internally - Updated `AudioExtractor` to write directly to the provided audio stream, eliminating the need to load entire video into memory - Enhanced `FFMpegService` to detect FileStream inputs and use direct file-to-file processing when possible, falling back to pipe-based streaming for other stream types - Refactored `DefaultCommand` to create output directory and audio file stream before extraction, enabling direct file-based audio extraction - Updated all unit and integration tests to accommodate the new audio stream parameter This change prevents `OutOfMemoryException` errors when processing large video files by streaming data directly to disk rather than buffering in memory. Additionally, it removes the possibility of overflowing the 2GB size limit that `MemoryStream` has, which would result in `IOException` errors when trying to write or copy to it.
This commit is contained in:
@@ -69,13 +69,28 @@ internal sealed class DefaultCommand(
|
||||
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings)
|
||||
{
|
||||
_console.MarkupLine($"[blue]Processing stream:[/] {settings.Stream.EscapeMarkup()}");
|
||||
var videoStream = _fileSystem.File.OpenRead(settings.Stream);
|
||||
var videoStream = (FileStream)_fileSystem.File.OpenRead(settings.Stream);
|
||||
|
||||
Stream? audioStream = null;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var inputFileName = _fileSystem.Path.GetFileNameWithoutExtension(settings.Stream);
|
||||
var baseDirectory = ValidateAndGetBaseOutputDirectory();
|
||||
var artifactsOutputDirectory = $"{now:yyyy_MM_dd_HH_mm_ss}_{inputFileName}";
|
||||
var outputDirectoryPath = _fileSystem.Path.Combine(baseDirectory, artifactsOutputDirectory);
|
||||
|
||||
_fileSystem.Directory.CreateDirectory(outputDirectoryPath);
|
||||
|
||||
using var audioStream = new FileStream(
|
||||
_fileSystem.Path.Combine(outputDirectoryPath, "audio.mp3"),
|
||||
FileMode.Create,
|
||||
FileAccess.ReadWrite,
|
||||
FileShare.ReadWrite,
|
||||
4096,
|
||||
FileOptions.Asynchronous
|
||||
);
|
||||
|
||||
await _console.Status()
|
||||
.Spinner(Spinner.Known.Dots)
|
||||
.StartAsync("Extracting audio...", async _ => audioStream = await _audioExtractor.ExtractMp3FromMp4Async(videoStream));
|
||||
.StartAsync("Extracting audio...", async _ => await _audioExtractor.ExtractMp3FromMp4Async(videoStream, audioStream));
|
||||
|
||||
if (audioStream is null)
|
||||
{
|
||||
@@ -101,14 +116,6 @@ internal sealed class DefaultCommand(
|
||||
|
||||
_console.MarkupLine($"[blue]Transcription completed[/] [green]successfully![/]");
|
||||
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var inputFileName = _fileSystem.Path.GetFileNameWithoutExtension(settings.Stream);
|
||||
var baseDirectory = ValidateAndGetBaseOutputDirectory();
|
||||
var artifactsOutputDirectory = $"{now:yyyy_MM_dd_HH_mm_ss}_{inputFileName}";
|
||||
var outputDirectoryPath = _fileSystem.Path.Combine(baseDirectory, artifactsOutputDirectory);
|
||||
|
||||
_fileSystem.Directory.CreateDirectory(outputDirectoryPath);
|
||||
|
||||
await _fileSystem.File.WriteAllTextAsync(
|
||||
_fileSystem.Path.Combine(outputDirectoryPath, "transcription.txt"),
|
||||
string.Join(Environment.NewLine, transcriptionSegments)
|
||||
|
||||
Reference in New Issue
Block a user