npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@audio/encode-mp4

v1.2.0

Published

Encode PCM audio samples to MP4/M4A (ISO BMFF) — pure JS muxer, remuxer and iTunes-tag writer

Readme

@audio/encode-mp4 npm MIT

Pure-JS MP4/M4A/MOV muxer, remuxer, and iTunes-tag writer

npm install @audio/encode-mp4
import mp4 from '@audio/encode-mp4'
import { mux } from '@audio/encode-mp4/mux'
import { remux } from '@audio/encode-mp4/remux'
import { writeMeta } from '@audio/encode-mp4/meta'

No wasm, no codec knowledge beyond wrapping pre-encoded access units in ISO/IEC 14496-12 (ISOBMFF) boxes. mux() wraps AAC, ALAC, Opus, FLAC, MP3, or raw PCM samples into a complete .m4a/.mp4 file (moov before mdat, so it streams/plays progressively). remux() swaps or strips the audio track of an existing MP4/MOV without touching video. writeMeta() rewrites iTunes-style tags in place. The default export drives a codec encoder end to end for the m4a umbrella format. decode-mp4 (this ecosystem's demuxer) and decode-aac/meta are the two things every box this package writes is checked against.

mux(track, opts) → Uint8Array

let file = mux({
  codec: 'aac',              // 'aac' | 'alac' | 'opus' | 'flac' | 'mp3' | 'pcm'
  sampleRate: 44100,
  channels: 2,
  samples,                   // Uint8Array[] — one access unit per entry (raw AAC AU, no ADTS)
  config: audioSpecificConfig,
  durations: 1024,           // per-AU duration in `timescale` units — a number or per-AU array
  priming: 2112,             // encoder delay, PCM samples — edit list + iTunSMPB (aac)
}, { meta: { title: 'Song' }, chapters: [{ time: 0, title: 'Intro' }] })

| track field | Default | | |---|---|---| | codec | — | aac | alac | opus | flac | mp3 | pcm (required) | | sampleRate, channels | — | required | | samples | — | Uint8Array[], one access unit per entry (required) | | durations | codec-specific | AAC 1024; ALAC frame length from the cookie; Opus per-packet (TOC); FLAC per-frame (header); MP3 1152/576; PCM computed from chunk length | | timescale | sampleRate (Opus: 48000) | sample-table timescale | | config | — | AAC: AudioSpecificConfig bytes. ALAC: 24-byte ALACSpecificConfig. Opus: { preSkip, outputGain?, channelMappingFamily?, channelMappingTable? }. FLAC: 34-byte STREAMINFO. PCM: { bits, float?, be? }. MP3: unused | | priming, padding | 0 | encoder delay / trailing pad, PCM samples — written as an edit-list media-time offset, and (AAC) as iTunSMPB | | bitrate | — | bits/sec, written into esds/btrt |

| opts field | Default | | |---|---|---| | brand | 'M4A ' | 'M4A ' | 'isom' | 'mp42' | 'qt ' — qt writes sowt/in24/fl32 QuickTime PCM atoms instead of ipcm/fpcm | | meta | — | same keys writeMeta reads back — see below | | chapters | — | [{ time (s), title }] — Nero chpl | | creationTime | now | Date |

Sample tables are compact: stts run-length compressed, stsz uses the constant-size shortcut when every AU is the same length, stsc/stco chunk every ~1s or 128 AUs (whichever comes first), co64 only when an offset would exceed 4GB. Everything is written into one preallocated, growable buffer — no per-box array spreads — so muxing a 1-hour AAC track (~155k AUs) takes ~12ms.

remux(src, audio, opts) → Uint8Array

let strippedVideo = remux(videoBytes, null)                 // drop the audio track
let dubbed = remux(videoBytes, { codec: 'opus', ... })       // replace it (track object, mux() shape)
let dubbed2 = remux(videoBytes, mux({ codec: 'alac', ... })) // or a Uint8Array mux() already produced

Video (and any other non-audio track — subtitles, timecode) is copied through unchanged; only its stco/co64 chunk offsets are rewritten (stco→co64 if the new layout pushes an offset past 4GB). A single fresh mdat interleaves video and audio chunks by presentation time so the result still plays progressively. Handles 64-bit box sizes, uuid boxes, mdat before moov, and multiple mdats. Throws a clear error on a fragmented MP4 (moof) — this is a whole-file muxer, not a fragmented-MP4 writer.

writeMeta(bytes, { meta, chapters }) → Uint8Array

Rewrites moov/udta/meta/ilst (+ chpl for chapters) in an existing MP4/M4A. Moov is kept first — not relocated to the end — so when it grows, every track's chunk offsets are shifted by the same delta (only udta's length changes; nothing else in moov does). meta keys are the exact inverse of decode-aac/meta's parseMeta: title artist album albumartist composer genre year track disc comment lyrics copyright bpm key isrc publisher software pictures. key/isrc/publisher are written as ---- freeform atoms (the com.apple.iTunes convention documented by mutagen/AtomicParsley) — no shipped parser in this ecosystem reads them back yet, so they're write-only.

