using System.Buffers.Binary;
namespace DeepDrftContent.Processors.Opus;
///
/// The single derived sidecar artifact per track (§3.4a B, recommended design): the Opus setup header
/// (OpusHead + OpusTags) followed by the granule→byte seek index. The client fetches this
/// once on track load and parses it into its OpusSeekData, so it always has both the setup bytes
/// (to prepend to any mid-stream slice) and the accurate seek transfer function before it ever issues a
/// Range fetch — including a window that opens away from byte 0 (UC9).
///
/// The verbatim OpusHead + OpusTags pages.
/// The bucketed granule→byte seek index.
public sealed record OpusSidecar(byte[] SetupHeaderBytes, OggOpusSeekIndex SeekIndex)
{
///
/// Serializes to [uint32 setupHeaderLength][setup-header bytes][seek-index blob]. The
/// length prefix lets the client split the two regions with one read; the seek-index blob carries
/// its own self-describing header (), so it needs no trailing
/// length.
///
public byte[] ToBytes()
{
var indexBytes = SeekIndex.ToBytes();
var bytes = new byte[4 + SetupHeaderBytes.Length + indexBytes.Length];
var span = bytes.AsSpan();
BinaryPrimitives.WriteUInt32LittleEndian(span[..4], (uint)SetupHeaderBytes.Length);
SetupHeaderBytes.CopyTo(span.Slice(4));
indexBytes.CopyTo(span.Slice(4 + SetupHeaderBytes.Length));
return bytes;
}
///
/// Parses a blob produced by . Returns null on any structural inconsistency
/// (short blob, length prefix that overruns, or an unparseable index) — the format is exact, so a
/// malformed blob is corruption.
///
public static OpusSidecar? FromBytes(ReadOnlySpan bytes)
{
if (bytes.Length < 4)
return null;
var setupLength = BinaryPrimitives.ReadUInt32LittleEndian(bytes[..4]);
var indexStart = 4 + (long)setupLength;
if (bytes.Length < indexStart)
return null;
var setupHeader = bytes.Slice(4, (int)setupLength).ToArray();
var index = OggOpusSeekIndex.FromBytes(bytes.Slice((int)indexStart));
if (index is null)
return null;
return new OpusSidecar(setupHeader, index);
}
}