Skip to content

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 →