Skip to content

Media Metadata Inspector — Go source

Inspect audio, video and image files at the byte level — MP4 boxes, ID3 tags, WAV chunks, GIF headers — parsed by our own readers, entirely in your browser.

This is the Go implementation — the same logic the interactive tool runs, in a shareable, citable form.

// Package mediameta is the Go twin of CosmoDev's src/lib/media-meta.ts
// (dual source: the web lib is TypeScript, the CLI lib is Go — kept in
// lock-step). Pure + deterministic, never panics: every read is bounds
// checked the same way the TS subarray/index accesses clamp to undefined.
// The tests in media-metadata-inspector_test.go share their vectors with
// src/lib/media-meta.test.ts so the two implementations are held to one
// contract.
//
// Probe mirrors the TS probe() exactly: signature dispatch (ftyp@4, ID3,
// RIFF+WAVE, GIF8, EBML 1A45DFA3, else unknown); MP4 box walk with
// moov/mvhd (v0 AND v1 64-bit duration) + first trak/tkhd 16.16
// width/height; ID3v2.3/2.4 frames (syncsafe tag size; v2.3 plain frame
// sizes, v2.4 syncsafe; TIT2/TPE1/TALB with latin-1 and UTF-16-BOM text
// decode); WAV little-endian chunk walk (fmt byteRate + data size ->
// duration).
package mediameta

import (
	"bytes"
	"strings"
)

// MediaMeta is the probe result. Kind is one of "mp4", "mp3", "wav",
// "gif", "webm", "unknown". Nil pointer fields mirror the TS null
// (absent) values; empty Title/Artist/Album mirror undefined.
type MediaMeta struct {
	Kind        string
	DurationSec *float64
	Width       *float64
	Height      *float64
	Title       string
	Artist      string
	Album       string
}

// f64ptr boxes a float so callers can distinguish absent (nil) from 0.
func f64ptr(v float64) *float64 { return &v }

// ascii reads n bytes at start as a string, mirroring the TS
// String.fromCharCode spread. Out-of-range reads yield "".
func ascii(b []byte, start, n int) string {
	if start < 0 || n < 0 || start+n > len(b) {
		return ""
	}
	return string(b[start : start+n])
}

// at reads one byte, returning 0 for out-of-range offsets (the TS
// undefined byte behaves as 0 in every comparison it feeds).
func at(b []byte, i int) byte {
	if i < 0 || i >= len(b) {
		return 0
	}
	return b[i]
}

// u16 reads a big-endian 16-bit value; u32 a big-endian 32-bit one.
func u16(b []byte, o int) int {
	if o < 0 || o+2 > len(b) {
		return 0
	}
	return int(b[o]) | int(b[o+1])<<8
}

func u32(b []byte, o int) uint32 {
	if o < 0 || o+4 > len(b) {
		return 0
	}
	return uint32(b[o])<<24 | uint32(b[o+1])<<16 | uint32(b[o+2])<<8 | uint32(b[o+3])
}

// u32le reads a little-endian 32-bit value (RIFF/WAV fields, unlike
// MP4/ID3).
func u32le(b []byte, o int) uint32 {
	if o < 0 || o+4 > len(b) {
		return 0
	}
	return uint32(b[o]) | uint32(b[o+1])<<8 | uint32(b[o+2])<<16 | uint32(b[o+3])<<24
}

// mp4Box is one ISO-BMFF box: type plus the absolute payload bounds.
type mp4Box struct {
	typ        string
	start, end int
}

