From 2acb32392c3437a97f1002d9c1fcef6bec143fa5 Mon Sep 17 00:00:00 2001 From: Hai Vo Date: Wed, 5 Aug 2026 11:36:37 +0700 Subject: [PATCH 1/4] docs: remove internal platform name from CLAUDE.md Drop the two prose references to the proprietary "DSP" platform this library was originally extracted from -- not relevant to a public repo and not part of any code identifier, so this is a docs-only change. --- CLAUDE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 75d957b..551ba2f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## What is EggEncoder -EggEncoder is a .NET audio encoding/decoding toolkit built around a single `IMediaEncoder` abstraction (`Probe`, `ConvertFile`, `CutFile`) implemented entirely in-process by `NativeEncoder` — pure .NET + P/Invoke codec bindings, no external process, no ffmpeg dependency. Originally extracted from the DSP music distribution platform's `Dsp.Core.Encoder` project (which also had an ffmpeg-shell-out engine; that engine was dropped when EggEncoder became native-only). +EggEncoder is a .NET audio encoding/decoding toolkit built around a single `IMediaEncoder` abstraction (`Probe`, `ConvertFile`, `CutFile`) implemented entirely in-process by `NativeEncoder` — pure .NET + P/Invoke codec bindings, no external process, no ffmpeg dependency. Originally extracted from a music distribution platform's internal encoder project (which also had an ffmpeg-shell-out engine; that engine was dropped when EggEncoder became native-only). ## Commands @@ -39,7 +39,7 @@ dotnet test src/EggEncoder.UnitTests/EggEncoder.UnitTests.csproj --configuration ### Supporting infrastructure -- **`Transform/`** — `BitReader`, `BitWriter`, `HuffmanTable`, `Mdct` — low-level bitstream and DSP primitives shared by the AAC/WMA codecs +- **`Transform/`** — `BitReader`, `BitWriter`, `HuffmanTable`, `Mdct` — low-level bitstream and signal-processing primitives shared by the AAC/WMA codecs - **`Native/`** — `FlacNative.cs`/`Mp3Native.cs` (`[LibraryImport]` P/Invoke declarations), `NativeLibraryLoader.cs` (a `[ModuleInitializer]` that registers a custom `DllImportResolver` so `libFLAC`/`libmp3lame` load from `Native/win-x64/` relative to `AppContext.BaseDirectory` regardless of the consuming app's working directory) - **`Waveform/WaveformCalculator.cs`** — streaming peak-window calculator fed blocks during decode, used by every codec's probe path to produce `ProbeResult.WaveformResult` - **`Results/ProbeResult.cs`** — the public `ProbeResult` DTO returned by every `Probe` call From de8a163830c652543fc67df1fc8af14fcea9b8f1 Mon Sep 17 00:00:00 2001 From: Hai Vo Date: Wed, 5 Aug 2026 11:36:47 +0700 Subject: [PATCH 2/4] fix: copy native DLLs to consuming projects' output on NuGet restore The packed nuspec's entries for libmp3lame.dll/libFLAC.dll were missing copyToOutput="true". Without it, NuGet's default is false -- the DLLs land in the package and the global-packages cache on restore, but are never copied into a project's own output directory unless that project references EggEncoder via ProjectReference (which uses a completely different, MSBuild-native copy mechanism that happens to work in-repo for EggEncoder.UnitTests/EggEncoder.Benchmarks and masked this). Any real downstream consumer using got a DllNotFoundException the moment it P/Invoked into libFLAC/libmp3lame, since NativeLibraryLoader.Resolve computes a path that NuGet never actually populated. Verified by packing locally, restoring into a fresh console project against a clean NuGet cache, and confirming Native/win-x64/*.dll now appear in its build output. --- src/EggEncoder/EggEncoder.csproj | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/EggEncoder/EggEncoder.csproj b/src/EggEncoder/EggEncoder.csproj index 65c899e..9274711 100644 --- a/src/EggEncoder/EggEncoder.csproj +++ b/src/EggEncoder/EggEncoder.csproj @@ -39,10 +39,18 @@ - + + PreserveNewest - + PreserveNewest From 5105ec81d47f77348535b17f16c8e92ef6e8a1fe Mon Sep 17 00:00:00 2001 From: Hai Vo Date: Wed, 5 Aug 2026 11:37:04 +0700 Subject: [PATCH 3/4] feat!: replace ProbeResult's flat fields with structured Format/Stream detail ProbeResult previously exposed a thin set of flat fields (DurationInSeconds, BitsPerSample, BitRate, SampleRate, Height, Width) plus an opaque `Result` JSON string whose shape varied per codec. Replace both with two typed, always-populated objects modeled loosely on ffprobe's format/stream split (without chasing ffprobe's exact schema or fabricating fields ffprobe has that we can't honestly compute, like probe_score or disposition flags): - ProbeFormatInfo: FormatName, FormatLongName, SizeBytes, DurationSeconds, StreamCount - ProbeStreamInfo: CodecType, CodecName, CodecLongName, SampleRate, Channels, ChannelLayout, BitsPerSample, BitRate, IsVariableBitRate, DurationInSamples, TimeBase, Width, Height Each fact now lives in exactly one place -- e.g. BitRate is on Stream only (not duplicated onto Format), duration is on Format only (not also repeated on Stream), since every file this library probes has exactly one stream and a per-stream copy would just echo the format-level value. Also adds fractional-second duration (Mp3ProbeResult.DurationSeconds, MovProbeResult.DurationSeconds) and exposes WavReader.IsFloatFormat, both needed to populate the new codec_name/duration fields accurately. BREAKING CHANGE: ProbeResult.DurationInSeconds/BitsPerSample/BitRate/ SampleRate/Height/Width/Result no longer exist. Use Format/Stream instead. --- src/EggEncoder.UnitTests/NativeEncoderTest.cs | 78 +++++-- src/EggEncoder/Codecs/Mov/MovProbe.cs | 7 +- src/EggEncoder/Codecs/Mp3/Mp3Probe.cs | 15 +- src/EggEncoder/Codecs/Wav/WavReader.cs | 2 + src/EggEncoder/NativeEncoder.cs | 216 ++++++++++++++---- src/EggEncoder/Results/ProbeResult.cs | 51 ++++- 6 files changed, 292 insertions(+), 77 deletions(-) diff --git a/src/EggEncoder.UnitTests/NativeEncoderTest.cs b/src/EggEncoder.UnitTests/NativeEncoderTest.cs index 8139f6a..3282a9f 100644 --- a/src/EggEncoder.UnitTests/NativeEncoderTest.cs +++ b/src/EggEncoder.UnitTests/NativeEncoderTest.cs @@ -12,6 +12,8 @@ public class NativeEncoderTest private static readonly string _wavFixturePath = Path.GetFullPath("Codecs/Flac/sample.wav"); private static readonly string _movFixturePath = Path.GetFullPath("Codecs/Mov/test.mov"); private static readonly string _mp4FixturePath = Path.GetFullPath("Codecs/Mov/test.mp4"); + private static readonly string _aacFixturePath = Path.GetFullPath("Codecs/Aac/tone_mono.aac"); + private static readonly string _wmaFixturePath = Path.GetFullPath("Codecs/Wma/tone_mono.wma"); private readonly Mock> _logger = new(); @@ -27,10 +29,20 @@ public async Task Probe_WavFile_Should_Return_Correct_Metadata_And_Waveform() { var probeResult = await _nativeEncoder.Probe(_wavFixturePath); - probeResult.SampleRate.Should().Be(44100); - probeResult.BitsPerSample.Should().Be(16); - probeResult.DurationInSeconds.Should().Be(2); AssertNonEmptyWaveform(probeResult.WaveformResult); + + probeResult.Format.FormatName.Should().Be("wav"); + probeResult.Format.SizeBytes.Should().Be(new FileInfo(_wavFixturePath).Length); + probeResult.Format.DurationSeconds.Should().BeApproximately(2, 0.1); + probeResult.Format.StreamCount.Should().Be(1); + + probeResult.Stream.CodecType.Should().Be("audio"); + probeResult.Stream.CodecName.Should().Be("pcm_s16le"); + probeResult.Stream.SampleRate.Should().Be(44100); + probeResult.Stream.Channels.Should().Be(2); + probeResult.Stream.ChannelLayout.Should().Be("stereo"); + probeResult.Stream.BitsPerSample.Should().Be(16); + probeResult.Stream.TimeBase.Should().Be("1/44100"); } [Fact] @@ -45,10 +57,14 @@ public async Task Probe_FlacFile_Should_Return_Correct_Metadata_And_Waveform() var probeResult = await _nativeEncoder.Probe(flacPath); - probeResult.SampleRate.Should().Be(44100); - probeResult.BitsPerSample.Should().Be(16); - probeResult.DurationInSeconds.Should().Be(2); AssertNonEmptyWaveform(probeResult.WaveformResult); + + probeResult.Format.FormatName.Should().Be("flac"); + probeResult.Format.DurationSeconds.Should().BeApproximately(2, 0.1); + probeResult.Stream.CodecName.Should().Be("flac"); + probeResult.Stream.SampleRate.Should().Be(44100); + probeResult.Stream.BitsPerSample.Should().Be(16); + probeResult.Stream.ChannelLayout.Should().Be("stereo"); } finally { @@ -68,10 +84,14 @@ public async Task Probe_Mp3File_Should_Return_Correct_Metadata_And_Waveform() var probeResult = await _nativeEncoder.Probe(mp3Path); - probeResult.SampleRate.Should().Be(44100); - probeResult.BitsPerSample.Should().Be(16); - probeResult.DurationInSeconds.Should().Be(2); AssertNonEmptyWaveform(probeResult.WaveformResult); + + probeResult.Format.FormatName.Should().Be("mp3"); + probeResult.Format.DurationSeconds.Should().BeApproximately(2, 0.1); + probeResult.Stream.CodecName.Should().Be("mp3"); + probeResult.Stream.SampleRate.Should().Be(44100); + probeResult.Stream.BitsPerSample.Should().Be(16); + probeResult.Stream.ChannelLayout.Should().Be("stereo"); } finally { @@ -79,15 +99,41 @@ public async Task Probe_Mp3File_Should_Return_Correct_Metadata_And_Waveform() } } + [Fact] + public async Task Probe_AacFile_Should_Return_Correct_Metadata() + { + var probeResult = await _nativeEncoder.Probe(_aacFixturePath); + + probeResult.Format.FormatName.Should().Be("aac"); + probeResult.Stream.SampleRate.Should().BePositive(); + probeResult.Stream.CodecName.Should().Be("aac"); + probeResult.Stream.CodecType.Should().Be("audio"); + probeResult.Stream.ChannelLayout.Should().Be("mono"); + } + + [Fact] + public async Task Probe_WmaFile_Should_Return_Correct_Metadata() + { + var probeResult = await _nativeEncoder.Probe(_wmaFixturePath); + + probeResult.Format.FormatName.Should().Be("asf"); + probeResult.Stream.SampleRate.Should().BePositive(); + probeResult.Stream.CodecName.Should().Be("wmav2"); + probeResult.Stream.CodecType.Should().Be("audio"); + } + [Fact] public async Task Probe_MovFile_Should_Return_Correct_VideoMetadata() { var probeResult = await _nativeEncoder.Probe(_movFixturePath); - probeResult.DurationInSeconds.Should().Be(5); - probeResult.Width.Should().Be(640); - probeResult.Height.Should().Be(360); probeResult.WaveformResult.Should().BeNull(); + + probeResult.Format.FormatName.Should().Be("mov"); + probeResult.Format.DurationSeconds.Should().BeApproximately(5, 0.1); + probeResult.Stream.CodecType.Should().Be("video"); + probeResult.Stream.Width.Should().Be(640); + probeResult.Stream.Height.Should().Be(360); } [Fact] @@ -95,10 +141,12 @@ public async Task Probe_Mp4File_Should_Return_Correct_VideoMetadata() { var probeResult = await _nativeEncoder.Probe(_mp4FixturePath); - probeResult.DurationInSeconds.Should().Be(5); - probeResult.Width.Should().Be(640); - probeResult.Height.Should().Be(360); probeResult.WaveformResult.Should().BeNull(); + probeResult.Stream.Width.Should().Be(640); + probeResult.Stream.Height.Should().Be(360); + + probeResult.Format.FormatName.Should().Be("mp4"); + probeResult.Format.DurationSeconds.Should().BeApproximately(5, 0.1); } [Fact] diff --git a/src/EggEncoder/Codecs/Mov/MovProbe.cs b/src/EggEncoder/Codecs/Mov/MovProbe.cs index 90045b3..2e5cca9 100644 --- a/src/EggEncoder/Codecs/Mov/MovProbe.cs +++ b/src/EggEncoder/Codecs/Mov/MovProbe.cs @@ -46,9 +46,12 @@ public static MovProbeResult Probe(string filePath) break; } + var durationSeconds = timescale > 0 ? duration / (double)timescale : 0; + return new MovProbeResult { - DurationInSeconds = timescale > 0 ? (int)(duration / timescale) : 0, + DurationInSeconds = (int)durationSeconds, + DurationSeconds = durationSeconds, Width = width, Height = height, CodecFourCc = codecFourCc @@ -162,6 +165,8 @@ public class MovProbeResult { public required int DurationInSeconds { get; init; } + public required double DurationSeconds { get; init; } + public required int? Width { get; init; } public required int? Height { get; init; } diff --git a/src/EggEncoder/Codecs/Mp3/Mp3Probe.cs b/src/EggEncoder/Codecs/Mp3/Mp3Probe.cs index 3d1612d..152f71e 100644 --- a/src/EggEncoder/Codecs/Mp3/Mp3Probe.cs +++ b/src/EggEncoder/Codecs/Mp3/Mp3Probe.cs @@ -26,7 +26,7 @@ public static Mp3ProbeResult Probe(string filePath) var trailingTagSize = HasId3V1Tag(stream) ? 128 : 0; var audioDataLength = stream.Length - frame.Offset - trailingTagSize; - int durationInSeconds; + double durationSeconds; int bitRateKbps; bool isVariableBitRate; @@ -34,22 +34,23 @@ public static Mp3ProbeResult Probe(string filePath) { var samplesPerFrame = frame.Header.IsMpeg1 ? 1152 : 576; var totalSamples = (long)knownVbrInfo.FrameCount * samplesPerFrame; - durationInSeconds = (int)(totalSamples / frame.Header.SampleRate); - bitRateKbps = durationInSeconds > 0 && knownVbrInfo.ByteCount > 0 - ? (int)(knownVbrInfo.ByteCount * 8 / 1000 / durationInSeconds) + durationSeconds = (double)totalSamples / frame.Header.SampleRate; + bitRateKbps = durationSeconds > 0 && knownVbrInfo.ByteCount > 0 + ? (int)(knownVbrInfo.ByteCount * 8 / 1000 / durationSeconds) : frame.Header.BitRateKbps; isVariableBitRate = knownVbrInfo.IsVbr; } else { - durationInSeconds = (int)(audioDataLength * 8 / 1000 / frame.Header.BitRateKbps); + durationSeconds = (double)audioDataLength * 8 / (frame.Header.BitRateKbps * 1000.0); bitRateKbps = frame.Header.BitRateKbps; isVariableBitRate = false; } return new Mp3ProbeResult { - DurationInSeconds = durationInSeconds, + DurationInSeconds = (int)durationSeconds, + DurationSeconds = durationSeconds, SampleRate = frame.Header.SampleRate, Channels = frame.Header.Channels, BitRate = bitRateKbps * 1000, @@ -203,6 +204,8 @@ public class Mp3ProbeResult { public required int DurationInSeconds { get; init; } + public required double DurationSeconds { get; init; } + public required int SampleRate { get; init; } public required int Channels { get; init; } diff --git a/src/EggEncoder/Codecs/Wav/WavReader.cs b/src/EggEncoder/Codecs/Wav/WavReader.cs index def4dc6..9039cf2 100644 --- a/src/EggEncoder/Codecs/Wav/WavReader.cs +++ b/src/EggEncoder/Codecs/Wav/WavReader.cs @@ -37,6 +37,8 @@ private WavReader(FileStream stream, int channels, int sampleRate, int bitsPerSa public long TotalSamples { get; } + public bool IsFloatFormat => _isFloatFormat; + public static WavReader Open(string filePath) { var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read); diff --git a/src/EggEncoder/NativeEncoder.cs b/src/EggEncoder/NativeEncoder.cs index 03d4c77..fc97be0 100644 --- a/src/EggEncoder/NativeEncoder.cs +++ b/src/EggEncoder/NativeEncoder.cs @@ -39,7 +39,7 @@ public Task Probe(string filePath) _ => throw new NotSupportedException($"Probing '{extension}' files is not supported by the native audio encoder") }; - _logger.LogInformation($"Probe '{filePath}', result: '{result.Result}'"); + _logger.LogInformation($"Probe '{filePath}', format: '{result.Format.FormatName}', codec: '{result.Stream.CodecName}', duration: {result.Format.DurationSeconds}s"); return Task.FromResult(result); } @@ -108,17 +108,33 @@ private static ProbeResult ProbeWav(string filePath) waveformCalculator.AddBlock(new ReadOnlySpan(buffer, 0, framesRead * wavReader.Channels)); } - var durationInSeconds = wavReader.SampleRate > 0 ? (int)(wavReader.TotalSamples / wavReader.SampleRate) : 0; + var durationSeconds = wavReader.SampleRate > 0 ? (double)wavReader.TotalSamples / wavReader.SampleRate : 0; + var bitRate = wavReader.SampleRate * wavReader.BitsPerSample * wavReader.Channels; + var (codecName, codecLongName) = DescribeWavCodec(wavReader.BitsPerSample, wavReader.IsFloatFormat); return new ProbeResult { - DurationInSeconds = durationInSeconds, - BitsPerSample = wavReader.BitsPerSample, - BitRate = wavReader.SampleRate * wavReader.BitsPerSample * wavReader.Channels, - SampleRate = wavReader.SampleRate, - Height = null, - Width = null, - Result = JsonSerializer.Serialize(new { wavReader.Channels, wavReader.SampleRate, wavReader.BitsPerSample }), + Format = new ProbeFormatInfo + { + FormatName = "wav", + FormatLongName = "WAV / WAVE (Waveform Audio)", + SizeBytes = GetFileSize(filePath), + DurationSeconds = durationSeconds, + StreamCount = 1 + }, + Stream = new ProbeStreamInfo + { + CodecType = "audio", + CodecName = codecName, + CodecLongName = codecLongName, + SampleRate = wavReader.SampleRate, + Channels = wavReader.Channels, + ChannelLayout = DescribeChannelLayout(wavReader.Channels), + BitsPerSample = wavReader.BitsPerSample, + BitRate = bitRate, + DurationInSamples = wavReader.TotalSamples, + TimeBase = wavReader.SampleRate > 0 ? $"1/{wavReader.SampleRate}" : null + }, WaveformResult = JsonSerializer.Serialize(waveformCalculator.GetNormalizedWindows()) }; } @@ -133,17 +149,32 @@ private static ProbeResult ProbeFlac(string filePath) waveformCalculator.AddBlock(block); }); - var durationInSeconds = streamInfo.SampleRate > 0 ? (int)(streamInfo.TotalSamples / streamInfo.SampleRate) : 0; + var durationSeconds = streamInfo.SampleRate > 0 ? (double)streamInfo.TotalSamples / streamInfo.SampleRate : 0; + var bitRate = streamInfo.SampleRate * streamInfo.BitsPerSample * streamInfo.Channels; return new ProbeResult { - DurationInSeconds = durationInSeconds, - BitsPerSample = streamInfo.BitsPerSample, - BitRate = streamInfo.SampleRate * streamInfo.BitsPerSample * streamInfo.Channels, - SampleRate = streamInfo.SampleRate, - Height = null, - Width = null, - Result = JsonSerializer.Serialize(new { streamInfo.Channels, streamInfo.SampleRate, streamInfo.BitsPerSample }), + Format = new ProbeFormatInfo + { + FormatName = "flac", + FormatLongName = "FLAC (Free Lossless Audio Codec)", + SizeBytes = GetFileSize(filePath), + DurationSeconds = durationSeconds, + StreamCount = 1 + }, + Stream = new ProbeStreamInfo + { + CodecType = "audio", + CodecName = "flac", + CodecLongName = "FLAC (Free Lossless Audio Codec)", + SampleRate = streamInfo.SampleRate, + Channels = streamInfo.Channels, + ChannelLayout = DescribeChannelLayout(streamInfo.Channels), + BitsPerSample = streamInfo.BitsPerSample, + BitRate = bitRate, + DurationInSamples = streamInfo.TotalSamples, + TimeBase = streamInfo.SampleRate > 0 ? $"1/{streamInfo.SampleRate}" : null + }, WaveformResult = JsonSerializer.Serialize(waveformCalculator?.GetNormalizedWindows() ?? []) }; } @@ -161,13 +192,27 @@ private static ProbeResult ProbeMp3(string filePath) return new ProbeResult { - DurationInSeconds = mp3ProbeResult.DurationInSeconds, - BitsPerSample = 16, - BitRate = mp3ProbeResult.BitRate, - SampleRate = mp3ProbeResult.SampleRate, - Height = null, - Width = null, - Result = JsonSerializer.Serialize(mp3ProbeResult), + Format = new ProbeFormatInfo + { + FormatName = "mp3", + FormatLongName = "MP3 (MPEG audio layer 3)", + SizeBytes = GetFileSize(filePath), + DurationSeconds = mp3ProbeResult.DurationSeconds, + StreamCount = 1 + }, + Stream = new ProbeStreamInfo + { + CodecType = "audio", + CodecName = "mp3", + CodecLongName = "MP3 (MPEG audio layer 3)", + SampleRate = mp3ProbeResult.SampleRate, + Channels = mp3ProbeResult.Channels, + ChannelLayout = DescribeChannelLayout(mp3ProbeResult.Channels), + BitsPerSample = 16, + BitRate = mp3ProbeResult.BitRate, + IsVariableBitRate = mp3ProbeResult.IsVariableBitRate, + TimeBase = mp3ProbeResult.SampleRate > 0 ? $"1/{mp3ProbeResult.SampleRate}" : null + }, WaveformResult = JsonSerializer.Serialize(waveformCalculator?.GetNormalizedWindows() ?? []) }; } @@ -182,17 +227,32 @@ private static ProbeResult ProbeAac(string filePath) waveformCalculator.AddBlock(block); }); - var durationInSeconds = streamInfo.SampleRate > 0 ? (int)(streamInfo.TotalSamples / streamInfo.SampleRate) : 0; + var durationSeconds = streamInfo.SampleRate > 0 ? (double)streamInfo.TotalSamples / streamInfo.SampleRate : 0; + var bitRate = streamInfo.SampleRate * streamInfo.BitsPerSample * streamInfo.Channels; return new ProbeResult { - DurationInSeconds = durationInSeconds, - BitsPerSample = streamInfo.BitsPerSample, - BitRate = streamInfo.SampleRate * streamInfo.BitsPerSample * streamInfo.Channels, - SampleRate = streamInfo.SampleRate, - Height = null, - Width = null, - Result = JsonSerializer.Serialize(new { streamInfo.Channels, streamInfo.SampleRate, streamInfo.BitsPerSample }), + Format = new ProbeFormatInfo + { + FormatName = "aac", + FormatLongName = "ADTS AAC (Advanced Audio Coding)", + SizeBytes = GetFileSize(filePath), + DurationSeconds = durationSeconds, + StreamCount = 1 + }, + Stream = new ProbeStreamInfo + { + CodecType = "audio", + CodecName = "aac", + CodecLongName = "AAC-LC (Advanced Audio Coding, Low Complexity profile)", + SampleRate = streamInfo.SampleRate, + Channels = streamInfo.Channels, + ChannelLayout = DescribeChannelLayout(streamInfo.Channels), + BitsPerSample = streamInfo.BitsPerSample, + BitRate = bitRate, + DurationInSamples = streamInfo.TotalSamples, + TimeBase = streamInfo.SampleRate > 0 ? $"1/{streamInfo.SampleRate}" : null + }, WaveformResult = JsonSerializer.Serialize(waveformCalculator?.GetNormalizedWindows() ?? []) }; } @@ -207,17 +267,32 @@ private static ProbeResult ProbeWma(string filePath) waveformCalculator.AddBlock(block); }); - var durationInSeconds = streamInfo.SampleRate > 0 ? (int)(streamInfo.TotalSamples / streamInfo.SampleRate) : 0; + var durationSeconds = streamInfo.SampleRate > 0 ? (double)streamInfo.TotalSamples / streamInfo.SampleRate : 0; + var bitRate = streamInfo.SampleRate * streamInfo.BitsPerSample * streamInfo.Channels; return new ProbeResult { - DurationInSeconds = durationInSeconds, - BitsPerSample = streamInfo.BitsPerSample, - BitRate = streamInfo.SampleRate * streamInfo.BitsPerSample * streamInfo.Channels, - SampleRate = streamInfo.SampleRate, - Height = null, - Width = null, - Result = JsonSerializer.Serialize(new { streamInfo.Channels, streamInfo.SampleRate, streamInfo.BitsPerSample }), + Format = new ProbeFormatInfo + { + FormatName = "asf", + FormatLongName = "ASF (Advanced / Active Streaming Format)", + SizeBytes = GetFileSize(filePath), + DurationSeconds = durationSeconds, + StreamCount = 1 + }, + Stream = new ProbeStreamInfo + { + CodecType = "audio", + CodecName = "wmav2", + CodecLongName = "Windows Media Audio 2", + SampleRate = streamInfo.SampleRate, + Channels = streamInfo.Channels, + ChannelLayout = DescribeChannelLayout(streamInfo.Channels), + BitsPerSample = streamInfo.BitsPerSample, + BitRate = bitRate, + DurationInSamples = streamInfo.TotalSamples, + TimeBase = streamInfo.SampleRate > 0 ? $"1/{streamInfo.SampleRate}" : null + }, WaveformResult = JsonSerializer.Serialize(waveformCalculator?.GetNormalizedWindows() ?? []) }; } @@ -225,18 +300,65 @@ private static ProbeResult ProbeWma(string filePath) private static ProbeResult ProbeVideo(string filePath) { var movProbeResult = MovProbe.Probe(filePath); + var extension = Path.GetExtension(filePath).ToLowerInvariant(); + + var (formatName, formatLongName) = extension switch + { + ".mp4" => ("mp4", "MP4 (MPEG-4 Part 14)"), + _ => ("mov", "QuickTime / MOV") + }; return new ProbeResult { - DurationInSeconds = movProbeResult.DurationInSeconds, - BitsPerSample = null, - BitRate = null, - SampleRate = null, - Height = movProbeResult.Height, - Width = movProbeResult.Width, - Result = JsonSerializer.Serialize(movProbeResult), + Format = new ProbeFormatInfo + { + FormatName = formatName, + FormatLongName = formatLongName, + SizeBytes = GetFileSize(filePath), + DurationSeconds = movProbeResult.DurationSeconds, + StreamCount = 1 + }, + Stream = new ProbeStreamInfo + { + CodecType = "video", + CodecName = movProbeResult.CodecFourCc, + Width = movProbeResult.Width, + Height = movProbeResult.Height + }, WaveformResult = null }; } + + private static (string CodecName, string CodecLongName) DescribeWavCodec(int bitsPerSample, bool isFloatFormat) + { + if (isFloatFormat) + { + return ("pcm_f32le", "PCM 32-bit floating-point little-endian"); + } + + return bitsPerSample switch + { + 8 => ("pcm_u8", "PCM unsigned 8-bit"), + 16 => ("pcm_s16le", "PCM signed 16-bit little-endian"), + 24 => ("pcm_s24le", "PCM signed 24-bit little-endian"), + 32 => ("pcm_s32le", "PCM signed 32-bit little-endian"), + _ => ($"pcm_s{bitsPerSample}le", $"PCM signed {bitsPerSample}-bit little-endian") + }; + } + + private static string DescribeChannelLayout(int channels) + { + return channels switch + { + 1 => "mono", + 2 => "stereo", + _ => $"{channels} channels" + }; + } + + private static long GetFileSize(string filePath) + { + return new FileInfo(filePath).Length; + } } } diff --git a/src/EggEncoder/Results/ProbeResult.cs b/src/EggEncoder/Results/ProbeResult.cs index aaccbd9..51c95c6 100644 --- a/src/EggEncoder/Results/ProbeResult.cs +++ b/src/EggEncoder/Results/ProbeResult.cs @@ -2,20 +2,55 @@ namespace EggEncoder.Results { public class ProbeResult { - public required int DurationInSeconds { get; set; } + // Only populated with values this library can genuinely compute -- fields ffprobe + // reports that don't have a real equivalent here (probe_score, disposition flags, + // container timestamps) are intentionally omitted rather than filled with placeholders. + public required ProbeFormatInfo Format { get; set; } - public required int? BitsPerSample { get; set; } + public required ProbeStreamInfo Stream { get; set; } - public required int? BitRate { get; set; } + public string? WaveformResult { get; set; } + } - public required int? SampleRate { get; set; } + public class ProbeFormatInfo + { + public required string FormatName { get; init; } - public required int? Height { get; set; } + public required string FormatLongName { get; init; } - public required int? Width { get; set; } + public required long SizeBytes { get; init; } - public required string? Result { get; set; } + public required double DurationSeconds { get; init; } - public string? WaveformResult { get; set; } + public required int StreamCount { get; init; } + } + + public class ProbeStreamInfo + { + public required string CodecType { get; init; } + + public string? CodecName { get; init; } + + public string? CodecLongName { get; init; } + + public int? SampleRate { get; init; } + + public int? Channels { get; init; } + + public string? ChannelLayout { get; init; } + + public int? BitsPerSample { get; init; } + + public int? BitRate { get; init; } + + public bool? IsVariableBitRate { get; init; } + + public long? DurationInSamples { get; init; } + + public string? TimeBase { get; init; } + + public int? Width { get; init; } + + public int? Height { get; init; } } } From c021f153d11c5eeec27d51d868ad2a2d5d6b5dfc Mon Sep 17 00:00:00 2001 From: Hai Vo Date: Wed, 5 Aug 2026 11:46:01 +0700 Subject: [PATCH 4/4] refactor!: flatten ProbeResult.Format/Stream into a single object The Format/Stream split only earns its keep when a file can have multiple streams (ffprobe's actual use case). This library always probes exactly one stream per file, so the split just added a layer of nesting (probeResult.Stream.SampleRate) without ever resolving real ambiguity. Flatten ProbeFormatInfo + ProbeStreamInfo into ProbeResult directly; drop StreamCount too, since it was always 1 and meaningless once there's no per-stream collection to count. BREAKING CHANGE: ProbeResult.Format.* / ProbeResult.Stream.* no longer exist -- all fields (FormatName, CodecName, SampleRate, etc.) are now directly on ProbeResult. --- src/EggEncoder.UnitTests/NativeEncoderTest.cs | 83 ++++--- src/EggEncoder/NativeEncoder.cs | 204 +++++++----------- src/EggEncoder/Results/ProbeResult.cs | 19 +- 3 files changed, 122 insertions(+), 184 deletions(-) diff --git a/src/EggEncoder.UnitTests/NativeEncoderTest.cs b/src/EggEncoder.UnitTests/NativeEncoderTest.cs index 3282a9f..4b70b05 100644 --- a/src/EggEncoder.UnitTests/NativeEncoderTest.cs +++ b/src/EggEncoder.UnitTests/NativeEncoderTest.cs @@ -31,18 +31,17 @@ public async Task Probe_WavFile_Should_Return_Correct_Metadata_And_Waveform() AssertNonEmptyWaveform(probeResult.WaveformResult); - probeResult.Format.FormatName.Should().Be("wav"); - probeResult.Format.SizeBytes.Should().Be(new FileInfo(_wavFixturePath).Length); - probeResult.Format.DurationSeconds.Should().BeApproximately(2, 0.1); - probeResult.Format.StreamCount.Should().Be(1); - - probeResult.Stream.CodecType.Should().Be("audio"); - probeResult.Stream.CodecName.Should().Be("pcm_s16le"); - probeResult.Stream.SampleRate.Should().Be(44100); - probeResult.Stream.Channels.Should().Be(2); - probeResult.Stream.ChannelLayout.Should().Be("stereo"); - probeResult.Stream.BitsPerSample.Should().Be(16); - probeResult.Stream.TimeBase.Should().Be("1/44100"); + probeResult.FormatName.Should().Be("wav"); + probeResult.SizeBytes.Should().Be(new FileInfo(_wavFixturePath).Length); + probeResult.DurationSeconds.Should().BeApproximately(2, 0.1); + + probeResult.CodecType.Should().Be("audio"); + probeResult.CodecName.Should().Be("pcm_s16le"); + probeResult.SampleRate.Should().Be(44100); + probeResult.Channels.Should().Be(2); + probeResult.ChannelLayout.Should().Be("stereo"); + probeResult.BitsPerSample.Should().Be(16); + probeResult.TimeBase.Should().Be("1/44100"); } [Fact] @@ -59,12 +58,12 @@ public async Task Probe_FlacFile_Should_Return_Correct_Metadata_And_Waveform() AssertNonEmptyWaveform(probeResult.WaveformResult); - probeResult.Format.FormatName.Should().Be("flac"); - probeResult.Format.DurationSeconds.Should().BeApproximately(2, 0.1); - probeResult.Stream.CodecName.Should().Be("flac"); - probeResult.Stream.SampleRate.Should().Be(44100); - probeResult.Stream.BitsPerSample.Should().Be(16); - probeResult.Stream.ChannelLayout.Should().Be("stereo"); + probeResult.FormatName.Should().Be("flac"); + probeResult.DurationSeconds.Should().BeApproximately(2, 0.1); + probeResult.CodecName.Should().Be("flac"); + probeResult.SampleRate.Should().Be(44100); + probeResult.BitsPerSample.Should().Be(16); + probeResult.ChannelLayout.Should().Be("stereo"); } finally { @@ -86,12 +85,12 @@ public async Task Probe_Mp3File_Should_Return_Correct_Metadata_And_Waveform() AssertNonEmptyWaveform(probeResult.WaveformResult); - probeResult.Format.FormatName.Should().Be("mp3"); - probeResult.Format.DurationSeconds.Should().BeApproximately(2, 0.1); - probeResult.Stream.CodecName.Should().Be("mp3"); - probeResult.Stream.SampleRate.Should().Be(44100); - probeResult.Stream.BitsPerSample.Should().Be(16); - probeResult.Stream.ChannelLayout.Should().Be("stereo"); + probeResult.FormatName.Should().Be("mp3"); + probeResult.DurationSeconds.Should().BeApproximately(2, 0.1); + probeResult.CodecName.Should().Be("mp3"); + probeResult.SampleRate.Should().Be(44100); + probeResult.BitsPerSample.Should().Be(16); + probeResult.ChannelLayout.Should().Be("stereo"); } finally { @@ -104,11 +103,11 @@ public async Task Probe_AacFile_Should_Return_Correct_Metadata() { var probeResult = await _nativeEncoder.Probe(_aacFixturePath); - probeResult.Format.FormatName.Should().Be("aac"); - probeResult.Stream.SampleRate.Should().BePositive(); - probeResult.Stream.CodecName.Should().Be("aac"); - probeResult.Stream.CodecType.Should().Be("audio"); - probeResult.Stream.ChannelLayout.Should().Be("mono"); + probeResult.FormatName.Should().Be("aac"); + probeResult.SampleRate.Should().BePositive(); + probeResult.CodecName.Should().Be("aac"); + probeResult.CodecType.Should().Be("audio"); + probeResult.ChannelLayout.Should().Be("mono"); } [Fact] @@ -116,10 +115,10 @@ public async Task Probe_WmaFile_Should_Return_Correct_Metadata() { var probeResult = await _nativeEncoder.Probe(_wmaFixturePath); - probeResult.Format.FormatName.Should().Be("asf"); - probeResult.Stream.SampleRate.Should().BePositive(); - probeResult.Stream.CodecName.Should().Be("wmav2"); - probeResult.Stream.CodecType.Should().Be("audio"); + probeResult.FormatName.Should().Be("asf"); + probeResult.SampleRate.Should().BePositive(); + probeResult.CodecName.Should().Be("wmav2"); + probeResult.CodecType.Should().Be("audio"); } [Fact] @@ -129,11 +128,11 @@ public async Task Probe_MovFile_Should_Return_Correct_VideoMetadata() probeResult.WaveformResult.Should().BeNull(); - probeResult.Format.FormatName.Should().Be("mov"); - probeResult.Format.DurationSeconds.Should().BeApproximately(5, 0.1); - probeResult.Stream.CodecType.Should().Be("video"); - probeResult.Stream.Width.Should().Be(640); - probeResult.Stream.Height.Should().Be(360); + probeResult.FormatName.Should().Be("mov"); + probeResult.DurationSeconds.Should().BeApproximately(5, 0.1); + probeResult.CodecType.Should().Be("video"); + probeResult.Width.Should().Be(640); + probeResult.Height.Should().Be(360); } [Fact] @@ -142,11 +141,11 @@ public async Task Probe_Mp4File_Should_Return_Correct_VideoMetadata() var probeResult = await _nativeEncoder.Probe(_mp4FixturePath); probeResult.WaveformResult.Should().BeNull(); - probeResult.Stream.Width.Should().Be(640); - probeResult.Stream.Height.Should().Be(360); + probeResult.Width.Should().Be(640); + probeResult.Height.Should().Be(360); - probeResult.Format.FormatName.Should().Be("mp4"); - probeResult.Format.DurationSeconds.Should().BeApproximately(5, 0.1); + probeResult.FormatName.Should().Be("mp4"); + probeResult.DurationSeconds.Should().BeApproximately(5, 0.1); } [Fact] diff --git a/src/EggEncoder/NativeEncoder.cs b/src/EggEncoder/NativeEncoder.cs index fc97be0..cd914b3 100644 --- a/src/EggEncoder/NativeEncoder.cs +++ b/src/EggEncoder/NativeEncoder.cs @@ -39,7 +39,7 @@ public Task Probe(string filePath) _ => throw new NotSupportedException($"Probing '{extension}' files is not supported by the native audio encoder") }; - _logger.LogInformation($"Probe '{filePath}', format: '{result.Format.FormatName}', codec: '{result.Stream.CodecName}', duration: {result.Format.DurationSeconds}s"); + _logger.LogInformation($"Probe '{filePath}', format: '{result.FormatName}', codec: '{result.CodecName}', duration: {result.DurationSeconds}s"); return Task.FromResult(result); } @@ -109,32 +109,24 @@ private static ProbeResult ProbeWav(string filePath) } var durationSeconds = wavReader.SampleRate > 0 ? (double)wavReader.TotalSamples / wavReader.SampleRate : 0; - var bitRate = wavReader.SampleRate * wavReader.BitsPerSample * wavReader.Channels; var (codecName, codecLongName) = DescribeWavCodec(wavReader.BitsPerSample, wavReader.IsFloatFormat); return new ProbeResult { - Format = new ProbeFormatInfo - { - FormatName = "wav", - FormatLongName = "WAV / WAVE (Waveform Audio)", - SizeBytes = GetFileSize(filePath), - DurationSeconds = durationSeconds, - StreamCount = 1 - }, - Stream = new ProbeStreamInfo - { - CodecType = "audio", - CodecName = codecName, - CodecLongName = codecLongName, - SampleRate = wavReader.SampleRate, - Channels = wavReader.Channels, - ChannelLayout = DescribeChannelLayout(wavReader.Channels), - BitsPerSample = wavReader.BitsPerSample, - BitRate = bitRate, - DurationInSamples = wavReader.TotalSamples, - TimeBase = wavReader.SampleRate > 0 ? $"1/{wavReader.SampleRate}" : null - }, + FormatName = "wav", + FormatLongName = "WAV / WAVE (Waveform Audio)", + SizeBytes = GetFileSize(filePath), + DurationSeconds = durationSeconds, + CodecType = "audio", + CodecName = codecName, + CodecLongName = codecLongName, + SampleRate = wavReader.SampleRate, + Channels = wavReader.Channels, + ChannelLayout = DescribeChannelLayout(wavReader.Channels), + BitsPerSample = wavReader.BitsPerSample, + BitRate = wavReader.SampleRate * wavReader.BitsPerSample * wavReader.Channels, + DurationInSamples = wavReader.TotalSamples, + TimeBase = wavReader.SampleRate > 0 ? $"1/{wavReader.SampleRate}" : null, WaveformResult = JsonSerializer.Serialize(waveformCalculator.GetNormalizedWindows()) }; } @@ -150,31 +142,23 @@ private static ProbeResult ProbeFlac(string filePath) }); var durationSeconds = streamInfo.SampleRate > 0 ? (double)streamInfo.TotalSamples / streamInfo.SampleRate : 0; - var bitRate = streamInfo.SampleRate * streamInfo.BitsPerSample * streamInfo.Channels; return new ProbeResult { - Format = new ProbeFormatInfo - { - FormatName = "flac", - FormatLongName = "FLAC (Free Lossless Audio Codec)", - SizeBytes = GetFileSize(filePath), - DurationSeconds = durationSeconds, - StreamCount = 1 - }, - Stream = new ProbeStreamInfo - { - CodecType = "audio", - CodecName = "flac", - CodecLongName = "FLAC (Free Lossless Audio Codec)", - SampleRate = streamInfo.SampleRate, - Channels = streamInfo.Channels, - ChannelLayout = DescribeChannelLayout(streamInfo.Channels), - BitsPerSample = streamInfo.BitsPerSample, - BitRate = bitRate, - DurationInSamples = streamInfo.TotalSamples, - TimeBase = streamInfo.SampleRate > 0 ? $"1/{streamInfo.SampleRate}" : null - }, + FormatName = "flac", + FormatLongName = "FLAC (Free Lossless Audio Codec)", + SizeBytes = GetFileSize(filePath), + DurationSeconds = durationSeconds, + CodecType = "audio", + CodecName = "flac", + CodecLongName = "FLAC (Free Lossless Audio Codec)", + SampleRate = streamInfo.SampleRate, + Channels = streamInfo.Channels, + ChannelLayout = DescribeChannelLayout(streamInfo.Channels), + BitsPerSample = streamInfo.BitsPerSample, + BitRate = streamInfo.SampleRate * streamInfo.BitsPerSample * streamInfo.Channels, + DurationInSamples = streamInfo.TotalSamples, + TimeBase = streamInfo.SampleRate > 0 ? $"1/{streamInfo.SampleRate}" : null, WaveformResult = JsonSerializer.Serialize(waveformCalculator?.GetNormalizedWindows() ?? []) }; } @@ -192,27 +176,20 @@ private static ProbeResult ProbeMp3(string filePath) return new ProbeResult { - Format = new ProbeFormatInfo - { - FormatName = "mp3", - FormatLongName = "MP3 (MPEG audio layer 3)", - SizeBytes = GetFileSize(filePath), - DurationSeconds = mp3ProbeResult.DurationSeconds, - StreamCount = 1 - }, - Stream = new ProbeStreamInfo - { - CodecType = "audio", - CodecName = "mp3", - CodecLongName = "MP3 (MPEG audio layer 3)", - SampleRate = mp3ProbeResult.SampleRate, - Channels = mp3ProbeResult.Channels, - ChannelLayout = DescribeChannelLayout(mp3ProbeResult.Channels), - BitsPerSample = 16, - BitRate = mp3ProbeResult.BitRate, - IsVariableBitRate = mp3ProbeResult.IsVariableBitRate, - TimeBase = mp3ProbeResult.SampleRate > 0 ? $"1/{mp3ProbeResult.SampleRate}" : null - }, + FormatName = "mp3", + FormatLongName = "MP3 (MPEG audio layer 3)", + SizeBytes = GetFileSize(filePath), + DurationSeconds = mp3ProbeResult.DurationSeconds, + CodecType = "audio", + CodecName = "mp3", + CodecLongName = "MP3 (MPEG audio layer 3)", + SampleRate = mp3ProbeResult.SampleRate, + Channels = mp3ProbeResult.Channels, + ChannelLayout = DescribeChannelLayout(mp3ProbeResult.Channels), + BitsPerSample = 16, + BitRate = mp3ProbeResult.BitRate, + IsVariableBitRate = mp3ProbeResult.IsVariableBitRate, + TimeBase = mp3ProbeResult.SampleRate > 0 ? $"1/{mp3ProbeResult.SampleRate}" : null, WaveformResult = JsonSerializer.Serialize(waveformCalculator?.GetNormalizedWindows() ?? []) }; } @@ -228,31 +205,23 @@ private static ProbeResult ProbeAac(string filePath) }); var durationSeconds = streamInfo.SampleRate > 0 ? (double)streamInfo.TotalSamples / streamInfo.SampleRate : 0; - var bitRate = streamInfo.SampleRate * streamInfo.BitsPerSample * streamInfo.Channels; return new ProbeResult { - Format = new ProbeFormatInfo - { - FormatName = "aac", - FormatLongName = "ADTS AAC (Advanced Audio Coding)", - SizeBytes = GetFileSize(filePath), - DurationSeconds = durationSeconds, - StreamCount = 1 - }, - Stream = new ProbeStreamInfo - { - CodecType = "audio", - CodecName = "aac", - CodecLongName = "AAC-LC (Advanced Audio Coding, Low Complexity profile)", - SampleRate = streamInfo.SampleRate, - Channels = streamInfo.Channels, - ChannelLayout = DescribeChannelLayout(streamInfo.Channels), - BitsPerSample = streamInfo.BitsPerSample, - BitRate = bitRate, - DurationInSamples = streamInfo.TotalSamples, - TimeBase = streamInfo.SampleRate > 0 ? $"1/{streamInfo.SampleRate}" : null - }, + FormatName = "aac", + FormatLongName = "ADTS AAC (Advanced Audio Coding)", + SizeBytes = GetFileSize(filePath), + DurationSeconds = durationSeconds, + CodecType = "audio", + CodecName = "aac", + CodecLongName = "AAC-LC (Advanced Audio Coding, Low Complexity profile)", + SampleRate = streamInfo.SampleRate, + Channels = streamInfo.Channels, + ChannelLayout = DescribeChannelLayout(streamInfo.Channels), + BitsPerSample = streamInfo.BitsPerSample, + BitRate = streamInfo.SampleRate * streamInfo.BitsPerSample * streamInfo.Channels, + DurationInSamples = streamInfo.TotalSamples, + TimeBase = streamInfo.SampleRate > 0 ? $"1/{streamInfo.SampleRate}" : null, WaveformResult = JsonSerializer.Serialize(waveformCalculator?.GetNormalizedWindows() ?? []) }; } @@ -268,31 +237,23 @@ private static ProbeResult ProbeWma(string filePath) }); var durationSeconds = streamInfo.SampleRate > 0 ? (double)streamInfo.TotalSamples / streamInfo.SampleRate : 0; - var bitRate = streamInfo.SampleRate * streamInfo.BitsPerSample * streamInfo.Channels; return new ProbeResult { - Format = new ProbeFormatInfo - { - FormatName = "asf", - FormatLongName = "ASF (Advanced / Active Streaming Format)", - SizeBytes = GetFileSize(filePath), - DurationSeconds = durationSeconds, - StreamCount = 1 - }, - Stream = new ProbeStreamInfo - { - CodecType = "audio", - CodecName = "wmav2", - CodecLongName = "Windows Media Audio 2", - SampleRate = streamInfo.SampleRate, - Channels = streamInfo.Channels, - ChannelLayout = DescribeChannelLayout(streamInfo.Channels), - BitsPerSample = streamInfo.BitsPerSample, - BitRate = bitRate, - DurationInSamples = streamInfo.TotalSamples, - TimeBase = streamInfo.SampleRate > 0 ? $"1/{streamInfo.SampleRate}" : null - }, + FormatName = "asf", + FormatLongName = "ASF (Advanced / Active Streaming Format)", + SizeBytes = GetFileSize(filePath), + DurationSeconds = durationSeconds, + CodecType = "audio", + CodecName = "wmav2", + CodecLongName = "Windows Media Audio 2", + SampleRate = streamInfo.SampleRate, + Channels = streamInfo.Channels, + ChannelLayout = DescribeChannelLayout(streamInfo.Channels), + BitsPerSample = streamInfo.BitsPerSample, + BitRate = streamInfo.SampleRate * streamInfo.BitsPerSample * streamInfo.Channels, + DurationInSamples = streamInfo.TotalSamples, + TimeBase = streamInfo.SampleRate > 0 ? $"1/{streamInfo.SampleRate}" : null, WaveformResult = JsonSerializer.Serialize(waveformCalculator?.GetNormalizedWindows() ?? []) }; } @@ -310,21 +271,14 @@ private static ProbeResult ProbeVideo(string filePath) return new ProbeResult { - Format = new ProbeFormatInfo - { - FormatName = formatName, - FormatLongName = formatLongName, - SizeBytes = GetFileSize(filePath), - DurationSeconds = movProbeResult.DurationSeconds, - StreamCount = 1 - }, - Stream = new ProbeStreamInfo - { - CodecType = "video", - CodecName = movProbeResult.CodecFourCc, - Width = movProbeResult.Width, - Height = movProbeResult.Height - }, + FormatName = formatName, + FormatLongName = formatLongName, + SizeBytes = GetFileSize(filePath), + DurationSeconds = movProbeResult.DurationSeconds, + CodecType = "video", + CodecName = movProbeResult.CodecFourCc, + Width = movProbeResult.Width, + Height = movProbeResult.Height, WaveformResult = null }; } diff --git a/src/EggEncoder/Results/ProbeResult.cs b/src/EggEncoder/Results/ProbeResult.cs index 51c95c6..de5d580 100644 --- a/src/EggEncoder/Results/ProbeResult.cs +++ b/src/EggEncoder/Results/ProbeResult.cs @@ -1,18 +1,6 @@ namespace EggEncoder.Results { public class ProbeResult - { - // Only populated with values this library can genuinely compute -- fields ffprobe - // reports that don't have a real equivalent here (probe_score, disposition flags, - // container timestamps) are intentionally omitted rather than filled with placeholders. - public required ProbeFormatInfo Format { get; set; } - - public required ProbeStreamInfo Stream { get; set; } - - public string? WaveformResult { get; set; } - } - - public class ProbeFormatInfo { public required string FormatName { get; init; } @@ -22,11 +10,6 @@ public class ProbeFormatInfo public required double DurationSeconds { get; init; } - public required int StreamCount { get; init; } - } - - public class ProbeStreamInfo - { public required string CodecType { get; init; } public string? CodecName { get; init; } @@ -52,5 +35,7 @@ public class ProbeStreamInfo public int? Width { get; init; } public int? Height { get; init; } + + public string? WaveformResult { get; set; } } }