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

taglib-wasm

v1.5.0

Published

TagLib-Wasm is the universal tagging library for TypeScript/JavaScript platforms: Browsers, Node.js, Deno, Bun, Cloudflare Workers, and Electron apps

Readme

TagLib-Wasm

Tests npm version npm downloads License: MIT Built with Built with Emscripten Built with WebAssembly Built with TagLib Node.js Deno Bun Cloudflare Workers Electron (Node.js) Browsers

TagLib-Wasm is the universal tagging library for TypeScript/JavaScript (TS|JS) platforms: Node.js, Deno, Bun, Cloudflare Workers, Electron (via Node.js), and browsers.

Features

  • Local filesystem support – On Node.js and Deno, WASI enables seek-based I/O that reads only headers and tags from disk — not entire files
  • Automatic runtime optimization – Auto-selects WASI (server) or Emscripten (browser) for optimal performance with no configuration
  • Full audio format support – Supports all audio formats supported by TagLib
  • TypeScript first – Complete type definitions and modern API
  • Wide TS/JS runtime support – Node.js, Deno, Bun, Electron (Node.js), Cloudflare Workers, and browsers
  • Format abstraction – Handles container format details automatically when possible
  • Rich metadata – Cover art, ratings, chapters (MP3 CHAP, MP4 QuickTime/Nero), and broadcast metadata (BWF bext/iXML for WAV/FLAC)
  • Zero dependencies – Self-contained Wasm bundle
  • Tested – 293 tests across all formats
  • Two API styles – Use the "Simple" API (3 functions), or the full "Core" API for more advanced applications
  • Batch folder operations – Scan directories, process multiple files, find duplicates, and export metadata catalogs

Installation

Node.js

npm install taglib-wasm

Note: Requires Node.js v22.6.0 or higher for WASI and WebAssembly exception handling support. To consume the package as TypeScript source (e.g., via tsx), see the installation guide.

Deno

import { TagLib } from "@charlesw/taglib-wasm";

Bun

bun add taglib-wasm

Electron (Node.js)

npm install taglib-wasm

taglib-wasm works in Electron's main process (which is Node.js). For the renderer process, expose metadata through IPC:

// Main process
import { TagLib } from "taglib-wasm";

See Platform Examples for full IPC setup.

Deno Compiled Binaries (Offline Support)

For Deno compiled binaries that need to work offline, you can embed the WASM file:

// 1. Prepare your build by copying the WASM file
import { prepareWasmForEmbedding } from "@charlesw/taglib-wasm";
await prepareWasmForEmbedding("./taglib-web.wasm");

// 2. In your application, use the helper for automatic handling
import { initializeForDenoCompile } from "@charlesw/taglib-wasm";
const taglib = await initializeForDenoCompile();

// 3. Compile with the embedded WASM
// deno compile --allow-read --include taglib-web.wasm myapp.ts

See the complete Deno compile guide for more options including CDN loading.

For manual control:

// Load embedded WASM in compiled binaries
const wasmBinary = await Deno.readFile(
  new URL("./taglib-web.wasm", import.meta.url),
);
const taglib = await TagLib.initialize({ wasmBinary });

Supplying wasmBinary selects the Emscripten backend (the bytes above are taglib-web.wasm, its artifact). The WASI backend loads from a filesystem path or URL instead — use wasmUrl with it.

Quick Start

Import paths: Deno uses @charlesw/taglib-wasm, npm uses taglib-wasm. Examples below use npm paths — substitute accordingly.

Simple API

import { applyTags, applyTagsToFile, readTags } from "taglib-wasm/simple";

// Read tags (string fields are arrays to support multi-value metadata)
const tags = await readTags("song.mp3");
console.log(tags.title?.[0], tags.artist?.[0], tags.album?.[0]);

// Apply tags and get modified buffer (in-memory)
const modifiedBuffer = await applyTags("song.mp3", {
  title: "New Title",
  artist: "New Artist",
  album: "New Album",
});

// Or update tags on disk (requires file path)
await applyTagsToFile("song.mp3", {
  title: "New Title",
  artist: "New Artist",
});

High-Performance Batch Processing

import { readMetadataBatch, readTagsBatch } from "taglib-wasm/simple";

