using System.Buffers.Binary; using System.Text; namespace SeaHaven.Services.Helpers { /// /// Reads a video's duration from the ISO base media (MP4 / QuickTime) movie header: /// top-level moov box → mvhd timescale and duration. It seeks over box /// headers only (the moov box may sit after a large mdat), never decodes /// media and never allocates more than a few bytes. Returns null whenever the structure /// cannot be read, so callers can let unreadable files through (SH-116: unreadable /// metadata never blocks an upload). /// public static class VideoDurationProbe { private const int MaxBoxesPerLevel = 1024; public static double? TryReadDurationSeconds(Stream? stream) { if (stream == null || !stream.CanSeek || !stream.CanRead) return null; try { var moov = FindBox(stream, 0, stream.Length, "moov"); if (moov == null) return null; var mvhd = FindBox(stream, moov.Value.BodyStart, moov.Value.End, "mvhd"); return mvhd == null ? null : ReadMovieHeaderSeconds(stream, mvhd.Value); } catch (IOException) { return null; } } private readonly record struct Box(long BodyStart, long End); private static Box? FindBox(Stream stream, long start, long end, string type) { var header = new byte[8]; var position = start; for (var i = 0; i < MaxBoxesPerLevel && position + 8 <= end; i++) { stream.Position = position; if (!ReadExactly(stream, header, 8)) return null; long size = BinaryPrimitives.ReadUInt32BigEndian(header); var boxType = Encoding.ASCII.GetString(header, 4, 4); var headerLength = 8; if (size == 1) { // 64-bit "largesize" follows the type. var large = new byte[8]; if (!ReadExactly(stream, large, 8)) return null; var largeSize = BinaryPrimitives.ReadUInt64BigEndian(large); if (largeSize > long.MaxValue) return null; size = (long)largeSize; headerLength = 16; } else if (size == 0) { size = end - position; } if (size < headerLength || position + size > end) return null; if (boxType == type) return new Box(position + headerLength, position + size); position += size; } return null; } private static double? ReadMovieHeaderSeconds(Stream stream, Box mvhd) { // version(1) flags(3), then v0: creation(4) modification(4) timescale(4) duration(4) // v1: creation(8) modification(8) timescale(4) duration(8) var body = new byte[32]; stream.Position = mvhd.BodyStart; var available = (int)Math.Min(body.Length, mvhd.End - mvhd.BodyStart); if (available < 20 || !ReadExactly(stream, body, available)) return null; uint timescale; ulong duration; if (body[0] == 0) { timescale = BinaryPrimitives.ReadUInt32BigEndian(body.AsSpan(12, 4)); duration = BinaryPrimitives.ReadUInt32BigEndian(body.AsSpan(16, 4)); } else if (body[0] == 1 && available >= 32) { timescale = BinaryPrimitives.ReadUInt32BigEndian(body.AsSpan(20, 4)); duration = BinaryPrimitives.ReadUInt64BigEndian(body.AsSpan(24, 8)); } else { return null; } // All-ones duration means "unknown" in the spec. if (timescale == 0 || duration == uint.MaxValue || duration == ulong.MaxValue) return null; return (double)duration / timescale; } private static bool ReadExactly(Stream stream, byte[] buffer, int count) { var read = 0; while (read < count) { var n = stream.Read(buffer, read, count - read); if (n <= 0) return false; read += n; } return true; } } }