// findBoxes walks boxes and returns payload bounds for every box of a
// wanted type. Mirrors the TS loop, including its safe stops (64-bit
// sizes, undersized boxes, boxes past the end).
func findBoxes(b []byte, start, end int, wanted ...string) []mp4Box {
	var found []mp4Box
	wants := func(typ string) bool {
		for _, w := range wanted {
			if typ == w {
				return true
			}
		}
		return false
	}
	pos := start
	for pos+8 <= end {
		size := int(u32(b, pos))
		const headerSize = 8
		if size == 1 {
			return found // 64-bit sizes unsupported — stop safely.
		}
		if size == 0 {
			size = end - pos // extends to the end
		}
		if size < headerSize || pos+size > end {
			return found
		}
		typ := ascii(b, pos+4, 4)
		if wants(typ) {
			found = append(found, mp4Box{typ, pos + headerSize, pos + size})
		}
		pos += size
	}
	return found
}

// probeMp4 walks moov → mvhd (duration) and the first trak → tkhd
// (16.16 fixed width/height).
func probeMp4(b []byte) *MediaMeta {
	meta := &MediaMeta{Kind: "mp4"}
	for _, moov := range findBoxes(b, 0, len(b), "moov") {
		for _, mvhd := range findBoxes(b, moov.start, moov.end, "mvhd") {
			version := at(b, mvhd.start)
			if version == 0 && mvhd.end-mvhd.start >= 20 {
				timescale := u32(b, mvhd.start+12)
				duration := u32(b, mvhd.start+16)
				if timescale > 0 {
					meta.DurationSec = f64ptr(float64(duration) / float64(timescale))
				}
			} else if version == 1 && mvhd.end-mvhd.start >= 32 {
				timescale := u32(b, mvhd.start+20)
				duration := uint64(u32(b, mvhd.start+24))<<32 | uint64(u32(b, mvhd.start+28))
				if timescale > 0 {
					meta.DurationSec = f64ptr(float64(duration) / float64(timescale))
				}
			}
		}
		for _, trak := range findBoxes(b, moov.start, moov.end, "trak") {
			for _, tkhd := range findBoxes(b, trak.start, trak.end, "tkhd") {
				if at(b, tkhd.start) == 0 && tkhd.end-tkhd.start >= 84 {
					meta.Width = f64ptr(float64(u32(b, tkhd.start+76)) / 65536)
					meta.Height = f64ptr(float64(u32(b, tkhd.start+80)) / 65536)
				}
				break // First track is enough for v1.
			}
			if meta.Width != nil {
				break
			}
		}
	}
	return meta
}

// syncsafe decodes a 4-byte synchsafe integer (7 bits per byte) — the
// ID3v2 tag size is always encoded this way.
func syncsafe(b []byte, o int) int {
	return int(b[o])<<21 | int(b[o+1])<<14 | int(b[o+2])<<7 | int(b[o+3])
}

// isFrameID mirrors the TS /^[A-Z0-9]{4}$/ frame-id guard.
func isFrameID(id string) bool {
	if len(id) != 4 {
		return false
	}
	for _, c := range id {
		if !('A' <= c && c <= 'Z' || '0' <= c && c <= '9') {
			return false
		}
	}
	return true
}

// decodeID3Text decodes a frame payload: leading encoding byte, then
// latin-1 (0), UTF-16 with BOM (1), BOM-less big-endian UTF-16 (2), or
// UTF-8 (3) text. Stops at the first NUL, like the TS decoder.
func decodeID3Text(b []byte, start, n int) string {
	if n <= 0 || start < 0 || start >= len(b) {
		return ""
	}
	encoding := b[start]
	lo, hi := start+1, start+n
	if hi > len(b) {
		hi = len(b) // subarray clamps in TS; Go must clamp explicitly
	}
	if lo > hi {
		return ""
	}
	text := b[lo:hi]
	if encoding == 3 {
		// UTF-8 (ID3v2.4): decode multi-byte sequences, stop at the NUL.
		if i := bytes.IndexByte(text, 0); i != -1 {
			text = text[:i]
		}
		return string(text)
	}
	if encoding == 1 || encoding == 2 {
		// UTF-16: encoding 1 carries a BOM (sniff endianness, skip it);
		// encoding 2 is BOM-less big-endian per ID3v2.4.
		bom := encoding == 1 && len(text) > 1 && (text[0] == 0xff || text[0] == 0xfe)
		start2 := 0
		littleEndian := false
		if bom {
			start2 = 2
			littleEndian = text[0] == 0xff
		}
		var out strings.Builder
		for i := start2; i+1 < len(text); i += 2 {
			var code int
			if littleEndian {
				code = int(text[i]) | int(text[i+1])<<8
			} else {
				code = int(text[i])<<8 | int(text[i+1])
			}
			if code == 0 {
				break
			}
			out.WriteRune(rune(code))
		}
		return out.String()
	}
	var out strings.Builder
	for _, by := range text {
		if by == 0 {
			break
		}
		out.WriteRune(rune(by)) // ISO-8859-1 / UTF-8 ASCII range
	}
	return out.String()
}