// Process multiple files in parallel
const files = ["track01.mp3", "track02.mp3", /* ... */ "track20.mp3"];

// Read just tags (18x faster than sequential)
const tags = await readTagsBatch(files, { concurrency: 8 });

// Read complete metadata including cover art detection (15x faster)
const metadata = await readMetadataBatch(files, { concurrency: 8 });

// Real-world performance:
// Sequential: ~100 seconds for 20 files
// Batch: ~5 seconds for 20 files (20x speedup!)

Full API

The Full API might be a better choice for apps and utilities focused on advanced metadata management.

import { TagLib } from "taglib-wasm";

// Initialize taglib-wasm
const taglib = await TagLib.initialize();

// Load audio file (automatically cleaned up when scope exits)
using file = await taglib.open("song.mp3");

// Read and update metadata
const tag = file.tag();
tag.setTitle("New Title");
tag.setArtist("New Artist");

// Save changes
file.save();

Batch Folder Operations

Process entire music collections efficiently:

import { findDuplicates, scanFolder } from "taglib-wasm";

// Scan a music library
const result = await scanFolder("/path/to/music", {
  recursive: true,
  onProgress: (processed, total, file) => {
    console.log(`Processing ${processed}/${total}: ${file}`);
  },
});

console.log(`Found ${result.items.length} audio files`);
console.log(
  `Successfully processed ${
    result.items.filter((i) => i.status === "ok").length
  } files`,
);

// Process results
for (const file of result.items) {
  console.log(
    `${file.path}: ${file.tags.artist?.[0]} - ${file.tags.title?.[0]}`,
  );
  console.log(`Duration: ${file.properties?.duration}s`);
}

// Find duplicates
const duplicates = await findDuplicates("/path/to/music", {
  criteria: ["artist", "title"],
});
console.log(`Found ${duplicates.length} groups of duplicates`);

Working with Cover Art

import { applyCoverArt, readCoverArt } from "taglib-wasm/simple";

// Extract cover art
const coverData = await readCoverArt("song.mp3");
if (coverData) {
  await Deno.writeFile("cover.jpg", coverData);
}

// Set new cover art
const imageData = await Deno.readFile("new-cover.jpg");
const modifiedBuffer = await applyCoverArt("song.mp3", imageData, "image/jpeg");
// Save modifiedBuffer to file if needed

Working with Ratings

import { RatingUtils, TagLib } from "taglib-wasm";

const taglib = await TagLib.initialize();
using file = await taglib.open("song.mp3");

// Read rating (normalized 0.0-1.0)
const rating = file.getRating();
if (rating !== undefined) {
  console.log(`Rating: ${RatingUtils.toStars(rating)} stars`);
}

// Set rating (4 out of 5 stars)
file.setRating(0.8);
file.save();

See the Track Ratings Guide for RatingUtils API and cross-format conversion details.

Working with Chapters

import { TagLib } from "taglib-wasm";

const taglib = await TagLib.initialize();
using file = await taglib.open("audiobook.m4b");

// Read chapters (ordered by start time)
for (const ch of file.getChapters()) {
  console.log(`${ch.startTimeMs}–${ch.endTimeMs} ${ch.title} (${ch.source})`);
}

// Replace all chapters
file.setChapters([
  { startTimeMs: 0, title: "Intro" },
  { startTimeMs: 95_000, title: "Chapter 1" },
]);
file.save();

