Files
skills/docs/media/audio/audio-codecs/src/detect/detect-format.md
2026-05-29 08:20:56 +01:00

5.7 KiB

detect/detect-format

Format detection and source-resolution helpers — the runtime-gated entry point that turns any Source (string path, URL, ArrayBuffer, Uint8Array, ReadableStream) into a header slice plus an optional detected format name.

(no submodules)

Purpose

The package handles five concrete Source shapes; each needs a different platform-specific resolution strategy. Without a single helper, every public entry point (decodeSource, encodeTo, progressiveDecode) would re-implement the same string | URL | ArrayBuffer | Uint8Array | ReadableStream switch — duplicating wiring and risking divergent behaviour for the edge cases. This module centralises it.

The detection delegates magic-byte sniffing to @suaveplan/audio-metadata's detectAudioFormat — the same function used by every other package that needs to identify audio bytes — so the registry's detect and the entry-point detectSource always agree.

Features

  • Source type union — string | URL | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array>.
  • DetectResult shape — { format: string | undefined; bytes?: Uint8Array; path?: string; reader?: ReadableStreamDefaultReader }. The optional fields tell the caller what kind of resolution happened so they can continue draining the source efficiently.
  • detectSource(source) — reads at most HEADER_SIZE (256 KiB) bytes from the source head, sniffs the format, and returns either the detected format name or undefined for unknown content. Never throws for unknown content — only throws INVALID_SOURCE for a source value that is not a recognised variant or a fetch that returned no body.
  • resolveSource(source) — drains the source completely into a single Uint8Array. Used by decodeSource to materialise the full payload before dispatch.
  • HEADER_SIZE — exported constant set to 256 * 1024. Reading exactly this many bytes keeps detection cheap even for multi-GB files while being large enough to cover every realistic header layout (ID3v2 with embedded artwork, AIFF with large COMM-adjacent comment chunks, etc).

Resolution rules

  • string starting with http:// / https:// / data: / blob:, or any URL instance → resolved via fetch(). Header read via the response stream reader, which is returned to the caller for continued draining.
  • string starting with file:// → resolved via dynamic import('node:fs/promises'). Returns the resolved file path so the caller can perform random-access reads.
  • Other bare string → on a server runtime (process.versions.node is a string) resolves via fs/promises.readFile; in a browser runtime resolves via fetch().
  • ArrayBuffer / Uint8Array → header is the first 256 KiB slice; no platform call.
  • ReadableStream<Uint8Array> → reader drains until 256 KiB observed; the original reader is returned for continued draining.

Usage: Basic Example

import { detectSource } from "@suaveplan/audio-codecs/detect/detect-format";

const result = await detectSource(bytes);
if (result.format === "wav") {
    decodeWav(result.bytes!);
}

Usage: Advanced Example

Resolve a remote URL once and reuse the detection result + stream reader without re-fetching:

import { detectSource, resolveSource } from "@suaveplan/audio-codecs/detect/detect-format";

const result = await detectSource("https://cdn.example/audio.flac");
if (result.format === "flac") {
    // We have the first 256 KiB cached on result.bytes, and the rest
    // is available via result.reader.
    const rest = await drainReader(result.reader!);
    const full = concat(result.bytes!, rest);
    return decodeFlac(full);
}

// resolveSource fully drains regardless of source shape.
const buf = await resolveSource("file:///srv/audio/in.wav");

API Reference

  • type Source = string | URL | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array>
  • interface DetectResult { format: string | undefined; bytes?: Uint8Array; path?: string; reader?: ReadableStreamDefaultReader<Uint8Array> }
  • const HEADER_SIZE: number — 256 * 1024
  • async function detectSource(source: Source): Promise<DetectResult> — throws AUDIO_CODECS.INVALID_SOURCE for an unrecognised source value or when fetch() returns a body-less response.
  • async function resolveSource(source: Source): Promise<Uint8Array> — throws AUDIO_CODECS.INVALID_SOURCE for an unrecognised source.

Implementation Notes

  • isServerRuntime() is a closure over typeof process !== "undefined" && typeof process.versions?.node === "string". The function exists so the runtime branch is not evaluated at module-load time — the package stays universal with no static node:fs import.
  • The node:fs/promises import is always inside a runtime-gated dynamic import() — never a top-level import. This keeps the package importable from browser bundles without bundler config tweaks.
  • The browser-only fetch() fallback paths (lines 154-156 in detectSource, 214-216 in resolveSource) are unreachable under Node tests; structural coverage of that branch requires a happy-dom or browser-target test harness (a future integration suite).
  • The URL instance branch and the http:// / https:// / data: / blob: string branch both route through fetchHeaderViaFetch — no logic is duplicated.
  • readHeaderFromStream keeps reading until at least HEADER_SIZE bytes are observed OR the stream ends. The resulting buffer may be larger than HEADER_SIZE because chunks arrive whole; that's intentional — over-read is cheap and avoids fragmenting the caller's understanding of what bytes were consumed.