Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ dotnet test src/EggEncoder.UnitTests/EggEncoder.UnitTests.csproj --configuration

- **`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`
- **`Waveform/WaveformCalculator.cs`** — streaming peak-window calculator fed blocks during decode, used by every codec's probe path to produce `ProbeResult.Waveform`
- **`Results/ProbeResult.cs`** — the public `ProbeResult` DTO returned by every `Probe` call
- **`ServiceCollectionExtensions.cs`** — `AddEggEncoder()` DI registration; no options, registers `IMediaEncoder` → `NativeEncoder` (scoped)

Expand Down
2 changes: 1 addition & 1 deletion docs/API-Reference.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ <h2>ProbeResult</h2>
public string? TimeBase { get; init; } // e.g. "1/44100"
public int? Width { get; init; } // set for video containers (MOV/MP4), null for audio
public int? Height { get; init; }
public string? WaveformResult { get; set; }
public IReadOnlyList&lt;double&gt;? Waveform { get; init; } // normalized peak windows in [0,1], or null
}</code></pre>

<h2>NativeEncoder</h2>
Expand Down
4 changes: 2 additions & 2 deletions docs/Advanced-Features.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
<h1>Advanced Features</h1>

<h2>Waveform Generation</h2>
<p>Every <code>Probe</code> call that decodes audio (WAV, FLAC, MP3, AAC, WMA — not MOV/MP4) populates <code>ProbeResult.WaveformResult</code> with a JSON array of normalized peak values in <code>[0, 1]</code>, one per window, via <code>WaveformCalculator</code>:</p>
<p>Every <code>Probe</code> call that decodes audio (WAV, FLAC, MP3, AAC, WMA — not MOV/MP4) populates <code>ProbeResult.Waveform</code> with a list of normalized peak values in <code>[0, 1]</code>, one per window, via <code>WaveformCalculator</code>:</p>
<pre><code class="language-csharp">using EggEncoder.Waveform;

var calculator = new WaveformCalculator(totalSamplesPerChannel, channels, bitsPerSample);
Expand Down Expand Up @@ -71,7 +71,7 @@ <h2>MOV/MP4 Probing</h2>

var result = MovProbe.Probe("clip.mp4");
Console.WriteLine($"{result.Width}x{result.Height}, {result.DurationInSeconds}s, {result.CodecFourCc}");</code></pre>
<p>This is metadata-only — <code>MovProbe</code> does not decode audio or video frames. <code>NativeEncoder.Probe</code> routes <code>.mov</code>/<code>.mp4</code> files here automatically and leaves <code>ProbeResult.WaveformResult</code> null.</p>
<p>This is metadata-only — <code>MovProbe</code> does not decode audio or video frames. <code>NativeEncoder.Probe</code> routes <code>.mov</code>/<code>.mp4</code> files here automatically and leaves <code>ProbeResult.Waveform</code> null.</p>