mp4(opts) → { encode(channels), flush(), free() }

let enc = await mp4({ sampleRate: 44100, channels: 2, codec: 'flac' })
enc.encode(channelData)   // buffers; always returns an empty Uint8Array
let file = await enc.flush()
enc.free()

Drives @audio/encode-aac (AAC, browser-only — needs AudioEncoder), @audio/encode-opus/core (Opus), @audio/encode-flac (FLAC), @audio/encode-mp3 (MP3), or plain interleaving (PCM), then mux()s the result. Whole-file container: encode() buffers, flush() returns the complete file (same shape as @audio/encode-qoa). codec defaults to 'aac' when globalThis.AudioEncoder exists (browser), else 'flac'. AAC's encoder delay isn't reported by WebCodecs, so priming defaults to 2112 samples — the standard AAC-LC value every LC encoder from FAAC to FDK-AAC uses; override it if you know better. encode() split into any number of chunks produces byte-identical PCM output and equivalent (SNR-consistent) lossy output to a single whole-buffer call.

Not done here: mux() doesn't trim MP3/Opus encoder delay and padding for you the way mp4() does (from LAME's Info tag, from the Opus encoder's lookahead): pass priming/padding yourself if you need bit-exact gapless MP3/Opus from mux() (Opus's own pre-skip is handled automatically by mux(), since it's intrinsic to every Opus packet stream, not an encoder-specific unknown). stco→co64 upgrade during writeMeta isn't implemented — it throws instead of writing a wrong offset.

Use when: writing .m4a/.mp4 output in the browser or Node without wasm; swapping the audio track of a video file (dubbing, replacing a soundtrack) without re-encoding video; tagging M4A files.

Spec references

  • ISO/IEC 14496-12 — ISO Base Media File Format (box structure, sample tables, edit lists)
  • ISO/IEC 14496-14 — MP4 file format
  • ISO/IEC 14496-1 §8.3 — esds descriptors (ES_Descriptor/DecoderConfigDescriptor/SLConfigDescriptor)
  • ISO/IEC 23003-5 — PCM in ISOBMFF (ipcm/fpcm/pcmC)
  • Opus in ISOBMFF — Opus sample entry + dOps
  • FLAC in ISOBMFF — fLaC sample entry + dfLa
  • QuickTime File Format — sound sample description versions 0/1/2, sowt/in24/fl32/enda
  • iTunes metadata atoms — mutagen.mp4, AtomicParsley docs
  • Nero chapters (chpl) — format per ffmpeg libavformat/movenc.c mov_write_chpl_tag

Streaming output

stream: true writes a fragmented file (ISO/IEC 14496-12 §8.8) as it encodes: ftyp + moov (sample entry, empty tables, mvex, tags and chapters) once the first units exist, then moof + mdat per second of media. Only the current fragment's units are held. head() after flush() returns the init segment rebuilt with what only the end knows (FLAC's STREAMINFO), or null.

License

MIT

ॐ