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 →