<h2 id="native-binary-packaging">Native Binary Packaging</h2>
<p><code>libmp3lame.dll</code> and <code>libFLAC.dll</code> are packed via the NuGet <code>contentFiles</code> convention so they land at <code>Native/win-x64/*.dll</code> relative to your application's output directory — exactly where <code>NativeLibraryLoader</code>'s custom <code>DllImportResolver</code> looks for them at runtime, regardless of your app's working directory. No manual copy step is required after <code>dotnet add package EggEncoder</code>.</p>
Expand Down
2 changes: 1 addition & 1 deletion docs/Getting-Started.html
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ <h2>Reading a ProbeResult</h2>
public string? TimeBase { get; init; } // e.g. "1/44100"
public int? Width { get; init; } // set for video containers (MOV/MP4), null for audio
public int? Height { get; init; }
public string? WaveformResult { get; set; } // JSON array of normalized peak windows, or null
public IReadOnlyList&lt;double&gt;? Waveform { get; init; } // normalized peak windows, or null
}</code></pre>
<p>See <a href="Advanced-Features.html">Advanced Features</a> for waveform generation and sample-accurate cutting details, or the <a href="API-Reference.html">API Reference</a> for the full type list.</p>
</div>
Expand Down
2 changes: 1 addition & 1 deletion llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public class ProbeResult
public string? TimeBase { get; init; } // e.g. "1/44100"
public int? Width { get; init; } // set for video containers (MOV/MP4), null for audio
public int? Height { get; init; }
public string? WaveformResult { get; set; } // JSON array of normalized peak windows in [0,1], or null
public IReadOnlyList<double>? Waveform { get; init; } // normalized peak windows in [0,1], or null
}
```

Expand Down
2 changes: 1 addition & 1 deletion llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ var probeResult = await encoder.Probe("track.flac");

- `IMediaEncoder` — the abstraction (`Probe`, `ConvertFile`, `CutFile`)
- `NativeEncoder` — the sole implementation, Windows x64 only (P/Invoke to libmp3lame/libFLAC)
- `ProbeResult` — format name/long name, file size, duration, codec name/long name, sample rate, channels, channel layout, bit depth, bitrate, dimensions (for video), waveform JSON
- `ProbeResult` — format name/long name, file size, duration, codec name/long name, sample rate, channels, channel layout, bit depth, bitrate, dimensions (for video), normalized waveform peak windows
- `AudioCutter` — format-dispatching convert/cut, used internally by `NativeEncoder`
- `WaveformCalculator` — streams decoded blocks into normalized peak windows

Expand Down
19 changes: 8 additions & 11 deletions src/EggEncoder.UnitTests/NativeEncoderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Moq;
using System.Text.Json;

namespace EggEncoder.UnitTests
{
Expand All @@ -29,7 +28,7 @@ public async Task Probe_WavFile_Should_Return_Correct_Metadata_And_Waveform()
{
var probeResult = await _nativeEncoder.Probe(_wavFixturePath);

AssertNonEmptyWaveform(probeResult.WaveformResult);
AssertNonEmptyWaveform(probeResult.Waveform);

probeResult.FormatName.Should().Be("wav");
probeResult.SizeBytes.Should().Be(new FileInfo(_wavFixturePath).Length);
Expand All @@ -56,7 +55,7 @@ public async Task Probe_FlacFile_Should_Return_Correct_Metadata_And_Waveform()

var probeResult = await _nativeEncoder.Probe(flacPath);

AssertNonEmptyWaveform(probeResult.WaveformResult);
AssertNonEmptyWaveform(probeResult.Waveform);

probeResult.FormatName.Should().Be("flac");
probeResult.DurationSeconds.Should().BeApproximately(2, 0.1);
Expand All @@ -83,7 +82,7 @@ public async Task Probe_Mp3File_Should_Return_Correct_Metadata_And_Waveform()

var probeResult = await _nativeEncoder.Probe(mp3Path);

AssertNonEmptyWaveform(probeResult.WaveformResult);
AssertNonEmptyWaveform(probeResult.Waveform);

probeResult.FormatName.Should().Be("mp3");
probeResult.DurationSeconds.Should().BeApproximately(2, 0.1);
Expand Down Expand Up @@ -126,7 +125,7 @@ public async Task Probe_MovFile_Should_Return_Correct_VideoMetadata()
{
var probeResult = await _nativeEncoder.Probe(_movFixturePath);

probeResult.WaveformResult.Should().BeNull();
probeResult.Waveform.Should().BeNull();

probeResult.FormatName.Should().Be("mov");
probeResult.DurationSeconds.Should().BeApproximately(5, 0.1);
Expand All @@ -140,7 +139,7 @@ public async Task Probe_Mp4File_Should_Return_Correct_VideoMetadata()
{
var probeResult = await _nativeEncoder.Probe(_mp4FixturePath);

probeResult.WaveformResult.Should().BeNull();
probeResult.Waveform.Should().BeNull();
probeResult.Width.Should().Be(640);
probeResult.Height.Should().Be(360);

Expand Down Expand Up @@ -193,12 +192,10 @@ public async Task CutFile_ToNestedMissingDirectory_Should_CreateDirectory_And_Cu
}
}

private static void AssertNonEmptyWaveform(string? waveformResult)
private static void AssertNonEmptyWaveform(IReadOnlyList<double>? waveform)
{
waveformResult.Should().NotBeNull();

var windows = JsonSerializer.Deserialize<List<double>>(waveformResult!);
windows.Should().NotBeEmpty();
waveform.Should().NotBeNull();
waveform.Should().NotBeEmpty();
}

private static string CreateTempDirectory()
Expand Down
12 changes: 6 additions & 6 deletions src/EggEncoder/NativeEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ private static ProbeResult ProbeWav(string filePath)
BitRate = wavReader.SampleRate * wavReader.BitsPerSample * wavReader.Channels,
DurationInSamples = wavReader.TotalSamples,
TimeBase = wavReader.SampleRate > 0 ? $"1/{wavReader.SampleRate}" : null,
WaveformResult = JsonSerializer.Serialize(waveformCalculator.GetNormalizedWindows())
Waveform = waveformCalculator.GetNormalizedWindows()
};
}

Expand Down Expand Up @@ -159,7 +159,7 @@ private static ProbeResult ProbeFlac(string filePath)
BitRate = streamInfo.SampleRate * streamInfo.BitsPerSample * streamInfo.Channels,
DurationInSamples = streamInfo.TotalSamples,
TimeBase = streamInfo.SampleRate > 0 ? $"1/{streamInfo.SampleRate}" : null,
WaveformResult = JsonSerializer.Serialize(waveformCalculator?.GetNormalizedWindows() ?? [])
Waveform = waveformCalculator?.GetNormalizedWindows() ?? []
};
}

Expand Down Expand Up @@ -190,7 +190,7 @@ private static ProbeResult ProbeMp3(string filePath)
BitRate = mp3ProbeResult.BitRate,
IsVariableBitRate = mp3ProbeResult.IsVariableBitRate,
TimeBase = mp3ProbeResult.SampleRate > 0 ? $"1/{mp3ProbeResult.SampleRate}" : null,
WaveformResult = JsonSerializer.Serialize(waveformCalculator?.GetNormalizedWindows() ?? [])
Waveform = waveformCalculator?.GetNormalizedWindows() ?? []
};
}

Expand Down Expand Up @@ -222,7 +222,7 @@ private static ProbeResult ProbeAac(string filePath)
BitRate = streamInfo.SampleRate * streamInfo.BitsPerSample * streamInfo.Channels,
DurationInSamples = streamInfo.TotalSamples,
TimeBase = streamInfo.SampleRate > 0 ? $"1/{streamInfo.SampleRate}" : null,
WaveformResult = JsonSerializer.Serialize(waveformCalculator?.GetNormalizedWindows() ?? [])
Waveform = waveformCalculator?.GetNormalizedWindows() ?? []
};
}

Expand Down Expand Up @@ -254,7 +254,7 @@ private static ProbeResult ProbeWma(string filePath)
BitRate = streamInfo.SampleRate * streamInfo.BitsPerSample * streamInfo.Channels,
DurationInSamples = streamInfo.TotalSamples,
TimeBase = streamInfo.SampleRate > 0 ? $"1/{streamInfo.SampleRate}" : null,
WaveformResult = JsonSerializer.Serialize(waveformCalculator?.GetNormalizedWindows() ?? [])
Waveform = waveformCalculator?.GetNormalizedWindows() ?? []
};
}

Expand All @@ -279,7 +279,7 @@ private static ProbeResult ProbeVideo(string filePath)
CodecName = movProbeResult.CodecFourCc,
Width = movProbeResult.Width,
Height = movProbeResult.Height,
WaveformResult = null
Waveform = null
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/EggEncoder/Results/ProbeResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ public class ProbeResult

public int? Height { get; init; }

public string? WaveformResult { get; set; }
public IReadOnlyList<double>? Waveform { get; init; }
}
}
Loading