Media Metadata Inspector — C 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 C implementation — the same logic the interactive tool runs, in a shareable, citable form.
/* Media Metadata Inspector — read container metadata straight from the bytes.
*
* Language: C (C11), standard library only
* CosmoDev polyglot showcase port of the `media-metadata-inspector` tool.
* Ported from src/lib/media-meta.ts — display source, part of CosmoDev's
* polyglot tool pages.
*
* Sniffs the container signature (MP4 "ftyp" box, "ID3", RIFF/WAVE, GIF8,
* WebM EBML magic), then walks that container for duration, dimensions, and
* tags. Deterministic; probe() returns 0 ("unknown"-style result) for inputs
* too short for a signature — NULL only on allocation failure. */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* ── Result type ────────────────────────────────────────────────────────── */
typedef enum { MM_MP4, MM_MP3, MM_WAV, MM_GIF, MM_WEBM, MM_UNKNOWN } MediaKind;
typedef struct {
MediaKind kind;
int has_duration; /* duration_sec is -1 when absent. */
double duration_sec;
int width, height; /* -1 = unknown. */
char title[257], artist[257], album[257]; /* empty = absent. */
} MediaMeta;
static void meta_init(MediaMeta *m, MediaKind kind) {
memset(m, 0, sizeof *m);
m->kind = kind;
m->duration_sec = -1.0;
m->width = m->height = -1;
}
static const char *kind_name(MediaKind k) {
switch (k) {
case MM_MP4: return "mp4"; case MM_MP3: return "mp3";
case MM_WAV: return "wav"; case MM_GIF: return "gif";
case MM_WEBM: return "webm"; default: return "unknown";
}
}
/* ── Byte readers ───────────────────────────────────────────────────────── */
/* Trap: shifting a signed/char byte into the high bit is UB, so read into
* unsigned first. MP4/ID3 are big-endian; RIFF/WAV is little-endian. */
static uint32_t u16be(const uint8_t *b, size_t o) {
return (uint32_t)b[o] << 8 | b[o + 1];
}
static uint32_t u32be(const uint8_t *b, size_t o) {
return (uint32_t)b[o] << 24 | (uint32_t)b[o + 1] << 16 |
(uint32_t)b[o + 2] << 8 | b[o + 3];
}
static uint32_t u32le(const uint8_t *b, size_t o) {
return (uint32_t)b[o] | (uint32_t)b[o + 1] << 8 |
(uint32_t)b[o + 2] << 16 | (uint32_t)b[o + 3] << 24;
}
/* 4 ASCII bytes as a comparable value ("ftyp" == FOUR(b, 4)). */
static uint32_t fourcc(const uint8_t *b, size_t o) {
return (uint32_t)b[o] << 24 | (uint32_t)b[o + 1] << 16 |
(uint32_t)b[o + 2] << 8 | b[o + 3];
}
#define FOUR(s) (((uint32_t)(s)[0] << 24) | ((uint32_t)(s)[1] << 16) | \
((uint32_t)(s)[2] << 8) | (uint32_t)(s)[3])
/* ── MP4 box walk ───────────────────────────────────────────────────────── */
/* Calls fn(payload, payload_len) for every box matching `want` between
* start..end. Returns nonzero if fn asked to stop early. */
typedef int (*BoxFn)(const uint8_t *payload, size_t len, void *ctx);
static int walk_boxes(const uint8_t *b, size_t pos, size_t end,
uint32_t want, BoxFn fn, void *ctx) {
while (pos + 8 <= end) {
uint32_t size = u32be(b, pos);
if (size == 1) return 0; /* 64-bit sizes unsupported — stop. */
if (size == 0) size = (uint32_t)(end - pos); /* box runs to the end */
if (size < 8 || pos + size > end) return 0;
if (fourcc(b, pos + 4) == want)
if (fn(b + pos + 8, size - 8, ctx)) return 1;
pos += size;
}
return 0;
}
typedef struct {
MediaMeta *meta;
int got_mvhd;
} MoovCtx;
/* moov -> mvhd gives duration. */
static int on_mvhd(const uint8_t *p, size_t len, void *ctx) {
MediaMeta *m = ((MoovCtx *)ctx)->meta;
((MoovCtx *)ctx)->got_mvhd = 1;
if (p[0] == 0 && len >= 20) {
uint32_t timescale = u32be(p, 12), duration = u32be(p, 16);
if (timescale > 0) {
m->duration_sec = (double)duration / timescale;
m->has_duration = 1;
}
} else if (p[0] == 1 && len >= 32) {
uint32_t timescale = u32be(p, 20);
/* v1 duration is 64-bit: fold halves into a double (no __int128 in
* the standard's integer-model sense on MSVC; double is fine here). */
double duration = (double)u32be(p, 24) * 4294967296.0 + u32be(p, 28);
if (timescale > 0) {
m->duration_sec = duration / timescale;
m->has_duration = 1;
}
}
return 0;
}
/* The first trak's tkhd (v0) gives width/height as 16.16 fixed-point. */
static int on_tkhd(const uint8_t *p, size_t len, void *ctx) {
MediaMeta *m = ctx;
if (p[0] == 0 && len >= 84) {
m->width = (int)(u32be(p, 76) / 65536);
m->height = (int)(u32be(p, 80) / 65536);
return 1; /* first track is enough */
}
return 0;
}
static int on_trak(const uint8_t *p, size_t len, void *ctx) {
if (walk_boxes(p, 0, len, FOUR("tkhd"), on_tkhd, ctx))
return 1; /* got dimensions — stop iterating tracks */
return 0;
}
static int on_moov(const uint8_t *p, size_t len, void *ctx) {
walk_boxes(p, 0, len, FOUR("mvhd"), on_mvhd, ctx);
walk_boxes(p, 0, len, FOUR("trak"), on_trak, ((MoovCtx *)ctx)->meta);
return 0;
}
static void probe_mp4(const uint8_t *b, size_t n, MediaMeta *m) {
MoovCtx ctx = { m, 0 };
walk_boxes(b, 0, n, FOUR("moov"), on_moov, &ctx);
}
/* ── ID3v2 ──────────────────────────────────────────────────────────────── */
/* Syncsafe ints (ID3v2.4 sizes) keep every byte below 0x80. */
static uint32_t syncsafe(const uint8_t *b, size_t o) {
return (uint32_t)b[o] << 21 | (uint32_t)b[o + 1] << 14 |
(uint32_t)b[o + 2] << 7 | b[o + 3];
}
/* Decode one ID3 text frame into `out` (cap includes the NUL). Encodings:
* 0 = ISO-8859-1, 1 = UTF-16 with BOM, 2 = UTF-16BE, 3 = UTF-8. */
static void decode_id3_text(const uint8_t *bytes, size_t start, size_t len,
char *out, size_t cap) {
size_t o = 0;
if (len == 0 || cap < 2) { if (cap) out[0] = '\0'; return; }
const uint8_t *text = bytes + start + 1; /* first byte was the encoding */
len--;
if (bytes[start] == 3) { /* UTF-8 — pass through, stop at NUL. */
for (size_t i = 0; i < len && o + 1 < cap && text[i]; i++)
out[o++] = (char)text[i];
} else if (bytes[start] == 1 || bytes[start] == 2) {
int be = bytes[start] == 2; /* 2 = BOM-less big-endian. */
size_t i = 0;
if (bytes[start] == 1 && len >= 2 &&
(text[0] == 0xff || text[0] == 0xfe)) {
be = text[0] == 0xfe; /* BOM sniffs the endianness. */
i = 2;
}
for (; i + 1 < len && o + 1 < cap; i += 2) {
uint32_t code = be ? (uint32_t)text[i] << 8 | text[i + 1]
: text[i] | (uint32_t)text[i + 1] << 8;
if (code == 0) break; /* null terminator. */
/* Keep BMP printable text; drop the rest (display snippet). */
if (code >= 0x20 && code < 0x7f) out[o++] = (char)code;
}
} else { /* ISO-8859-1 — map 1:1 in ASCII range. */
for (size_t i = 0; i < len && o + 1 < cap && text[i]; i++)
out[o++] = (char)text[i];
}
out[o] = '\0';
}
static int frame_id_ok(const uint8_t *p) {
for (int i = 0; i < 4; i++) {
char c = (char)p[i];
if (!((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))) return 0;
}
return 1;
}
static void probe_id3(const uint8_t *b, size_t n, MediaMeta *m) {
uint32_t version = b[3];
uint32_t tag_size = syncsafe(b, 6);
size_t end = 10 + (size_t)tag_size;
if (end > n) end = n; /* clamp to what we actually have. */
size_t pos = 10;
while (pos + 10 <= end) {
if (!frame_id_ok(b + pos)) break;
uint32_t size = version == 4 ? syncsafe(b, pos + 4) : u32be(b, pos + 4);
if (size == 0) break;
/* Clamp the declared size so a corrupt frame stays inside the tag. */
size_t avail = end - (pos + 10);
if (avail > size) avail = size;
uint32_t id = fourcc(b, pos);
if (id == FOUR("TIT2")) decode_id3_text(b, pos + 10, avail, m->title, sizeof m->title);
else if (id == FOUR("TPE1")) decode_id3_text(b, pos + 10, avail, m->artist, sizeof m->artist);
else if (id == FOUR("TALB")) decode_id3_text(b, pos + 10, avail, m->album, sizeof m->album);
pos += 10 + size;
}
}
/* ── RIFF/WAVE ──────────────────────────────────────────────────────────── */
static void probe_wav(const uint8_t *b, size_t n, MediaMeta *m) {
size_t pos = 12; /* past RIFF size + "WAVE". */
uint32_t byte_rate = 0;
while (pos + 8 <= n) {
uint32_t size = u32le(b, pos + 4);
if (fourcc(b, pos) == FOUR("fmt ") && pos + 8 + 16 <= n) {
byte_rate = u32le(b, pos + 16); /* byteRate at fmt payload +8. */
} else if (fourcc(b, pos) == FOUR("data") && byte_rate > 0) {
m->duration_sec = (double)size / byte_rate;
m->has_duration = 1;
break;
}
pos += 8 + size + (size % 2); /* chunks are word-aligned. */
}
}
/* ── Entry point ────────────────────────────────────────────────────────── */
/* Fills *out; returns 1 on success, 0 when input is under 12 bytes. */
int media_probe(const uint8_t *b, size_t n, MediaMeta *out) {
if (n < 12) return 0;
if (fourcc(b, 4) == FOUR("ftyp")) {
meta_init(out, MM_MP4);
probe_mp4(b, n, out);
} else if (b[0] == 'I' && b[1] == 'D' && b[2] == '3') {
meta_init(out, MM_MP3);
probe_id3(b, n, out);
} else if (fourcc(b, 0) == FOUR("RIFF") && fourcc(b, 8) == FOUR("WAVE")) {
meta_init(out, MM_WAV);
probe_wav(b, n, out);
} else if (memcmp(b, "GIF8", 4) == 0) {
meta_init(out, MM_GIF);
out->width = (int)u16be(b, 6); /* GIF dims are little-endian. */
out->height = (int)(b[8] | (uint32_t)b[9] << 8);
} else if (b[0] == 0x1a && b[1] == 0x45 && b[2] == 0xdf && b[3] == 0xa3) {
meta_init(out, MM_WEBM); /* EBML magic; walking it is a no-op. */
} else {
meta_init(out, MM_UNKNOWN);
}
return 1;
}
static void print_meta(const char *label, const MediaMeta *m) {
printf("%s: kind=%s duration=%s%.3fs width=%d height=%d\n", label,
kind_name(m->kind), m->has_duration ? "" : "n/a",
m->has_duration ? m->duration_sec : 0.0, m->width, m->height);
if (m->title[0]) printf(" title=%s\n", m->title);
if (m->artist[0]) printf(" artist=%s\n", m->artist);
if (m->album[0]) printf(" album=%s\n", m->album);
}
static void box(unsigned char *p, size_t len, const char *type) { /* helper */
p[0] = (unsigned char)(len >> 24); p[1] = (unsigned char)(len >> 16);
p[2] = (unsigned char)(len >> 8); p[3] = (unsigned char)len;
memcpy(p + 4, type, 4);
}
int main(void) {
/* MP4: ftyp + moov{mvhd(v0, 1000 ticks/s, 6.042s) + trak{tkhd 640x360}}. */
unsigned char mp4[8 + 8 + 8 + 20 + 8 + 8 + 84 + 4 /*free pad to >=12 sig*/];
memset(mp4, 0, sizeof mp4);
box(mp4, 16, "ftyp"); memcpy(mp4 + 8, "isom", 4);
size_t o = 16;
box(mp4 + o, sizeof mp4 - o, "moov"); o += 8;
box(mp4 + o, 28, "mvhd"); o += 8;
mp4[o + 12] = 0; mp4[o + 13] = 0; mp4[o + 14] = 0x03; mp4[o + 15] = 0xe8; /* ts=1000 */
mp4[o + 16] = 0; mp4[o + 17] = 0; mp4[o + 18] = 0x17; mp4[o + 19] = 0xaa; /* dur=6042 */
o += 20;
box(mp4 + o, 100, "trak"); o += 8;
box(mp4 + o, 92, "tkhd"); o += 8;
mp4[o + 76] = 0x0a; /* width 640.0 in 16.16 fixed */ mp4[o + 77] = 0x00;
mp4[o + 80] = 0x05; mp4[o + 81] = 0xa0; /* height 360.0 */
/* MP3: ID3v2.3, one TIT2 frame, ISO-8859-1 text "Cosmo Dev". */
unsigned char mp3[10 + 10 + 10];
memcpy(mp3, "ID3", 3); mp3[3] = 3; /* v2.3 */ memset(mp3 + 4, 0, 2);
mp3[6] = 0; mp3[7] = 0; mp3[8] = 0; mp3[9] = 20; /* tag size 20 (ss) */
memcpy(mp3 + 10, "TIT2", 4);
mp3[14] = 0; mp3[15] = 0; mp3[16] = 0; mp3[17] = 10; /* frame size 10 */
mp3[18] = 0; /* encoding 0 */
memcpy(mp3 + 20, "Cosmo Dev", 9); mp3[29] = 0;
/* WAV: 8000 Hz, 16-bit mono → byteRate 16000, data 32000B → 2s. */
unsigned char wav[] = {
'R','I','F','F', 0,0,0,0, 'W','A','V','E',
'f','m','t',' ', 16,0,0,0, 1,0, 1,0, 0x40,0x1f,0,0, 0x80,0x3e,0,0,
2,0, 16,0,
'd','a','t','a', 0x00,0x7d,0x00,0x00 /* 32000 */
};
/* GIF89a, 320×200 logical screen. */
unsigned char gif[] = {
'G','I','F','8','9','a', 0x40,0x01, 0xc8,0x00, 0x00, 0x00
};
const struct { const char *label; const unsigned char *b; size_t n; } cases[] = {
{ "mp4", mp4, sizeof mp4 },
{ "mp3", mp3, sizeof mp3 },
{ "wav", wav, sizeof wav },
{ "gif", gif, sizeof gif },
};
for (size_t i = 0; i < sizeof cases / sizeof cases[0]; i++) {
MediaMeta m;
if (!media_probe(cases[i].b, cases[i].n, &m)) {
fprintf(stderr, "%s: input too short\n", cases[i].label);
return 1;
}
print_meta(cases[i].label, &m);
}
return 0;
}
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 →