Skip to content

Media Metadata Inspector — Java 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 Java implementation — the same logic the interactive tool runs, in a shareable, citable form.

// Media Metadata Inspector — read container metadata straight from the bytes.
//
// Language: Java (17+), 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
// ID3 tags. Deterministic; probe() returns kind=UNKNOWN for inputs too short
// for a signature — never throws.

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public final class MediaMetadataInspector {

  public enum Kind { MP4, MP3, WAV, GIF, WEBM, UNKNOWN }

  public record MediaMeta(
      Kind kind,
      Optional<Double> durationSec,
      Optional<Double> width,
      Optional<Double> height,
      Optional<String> title,
      Optional<String> artist,
      Optional<String> album) {
    static MediaMeta unknown() {
      return new MediaMeta(Kind.UNKNOWN, Optional.empty(), Optional.empty(),
          Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty());
    }
  }

  private record Box(String type, int start, int end) {}

  private static int u16be(byte[] b, int o) { return (b[o] & 0xFF) << 8 | (b[o + 1] & 0xFF); }
  private static int u16le(byte[] b, int o) { return (b[o] & 0xFF) | (b[o + 1] & 0xFF) << 8; }
  private static long u32be(byte[] b, int o) {
    return (b[o] & 0xFFL) << 24 | (b[o + 1] & 0xFFL) << 16 | (b[o + 2] & 0xFFL) << 8 | (b[o + 3] & 0xFFL);
  }
  private static long u32le(byte[] b, int o) {
    return (b[o] & 0xFFL) | (b[o + 1] & 0xFFL) << 8 | (b[o + 2] & 0xFFL) << 16 | (b[o + 3] & 0xFFL) << 24;
  }
  private static String ascii(byte[] b, int start, int len) {
    return new String(b, start, len, StandardCharsets.ISO_8859_1);
  }

  /** MP4 box walk: payload ranges for every box of the wanted types. */
  private static List<Box> findBoxes(byte[] b, int pos, int end, String... wanted) {
    List<Box> found = new ArrayList<>();
    while (pos + 8 <= end) {
      long size = u32be(b, pos);
      if (size == 1) return found;                 // 64-bit sizes unsupported — stop safely
      if (size == 0) size = end - pos;             // extends to the end
      if (size < 8 || pos + size > end) return found;
      String type = ascii(b, pos + 4, 4);
      for (String w : wanted)
        if (type.equals(w)) found.add(new Box(type, pos + 8, (int) (pos + size)));
      pos += size;
    }
    return found;
  }

  private static void probeMp4(byte[] b, int len, Builder m) {
    m.kind = Kind.MP4;
    for (Box moov : findBoxes(b, 0, len, "moov")) {
      for (Box mvhd : findBoxes(b, moov.start(), moov.end(), "mvhd")) {
        int version = b[mvhd.start()] & 0xFF;
        if (version == 0 && mvhd.end() - mvhd.start() >= 20) {
          long timescale = u32be(b, mvhd.start() + 12);
          long duration = u32be(b, mvhd.start() + 16);
          if (timescale > 0) m.durationSec = Optional.of((double) duration / timescale);
        } else if (version == 1 && mvhd.end() - mvhd.start() >= 32) {
          long timescale = u32be(b, mvhd.start() + 20);
          // 64-bit duration as two 32-bit halves — no long overflow (unsigned via &).
          double duration = u32be(b, mvhd.start() + 24) * 4294967296.0 + u32be(b, mvhd.start() + 28);
          if (timescale > 0) m.durationSec = Optional.of(duration / timescale);
        }
      }
      for (Box trak : findBoxes(b, moov.start(), moov.end(), "trak")) {
        List<Box> tkhds = findBoxes(b, trak.start(), trak.end(), "tkhd");
        if (!tkhds.isEmpty() && b[tkhds.get(0).start()] == 0
            && tkhds.get(0).end() - tkhds.get(0).start() >= 84) {
          // Width/height ride in the high 16 bits of fixed-point 16.16 values.
          m.width = Optional.of(u32be(b, tkhds.get(0).start() + 76) / 65536.0);
          m.height = Optional.of(u32be(b, tkhds.get(0).start() + 80) / 65536.0);
        }
        if (m.width.isPresent()) break;  // first track is enough
      }
    }
  }

  private static int syncsafe(byte[] b, int o) {
    return (b[o] & 0x7F) << 21 | (b[o + 1] & 0x7F) << 14 | (b[o + 2] & 0x7F) << 7 | (b[o + 3] & 0x7F);
  }

  /** ID3 text frame: encoding byte (0 latin1, 3 utf-8), then the text. */
  private static String id3Text(byte[] b, int start, int len) {
    if (len <= 1) return "";
    int enc = b[start] & 0xFF;
    byte[] text = new byte[len - 1];
    System.arraycopy(b, start + 1, text, 0, len - 1);
    int n = 0;
    while (n < text.length && text[n] != 0) n++;  // stop at the null terminator
    // Encodings 1/2 (UTF-16) are handled by the TS reference; latin-1 and
    // UTF-8 cover the practical files — decode latin-1 explicitly so bytes
    // above 0x7F survive.
    return new String(text, 0, n, enc == 3 ? StandardCharsets.UTF_8 : StandardCharsets.ISO_8859_1);
  }

  private static void probeId3(byte[] b, int len, Builder m) {
    m.kind = Kind.MP3;
    int tagSize = syncsafe(b, 6);
    int end = Math.min(10 + tagSize, len);
    int pos = 10;
    while (pos + 10 <= end) {
      String id = ascii(b, pos, 4);
      if (id.charAt(0) == 0) break;  // padding
      int frameSize = syncsafe(b, pos + 4);  // v2.4 syncsafe; v2.3 differs
      if (frameSize == 0 || pos + 10 + frameSize > end) break;
      String text = id3Text(b, pos + 10, frameSize);
      if (id.equals("TIT2") && !text.isEmpty()) m.title = Optional.of(text);
      if (id.equals("TPE1") && !text.isEmpty()) m.artist = Optional.of(text);
      if (id.equals("TALB") && !text.isEmpty()) m.album = Optional.of(text);
      pos += 10 + frameSize;
    }
  }

  public static MediaMeta probe(byte[] bytes) {
    if (bytes.length < 12) return MediaMeta.unknown();  // too short for ANY signature
    Builder m = new Builder();
    String at4 = ascii(bytes, 4, 4);

    if (at4.equals("ftyp")) {
      probeMp4(bytes, bytes.length, m);
    } else if (new String(bytes, 0, 3, StandardCharsets.ISO_8859_1).equals("ID3")) {
      probeId3(bytes, bytes.length, m);
    } else if (ascii(bytes, 0, 4).equals("RIFF") && at4.equals("WAVE")) {
      m.kind = Kind.WAV;
      long byteRate = u32le(bytes, 28);  // PCM fmt chunk: byteRate at offset 28
      if (byteRate > 0 && bytes.length > 44)
        m.durationSec = Optional.of((bytes.length - 44) / (double) byteRate);
    } else if (ascii(bytes, 0, 4).equals("GIF8")) {
      m.kind = Kind.GIF;
      m.width = Optional.of((double) u16le(bytes, 6));   // logical screen descriptor
      m.height = Optional.of((double) u16le(bytes, 8));
    } else if ((bytes[0] & 0xFF) == 0x1A && (bytes[1] & 0xFF) == 0x45
        && (bytes[2] & 0xFF) == 0xDF && (bytes[3] & 0xFF) == 0xA3) {
      m.kind = Kind.WEBM;
      // Duration lives in a float SecsInfo element — the TS lib scans for it;
      // this port flags the kind and leaves duration unset (documented gap).
    }
    return m.build();
  }

  /** Mutable staging for the immutable record — Optional fields default empty. */
  private static final class Builder {
    Kind kind = Kind.UNKNOWN;
    Optional<Double> durationSec = Optional.empty();
    Optional<Double> width = Optional.empty();
    Optional<Double> height = Optional.empty();
    Optional<String> title = Optional.empty();
    Optional<String> artist = Optional.empty();
    Optional<String> album = Optional.empty();
    MediaMeta build() {
      return new MediaMeta(kind, durationSec, width, height, title, artist, album);
    }
  }

  public static void main(String[] args) {
    // Smoke: a minimal MP4-sized buffer with an ftyp brand at offset 4.
    byte[] mp4 = new byte[32];
    System.arraycopy("ftypisom".getBytes(StandardCharsets.ISO_8859_1), 0, mp4, 4, 8);
    System.out.println(probe(mp4).kind());  // MP4
    System.out.println(probe(new byte[3]).kind());  // UNKNOWN — too short
  }
}

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 →