79bbbd4956
Processors now emit a ProcessedAudio plan with a streamed writer instead of a whole-file AudioBinary; vault writes stream via RegisterResourceStreamingAsync. Header parsing is bounded. Wave 2 (waveform/Opus) still re-reads the full file by design.
69 lines
3.1 KiB
C#
69 lines
3.1 KiB
C#
namespace DeepDrftContent.Processors;
|
|
|
|
/// <summary>
|
|
/// The product of processing an uploaded audio file on the store path: the metadata SQL and the
|
|
/// vault index need, plus a streamed writer that emits the canonical vault bytes to a destination
|
|
/// stream without ever materializing the whole file in a managed <c>byte[]</c>.
|
|
///
|
|
/// This replaces the former whole-file <c>AudioBinary</c> as the processor output for upload /
|
|
/// replace-audio (Wave 1 OOM fix): passthrough formats (standard-PCM WAV, MP3, FLAC) stream the
|
|
/// source file straight to the destination, and EXTENSIBLE WAVs stream their normalization to
|
|
/// standard PCM. The vault <em>load</em> path still uses <c>AudioBinary</c> (a full buffer) — that
|
|
/// is the Wave 2 read path and is out of scope here.
|
|
///
|
|
/// <see cref="WriteToAsync"/> is invoked exactly once by the streaming vault register, against the
|
|
/// freshly opened backing <see cref="System.IO.FileStream"/>. The writer re-opens the source file
|
|
/// itself, so the source (a staging file) must still exist when the register runs — it does, because
|
|
/// processing and registration are sequential within the store call, before the staging-file
|
|
/// <c>finally</c> cleanup.
|
|
/// </summary>
|
|
public sealed class ProcessedAudio
|
|
{
|
|
/// <summary>The stored file extension (e.g. <c>.wav</c>, <c>.mp3</c>, <c>.flac</c>).</summary>
|
|
public string Extension { get; }
|
|
|
|
/// <summary>Audio duration in seconds, extracted from the header.</summary>
|
|
public double Duration { get; }
|
|
|
|
/// <summary>Audio bitrate in kbps, extracted from (or estimated for) the header.</summary>
|
|
public int Bitrate { get; }
|
|
|
|
/// <summary>
|
|
/// The canonical stored byte count — computed from the header and file length, never by
|
|
/// buffering the body. Used only for diagnostics (confirming the streamed path was taken).
|
|
/// </summary>
|
|
public long Size { get; }
|
|
|
|
private readonly Func<Stream, CancellationToken, Task> _writeTo;
|
|
|
|
public ProcessedAudio(
|
|
string extension,
|
|
double duration,
|
|
int bitrate,
|
|
long size,
|
|
Func<Stream, CancellationToken, Task> writeTo)
|
|
{
|
|
Extension = extension;
|
|
Duration = duration;
|
|
Bitrate = bitrate;
|
|
Size = size;
|
|
_writeTo = writeTo;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Streams the canonical vault bytes to <paramref name="destination"/>. Bounded-buffer — peak
|
|
/// managed memory is O(buffer), not O(filesize).
|
|
/// </summary>
|
|
public Task WriteToAsync(Stream destination, CancellationToken cancellationToken = default)
|
|
=> _writeTo(destination, cancellationToken);
|
|
|
|
/// <summary>
|
|
/// Builds a passthrough plan: the stored bytes are byte-identical to the source file (standard
|
|
/// PCM WAV, MP3, FLAC — no transcoding). The writer is a bounded disk-to-disk copy.
|
|
/// </summary>
|
|
public static ProcessedAudio Passthrough(
|
|
string sourcePath, string extension, double duration, int bitrate, long sourceLength)
|
|
=> new(extension, duration, bitrate, sourceLength,
|
|
(destination, ct) => AudioStoreStream.CopyFileAsync(sourcePath, destination, ct));
|
|
}
|