Chapters are read from ID3v2 CHAP frames (MP3) or, for MP4, a QuickTime chapter track (preferred when present) or a Nero chpl atom — each chapter reports its source. setChapters() supports MP3 and MP4 only; for MP4, mp4ChapterStyle ("quicktime" default, "nero", or "both") selects which structure(s) to write (the Nero atom is capped at 255 chapters). endTimeMs is explicit for ID3v2 chapters and inferred for MP4 (the next chapter's start, or the track duration for the last chapter).

Broadcast metadata (BWF bext / iXML)

import { TagLib } from "taglib-wasm";

const taglib = await TagLib.initialize();
using file = await taglib.open("recording.wav");

const bext = file.getBext(); // parsed EBU 3285 bext chunk, or undefined
console.log(bext?.description, bext?.timeReferenceSamples, bext?.codingHistory);
console.log(file.getIxml()); // raw iXML string, or undefined

file.setBext({
  ...bext!,
  description: "Updated",
  version: 2,
  loudnessValueDb: -16,
});
file.setIxml("<BWFXML>…</BWFXML>");
file.save();

getBext() / setBext() (WAV and FLAC only) parse and serialize the BWF Broadcast Audio Extension chunk; getBextData() / setBextData() expose the raw chunk bytes for vendor extensions or malformed chunks, and setBextData(null) removes the chunk. iXML is passed through verbatim as a string (setIxml(null) removes it). The bext v2 loudness fields are EBU R128-style measurements, distinct from ReplayGain tags. bwf.decodeBext / bwf.encodeBext are also exported for working with raw bext bytes directly.

Raw ID3v2 Frames (Escape Hatch)

import { TagLib } from "taglib-wasm";

const taglib = await TagLib.initialize();
using file = await taglib.open("song.mp3");

// Escape hatch: read/write raw ID3v2 frame bytes by ID (MP3 only)
const frames = file.getId3v2Frames("TXXX"); // [{ id, data, flags? }]
file.setId3v2Frames("RGAD", [rgadBody]); // replaces ALL RGAD frames
file.removeId3v2Frames("NCON"); // removes every NCON frame
file.save();

data is the frame body without the 10-byte header; the caller owns the body encoding. Bytes round-trip verbatim for frames TagLib does not model. For TagLib-modeled IDs (TIT2, APIC, …): typed getters see a raw write only after save+reload; bytes may be normalized by later saves after that reload; and raw reads reflect persisted state plus pending raw writes — not pending typed edits (backend-dependent). A typed write to the same ID as an existing raw write is silently ignored until that raw frame is removed or the file is saved and reloaded — raw writes always win within a save. Frames with compression/encryption flags are not supported for write (writes emit zero flags). flags exists on the returned frames for forward compatibility, but reads never populate it today — TagLib always blanks header flags when re-rendering a frame. On both backends, a raw write to an ID3v1-mapped frame ID (TIT2, TPE1, TALB, COMM, TCON, TDRC, TRCK) suspends the usual ID3v1↔ID3v2 duplicate-sync on save() until that raw frame is removed.

Container Format and Codec Detection

import { readProperties } from "taglib-wasm/simple";

// Get detailed audio properties including container and codec info
const props = await readProperties("song.m4a");

console.log(props.containerFormat); // "MP4" (container format)
console.log(props.codec); // "AAC" or "ALAC" (compressed media format)
console.log(props.isLossless); // false for AAC, true for ALAC
console.log(props.bitsPerSample); // 16 for most formats
console.log(props.bitrate); // 256 (kbps)
console.log(props.bitrateMode); // "CBR" | "VBR" | "ABR" | undefined (MP3 only)
console.log(props.sampleRate); // 44100 (Hz)
console.log(props.duration); // 180 (duration in seconds)

For Opus files, audioProperties() additionally exposes outputGainDb — the OpusHead output gain in decibels (RFC 7845). Players apply this unconditionally; it is separate from, and stacks with, ReplayGain / R128 tags, and is almost always 0.

Container format vs Codec:

  • Container format – How audio data and metadata are packaged (e.g., MP4, OGG)
  • Codec – How audio is compressed/encoded (e.g., AAC, Vorbis)

Supported formats:

  • MP4 container (.mp4, .m4a) – Can contain AAC (lossy) or ALAC (lossless)
  • OGG container (.ogg) – Can contain Vorbis, Opus, FLAC, or Speex
  • MP3 – Both container and codec (lossy)
  • FLAC – Both container and codec (lossless)
  • WAV – Container for PCM (uncompressed) audio
  • AIFF – Container for PCM (uncompressed) audio

Documentation

View Full Documentation

Getting Started

Guides

Supported Formats

taglib-wasm is designed to support all formats supported by TagLib:

  • .mp3 – ID3v2 and ID3v1 tags
  • .m4a/.mp4 – MPEG-4/AAC metadata for AAC and Apple Lossless audio
  • .flac – Vorbis comments and audio properties (plus BWF bext/iXML)
  • .ogg – Ogg Vorbis format with full metadata support
  • .wav – INFO chunk metadata, plus BWF bext and iXML
  • Additional formats – Opus, APE, MPC, WavPack, TrueAudio, AIFF, WMA, and more

Performance and Best Practices

Batch Processing for Multiple Files

When processing multiple audio files, use the optimized batch APIs for better performance:

import { readMetadataBatch, readTagsBatch } from "taglib-wasm/simple";

// Processing files one by one (can take 90+ seconds for 19 files)
for (const file of files) {
  const tags = await readTags(file); // Re-initializes for each file
}

// Batch processing (10-20x faster)
const result = await readTagsBatch(files, {
  concurrency: 8, // Process 8 files in parallel
  onProgress: (processed, total) => {
    console.log(`${processed}/${total} files processed`);
  },
});

// Read complete metadata in one batch
const metadata = await readMetadataBatch(files, { concurrency: 8 });

Performance comparison for 19 audio files:

  • Sequential: ~90 seconds (4.7s per file)
  • Batch (concurrency=4): ~8 seconds (11x faster)
  • Batch (concurrency=8): ~5 seconds (18x faster)

Smart Partial Loading

For large audio files (>50MB), enable partial loading to reduce memory usage:

// Enable partial loading for large files
using file = await taglib.open("large-concert.flac", {
  partial: true,
  maxHeaderSize: 2 * 1024 * 1024, // 2MB header
  maxFooterSize: 256 * 1024, // 256KB footer
});

// Read operations work normally
const tags = file.tag();
console.log(tags.title, tags.artist);

// Smart save - automatically loads full file when needed
await file.saveToFile(); // Full file loaded only here

Performance gains:

  • 500MB file: ~450x less memory usage (1.1MB vs 500MB)
  • Initial load: 50x faster (50ms vs 2500ms)
  • Memory peak: 3.3MB instead of 1.5GB

Runtime Optimization Tiers

taglib-wasm auto-selects the fastest available backend — no configuration needed:

| Environment | Backend | How it works | Performance | | ------------------------ | ----------------- | ------------------------------------------------------ | ----------- | | Node.js / Deno / Bun | WASI (auto) | Seek-based filesystem I/O; reads only headers and tags | Fastest | | Browsers / Workers | Emscripten (auto) | Entire file loaded into memory as buffer | Baseline |

On Node.js, Deno, and Bun you get WASI automatically — nothing to configure.

Runtime Compatibility

taglib-wasm works across all major JavaScript runtimes:

| Runtime | Status | Installation | Notes | | ---------------------- | ------- | ------------------------- | -------------------------------------------------------------------------------------------------- | | Node.js | Full | npm install taglib-wasm | TypeScript via tsx | | Deno | Full | npm:taglib-wasm | Native TypeScript | | Bun | Partial | bun add taglib-wasm | Import + init verified; full test suite is Deno-only | | Browser | Full | Via bundler | Full API support | | Cloudflare Workers | Full | npm install taglib-wasm | Buffer-based (Emscripten); no filesystem. See Workers guide | | Electron | Node.js | npm install taglib-wasm | Main process; renderer via IPC |

Known Limitations

  • Memory Usage (browsers) – In browser environments, entire files are loaded into memory. On Node.js/Deno, WASI reads only headers and tags from disk.
  • Concurrent Access – Not thread-safe (JavaScript single-threaded nature mitigates this)

Contributing

Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.

License

This project uses dual licensing:

  • TypeScript/JavaScript code – MIT License (see LICENSE)
  • WebAssembly binaries (taglib-web.wasm, taglib-wasi.wasm) – LGPL-2.1-or-later (inherited from TagLib)

The TagLib library is dual-licensed under LGPL/MPL. When compiled to WebAssembly, the resulting binary must comply with LGPL requirements. This means:

  • You can use taglib-wasm in commercial projects
  • If you modify the TagLib C++ code, you must share those changes
  • You must provide a way for users to relink with a modified TagLib

For details, see lib/taglib/COPYING.LGPL

Acknowledgments

  • TagLib – Excellent audio metadata library
  • Emscripten – WebAssembly compilation toolchain
  • WASI – WebAssembly System Interface for server-side runtimes