// probeId3 walks the ID3v2 frames: v2.4 frame sizes are syncsafe, v2.3
// plain 32-bit; the tag size is syncsafe in both.
func probeId3(b []byte) *MediaMeta {
	meta := &MediaMeta{Kind: "mp3"}
	version := b[3]
	tagSize := syncsafe(b, 6)
	pos := 10
	end := 10 + tagSize
	if end > len(b) {
		end = len(b)
	}
	for pos+10 <= end {
		id := ascii(b, pos, 4)
		if !isFrameID(id) {
			break
		}
		size := int(u32(b, pos + 4))
		if version == 4 {
			size = syncsafe(b, pos+4)
		}
		if size == 0 {
			break
		}
		// Clamp the declared size to the tag end so a corrupt frame cannot
		// read metadata out of the audio payload (mirrors the TS decoder).
		avail := end - (pos + 10)
		if avail < 0 {
			avail = 0
		}
		if size > avail {
			size = avail
		}
		text := decodeID3Text(b, pos+10, size)
		switch id {
		case "TIT2":
			meta.Title = text
		case "TPE1":
			meta.Artist = text
		case "TALB":
			meta.Album = text
		}
		pos += 10 + size
	}
	return meta
}

// probeWav walks the little-endian RIFF chunks: fmt carries the byte
// rate, data its size — their quotient is the duration.
func probeWav(b []byte) *MediaMeta {
	meta := &MediaMeta{Kind: "wav"}
	pos := 12 // past RIFF size + WAVE
	byteRate := uint32(0)
	for pos+8 <= len(b) {
		id := ascii(b, pos, 4)
		size := int(u32le(b, pos+4))
		if id == "fmt " && pos+8+16 <= len(b) {
			byteRate = u32le(b, pos+16) // byteRate sits at fmt payload offset 8
		} else if id == "data" && byteRate > 0 {
			meta.DurationSec = f64ptr(float64(size) / float64(byteRate))
			break
		}
		pos += 8 + size + (size % 2) // chunks are word-aligned
	}
	return meta
}

// Probe inspects a media file's leading bytes and returns its kind plus
// whatever metadata the signature exposes. It returns nil only for
// inputs too short (< 12 bytes) to even carry a signature.
func Probe(b []byte) *MediaMeta {
	if len(b) < 12 {
		return nil
	}
	if ascii(b, 4, 4) == "ftyp" {
		return probeMp4(b)
	}
	if ascii(b, 0, 3) == "ID3" {
		return probeId3(b)
	}
	if ascii(b, 0, 4) == "RIFF" && ascii(b, 8, 4) == "WAVE" {
		return probeWav(b)
	}
	if ascii(b, 0, 4) == "GIF8" {
		return &MediaMeta{
			Kind:   "gif",
			Width:  f64ptr(float64(u16(b, 6))),
			Height: f64ptr(float64(u16(b, 8))),
		}
	}
	if b[0] == 0x1a && b[1] == 0x45 && b[2] == 0xdf && b[3] == 0xa3 {
		return &MediaMeta{Kind: "webm"}
	}
	return &MediaMeta{Kind: "unknown"}
}

Also available in 9 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →