Media Metadata Inspector — Swift 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 Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.
// Media Metadata Inspector — read container metadata straight from the bytes.
//
// Language: Swift 5.9+ (Foundation only)
// 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; nil only for inputs too short for a signature.
//
// Endianness trap: MP4 and ID3 fields are big-endian, while RIFF/WAVE fields
// and GIF dimensions are little-endian — the readers below are named for the
// byte order they produce, because mixing them up is the classic bug here.
// Swift's shift operators also require the shift amount to fit the left-hand
// type, so byte assembly goes through UInt32/UInt16 widening first.
import Foundation
// MARK: - Types
enum MediaKind: String {
case mp4, mp3, wav, gif, webm, unknown
}
/// Container metadata; nil optionals mean "container parsed, field absent".
struct MediaMeta {
var kind = MediaKind.unknown
var durationSec: Double? = nil
var width: Double? = nil
var height: Double? = nil
var title: String? = nil
var artist: String? = nil
var album: String? = nil
init(kind: MediaKind) {
self.kind = kind
}
}
// MARK: - Byte readers
/// 4 ASCII bytes at `start` as a String (box types, chunk ids, frame ids).
func ascii(_ b: [UInt8], _ start: Int, _ len: Int) -> String {
String(decoding: b[start..<(start + len)], as: UTF8.self)
}
/// Big-endian u32 — MP4 box headers, ID3 frame sizes, mvhd/tkhd fields.
func u32be(_ b: [UInt8], _ o: Int) -> UInt32 {
(UInt32(b[o]) << 24) | (UInt32(b[o + 1]) << 16)
| (UInt32(b[o + 2]) << 8) | UInt32(b[o + 3])
}
/// Little-endian u32 — RIFF chunk sizes, WAV byteRate.
func u32le(_ b: [UInt8], _ o: Int) -> UInt32 {
UInt32(b[o]) | (UInt32(b[o + 1]) << 8)
| (UInt32(b[o + 2]) << 16) | (UInt32(b[o + 3]) << 24)
}
/// Little-endian u16 — GIF canvas width/height.
func u16le(_ b: [UInt8], _ o: Int) -> UInt16 {
UInt16(b[o]) | (UInt16(b[o + 1]) << 8)
}
/// ID3 syncsafe integer: four 7-bit groups, so the high bit of every byte is
/// free to keep MPEG audio frame syncs unambiguous.
func syncsafe(_ b: [UInt8], _ o: Int) -> Int {
(Int(b[o]) << 21) | (Int(b[o + 1]) << 14) | (Int(b[o + 2]) << 7) | Int(b[o + 3])
}
// MARK: - MP4
/// One MP4 box: its type and the half-open range [start, end) of its payload.
struct Box {
let type: String
let start: Int
let end: Int
}
/// MP4 box walk: every box of a wanted type between `start` and `end`.
func findBoxes(_ bytes: [UInt8], _ start: Int, _ end: Int, _ wanted: [String]) -> [Box] {
var found: [Box] = []
var pos = start
while pos + 8 <= end {
var size = Int(u32be(bytes, pos))
if size == 1 { return found } // 64-bit sizes unsupported — stop safely.
if size == 0 { size = end - pos } // size 0: box extends to the end
if size < 8 || pos + size > end { return found }
let type = ascii(bytes, pos + 4, 4)
if wanted.contains(type) {
found.append(Box(type: type, start: pos + 8, end: pos + size))
}
pos += size
}
return found
}
/// MP4: moov -> mvhd gives duration; the first trak's tkhd gives dimensions.
func probeMp4(_ bytes: [UInt8]) -> MediaMeta {
var meta = MediaMeta(kind: .mp4)
for moov in findBoxes(bytes, 0, bytes.count, ["moov"]) {
for mvhd in findBoxes(bytes, moov.start, moov.end, ["mvhd"]) {
let version = bytes[mvhd.start]
if version == 0, mvhd.end - mvhd.start >= 20 {
let timescale = u32be(bytes, mvhd.start + 12)
if timescale > 0 {
meta.durationSec = Double(u32be(bytes, mvhd.start + 16)) / Double(timescale)
}
} else if version == 1, mvhd.end - mvhd.start >= 32 {
let timescale = u32be(bytes, mvhd.start + 20)
// 64-bit duration: high and low halves of the u64, each u32.
let duration = Double(u32be(bytes, mvhd.start + 24)) * 0x1_0000_0000
+ Double(u32be(bytes, mvhd.start + 28))
if timescale > 0 {
meta.durationSec = duration / Double(timescale)
}
}
}
for trak in findBoxes(bytes, moov.start, moov.end, ["trak"]) {
guard let tkhd = findBoxes(bytes, trak.start, trak.end, ["tkhd"]).first,
bytes[tkhd.start] == 0, tkhd.end - tkhd.start >= 84 else { continue }
// tkhd stores width/height as 16.16 fixed point — divide by 65536.
meta.width = Double(u32be(bytes, tkhd.start + 76)) / 65536
meta.height = Double(u32be(bytes, tkhd.start + 80)) / 65536
break // first track is enough
}
}
return meta
}
// MARK: - ID3v2
/// Decode one ID3 text frame (first byte selects the encoding).
func decodeId3Text(_ bytes: [UInt8], _ start: Int, _ len: Int) -> String {
if len <= 0 { return "" }
let encoding = bytes[start]
let text = Array(bytes[(start + 1)..<(start + len)])
if encoding == 3 {
// UTF-8 (ID3v2.4); stop at the null terminator.
let end = text.firstIndex(of: 0) ?? text.count
return String(decoding: text[0..<end], as: UTF8.self)
}
if encoding == 1 || encoding == 2 {
// UTF-16: encoding 1 carries a BOM (sniff endianness); 2 is BOM-less BE.
let bom = encoding == 1 && (text.first == 0xff || text.first == 0xfe)
let littleEndian = bom ? text[0] == 0xff : false
var units: [UInt16] = []
var i = bom ? 2 : 0
while i + 1 < text.count {
let code: UInt16 = littleEndian
? UInt16(text[i]) | (UInt16(text[i + 1]) << 8)
: (UInt16(text[i]) << 8) | UInt16(text[i + 1])
if code == 0 { break }
units.append(code)
i += 2
}
// String(decoding:as:) mirrors TextDecoder(): unpaired surrogates
// become U+FFFD instead of trapping.
return String(decoding: units, as: UTF16.self)
}
var out = ""
for byte in text {
if byte == 0 { break }
out.append(Character(UnicodeScalar(byte))) // ISO-8859-1 / ASCII range
}
return out
}
/// ID3v2: walk frames, keep TIT2 (title), TPE1 (artist), TALB (album).
func probeId3(_ bytes: [UInt8]) -> MediaMeta {
var meta = MediaMeta(kind: .mp3)
let version = bytes[3]
let tagSize = syncsafe(bytes, 6)
var pos = 10
let end = min(10 + tagSize, bytes.count)
while pos + 10 <= end {
let id = ascii(bytes, pos, 4)
guard id.range(of: "^[A-Z0-9]{4}$", options: .regularExpression) != nil else { break }
// ID3v2.4 frame sizes are syncsafe; v2.2/v2.3 are plain big-endian.
let size = version == 4 ? syncsafe(bytes, pos + 4) : Int(u32be(bytes, pos + 4))
if size == 0 { break }
// Clamp declared size to the tag end: a corrupt frame stays inside the tag.
let avail = max(end - (pos + 10), 0)
let text = decodeId3Text(bytes, pos + 10, min(size, avail))
switch id {
case "TIT2": meta.title = text
case "TPE1": meta.artist = text
case "TALB": meta.album = text
default: break
}
pos += 10 + size
}
return meta
}
// MARK: - WAV
/// WAV: fmt chunk gives byteRate; data chunk size / byteRate = duration.
func probeWav(_ bytes: [UInt8]) -> MediaMeta {
var meta = MediaMeta(kind: .wav)
var pos = 12 // past RIFF size + WAVE
var byteRate: UInt32 = 0
while pos + 8 <= bytes.count {
let id = ascii(bytes, pos, 4)
let size = u32le(bytes, pos + 4)
if id == "fmt " && pos + 8 + 16 <= bytes.count {
byteRate = u32le(bytes, pos + 16) // byteRate sits at fmt payload offset 8
} else if id == "data" && byteRate > 0 {
meta.durationSec = Double(size) / Double(byteRate)
break
}
pos += 8 + Int(size) + Int(size % 2) // chunks are word-aligned
}
return meta
}
// MARK: - Probe
/// Sniff the container and parse its metadata. nil when input is under 12
/// bytes — too short for any signature.
func probe(_ bytes: [UInt8]) -> MediaMeta? {
if bytes.count < 12 { return nil }
if ascii(bytes, 4, 4) == "ftyp" { return probeMp4(bytes) } // MP4 / MOV
if ascii(bytes, 0, 3) == "ID3" { return probeId3(bytes) }
if ascii(bytes, 0, 4) == "RIFF" && ascii(bytes, 8, 4) == "WAVE" {
return probeWav(bytes)
}
if ascii(bytes, 0, 4) == "GIF8" {
var meta = MediaMeta(kind: .gif)
meta.width = Double(u16le(bytes, 6))
meta.height = Double(u16le(bytes, 8))
return meta
}
if bytes[0] == 0x1a && bytes[1] == 0x45 && bytes[2] == 0xdf && bytes[3] == 0xa3 {
return MediaMeta(kind: .webm) // EBML magic
}
return MediaMeta(kind: .unknown)
}
// Usage:
// let meta = probe([UInt8](fileData))
// print(meta?.kind.rawValue ?? "nil", meta?.durationSec ?? "-", meta?.width ?? "-")
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 →