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
Sourcetype union —string | URL | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array>.DetectResultshape —{ 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 mostHEADER_SIZE(256 KiB) bytes from the source head, sniffs the format, and returns either the detected format name orundefinedfor unknown content. Never throws for unknown content — only throwsINVALID_SOURCEfor a source value that is not a recognised variant or a fetch that returned no body.resolveSource(source)— drains the source completely into a singleUint8Array. Used bydecodeSourceto materialise the full payload before dispatch.HEADER_SIZE— exported constant set to256 * 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
stringstarting withhttp:///https:///data:/blob:, or anyURLinstance → resolved viafetch(). Header read via the response stream reader, which is returned to the caller for continued draining.stringstarting withfile://→ resolved via dynamicimport('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 viafs/promises.readFile; in a browser runtime resolves viafetch(). 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 * 1024async function detectSource(source: Source): Promise<DetectResult>— throwsAUDIO_CODECS.INVALID_SOURCEfor an unrecognised source value or whenfetch()returns a body-less response.async function resolveSource(source: Source): Promise<Uint8Array>— throwsAUDIO_CODECS.INVALID_SOURCEfor an unrecognised source.
Implementation Notes
isServerRuntime()is a closure overtypeof 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 staticnode:fsimport.- The
node:fs/promisesimport is always inside a runtime-gated dynamicimport()— 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 indetectSource, 214-216 inresolveSource) are unreachable under Node tests; structural coverage of that branch requires a happy-dom or browser-target test harness (a future integration suite). - The
URLinstance branch and thehttp:///https:///data:/blob:string branch both route throughfetchHeaderViaFetch— no logic is duplicated. readHeaderFromStreamkeeps reading until at leastHEADER_SIZEbytes are observed OR the stream ends. The resulting buffer may be larger thanHEADER_SIZEbecause chunks arrive whole; that's intentional — over-read is cheap and avoids fragmenting the caller's understanding of what bytes were consumed.