TagLib-Wasm
Guide
API Reference
Examples
NPM
GitHub
Guide
API Reference
Examples
NPM
GitHub
  • Documentation

    • taglib-wasm API Reference
    • TagLib Tag Name Constants and Cross-Format Mapping
    • Extended Metadata with PropertyMap API
    • Runtime Compatibility
    • Memory Management
    • Performance Guide
    • Error Handling Guide
    • Implementation Guide
    • Troubleshooting Guide
    • Publishing Guide
    • Cloudflare Workers

Error Handling Guide

This guide covers error handling best practices and common error scenarios in taglib-wasm.

Table of Contents

  • Error Types
  • Simple API Error Handling
  • Core API Error Handling
  • Common Error Scenarios
  • Error Recovery Strategies
  • Debugging Tips

Error Types

File Format Errors

Errors related to unsupported or invalid file formats.

// Unsupported format
try {
  const file = taglib.openFile(pdfBuffer); // PDF is not an audio file
} catch (error) {
  // Error: Unsupported format or corrupted file
}

// Corrupted file
try {
  const file = taglib.openFile(corruptedMp3);
} catch (error) {
  // Error: Failed to parse file
}

Memory Errors

Errors related to WebAssembly memory allocation.

// Insufficient memory
try {
  const taglib = await TagLib.initialize({
    memory: { initial: 1024 }, // Only 1KB - too small!
  });
} catch (error) {
  // Error: Failed to allocate memory
}

// Out of memory during processing
try {
  const file = taglib.openFile(veryLargeFile); // 500MB file
} catch (error) {
  // Error: Cannot allocate memory
}

Validation Errors

Errors when working with invalid files.

const file = taglib.openFile(buffer);
if (!file.isValid()) {
  throw new Error("File validation failed");
}

Save Errors

Errors when saving modifications.

if (!file.save()) {
  throw new Error("Failed to save file modifications");
}

Simple API Error Handling

The Simple API provides clear error messages for common scenarios.

Basic Error Handling

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

// Reading tags
try {
  const tags = await readTags("song.mp3");
  console.log(tags);
} catch (error) {
  if (error.message.includes("File not found")) {
    console.error("Audio file doesn't exist");
  } else if (error.message.includes("Unsupported format")) {
    console.error("File format not supported");
  } else {
    console.error("Failed to read tags:", error.message);
  }
}

// Writing tags
try {
  const modified = await applyTags("song.mp3", {
    title: "New Title",
    artist: "New Artist",
  });
  await Deno.writeFile("song-updated.mp3", modified);
} catch (error) {
  console.error("Failed to write tags:", error.message);
}

// Reading properties
try {
  const props = await readProperties("song.mp3");
  console.log(`Duration: ${props.length}s`);
} catch (error) {
  console.error("Failed to read properties:", error.message);
}

Handling Multiple Files

async function processBatch(files: string[]) {
  const results = [];

  for (const file of files) {
    try {
      const tags = await readTags(file);
      results.push({ file, success: true, tags });
    } catch (error) {
      results.push({
        file,
        success: false,
        error: error.message,
      });
    }
  }

  return results;
}

// Usage
const results = await processBatch([
  "song1.mp3",
  "song2.flac",
  "not-audio.txt", // Will fail
  "song3.ogg",
]);

// Check results
results.forEach((result) => {
  if (result.success) {
    console.log(`✓ ${result.file}: ${result.tags.title}`);
  } else {
    console.log(`✗ ${result.file}: ${result.error}`);
  }
});

Core API Error Handling

The Core API requires more careful error handling due to manual memory management.

Initialization Errors

async function initializeWithRetry(maxAttempts = 3) {
  let lastError;

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      const taglib = await TagLib.initialize({
        memory: {
          initial: 16 * 1024 * 1024,
          maximum: 256 * 1024 * 1024,
        },
      });
      return taglib;
    } catch (error) {
      lastError = error;
      console.warn(`Initialization attempt ${attempt} failed:`, error.message);

      // Wait before retry
      if (attempt < maxAttempts) {
        await new Promise((resolve) => setTimeout(resolve, 1000));
      }
    }
  }

  throw new Error(
    `Failed to initialize after ${maxAttempts} attempts: ${lastError.message}`,
  );
}

File Processing with Cleanup

async function processFileWithCleanup(buffer: Uint8Array) {
  const taglib = await TagLib.initialize();
  let file: AudioFile | null = null;

  try {
    // Open file
    file = taglib.openFile(buffer);

    // Validate
    if (!file.isValid()) {
      throw new Error("Invalid audio file");
    }

    // Process
    const format = file.getFormat();
    if (format === "UNKNOWN") {
      throw new Error("Unknown audio format");
    }

    // Modify
    file.setTitle("New Title");

    // Save
    if (!file.save()) {
      throw new Error("Failed to save modifications");
    }

    // Return modified buffer
    return file.toBuffer();
  } catch (error) {
    // Log detailed error info
    console.error("Error processing file:", {
      message: error.message,
      format: file?.format(),
      valid: file?.isValid(),
    });
    throw error;
  } finally {
    // Always clean up
    if (file) {
      file.dispose();
    }
  }
}

Batch Processing with Error Recovery

class BatchProcessor {
  private taglib: TagLib;

  async initialize() {
    this.taglib = await TagLib.initialize();
  }

  async processFiles(files: Map<string, Uint8Array>) {
    const results = new Map();

    for (const [filename, buffer] of files) {
      const result = await this.processFile(filename, buffer);
      results.set(filename, result);
    }

    return results;
  }

  private async processFile(filename: string, buffer: Uint8Array) {
    let file: AudioFile | null = null;

    try {
      file = this.taglib.openFile(buffer);

      if (!file.isValid()) {
        return {
          success: false,
          error: "Invalid file format",
        };
      }

      // Read metadata
      const metadata = {
        format: file.getFormat(),
        tags: file.tag(),
        properties: file.audioProperties(),
      };

      return {
        success: true,
        metadata,
      };
    } catch (error) {
      return {
        success: false,
        error: error.message,
        filename,
      };
    } finally {
      file?.dispose();
    }
  }
}

// Usage
const processor = new BatchProcessor();
await processor.initialize();

const files = new Map([
  ["song1.mp3", await Deno.readFile("song1.mp3")],
  ["song2.flac", await Deno.readFile("song2.flac")],
]);

const results = await processor.processFiles(files);

Common Error Scenarios

1. Unsupported File Format

function isSupportedFormat(filename: string): boolean {
  const ext = filename.toLowerCase().split(".").pop();
  const supported = ["mp3", "mp4", "m4a", "flac", "ogg", "wav"];
  return supported.includes(ext || "");
}

// Pre-check before processing
if (!isSupportedFormat(filename)) {
  throw new Error(`Unsupported file format: ${filename}`);
}

2. Large File Handling

async function processLargeFile(filePath: string) {
  const stats = await Deno.stat(filePath);
  const fileSizeMB = stats.size / (1024 * 1024);

  // Adjust memory based on file size
  const memoryConfig = {
    initial: Math.max(32, fileSizeMB * 2) * 1024 * 1024,
    maximum: Math.max(256, fileSizeMB * 4) * 1024 * 1024,
  };

  try {
    const taglib = await TagLib.initialize({ memory: memoryConfig });
    const buffer = await Deno.readFile(filePath);
    const file = taglib.openFile(buffer);

    // Process...

    file.dispose();
  } catch (error) {
    if (error.message.includes("memory")) {
      throw new Error(`File too large (${fileSizeMB}MB): ${error.message}`);
    }
    throw error;
  }
}

3. Concurrent Processing Limits

class ConcurrentProcessor {
  private concurrency: number;
  private queue: Array<() => Promise<any>> = [];
  private running = 0;

  constructor(concurrency = 3) {
    this.concurrency = concurrency;
  }

  async process<T>(task: () => Promise<T>): Promise<T> {
    while (this.running >= this.concurrency) {
      await new Promise((resolve) => setTimeout(resolve, 100));
    }

    this.running++;
    try {
      return await task();
    } finally {
      this.running--;
    }
  }
}

// Usage
const processor = new ConcurrentProcessor(3);
const files = ["file1.mp3", "file2.mp3" /* ... */];

const results = await Promise.all(
  files.map((file) =>
    processor.process(async () => {
      try {
        return await readTags(file);
      } catch (error) {
        return { file, error: error.message };
      }
    })
  ),
);

4. Network/Buffer Errors

async function fetchAndProcessAudio(url: string) {
  try {
    // Fetch with timeout
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), 30000); // 30s timeout

    const response = await fetch(url, { signal: controller.signal });
    clearTimeout(timeout);

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    // Check content type
    const contentType = response.headers.get("content-type");
    if (!contentType?.includes("audio")) {
      throw new Error(`Invalid content type: ${contentType}`);
    }

    // Process
    const buffer = new Uint8Array(await response.arrayBuffer());
    return await readTags(buffer);
  } catch (error) {
    if (error.name === "AbortError") {
      throw new Error("Request timeout");
    }
    throw error;
  }
}

Error Recovery Strategies

1. Retry with Exponential Backoff

async function retryWithBackoff<T>(
  operation: () => Promise<T>,
  maxRetries = 3,
  initialDelay = 1000,
): Promise<T> {
  let lastError;

  for (let i = 0; i < maxRetries; i++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error;

      if (i < maxRetries - 1) {
        const delay = initialDelay * Math.pow(2, i);
        console.warn(`Retry ${i + 1}/${maxRetries} after ${delay}ms`);
        await new Promise((resolve) => setTimeout(resolve, delay));
      }
    }
  }

  throw lastError;
}

// Usage
const tags = await retryWithBackoff(() => readTags("song.mp3"));

2. Fallback Strategies

async function readTagsWithFallback(filePath: string) {
  try {
    // Try taglib-wasm first
    return await readTags(filePath);
  } catch (error) {
    console.warn("taglib-wasm failed, trying fallback:", error.message);

    // Fallback to basic detection
    const filename = filePath.split("/").pop() || "";
    const [artist, title] = filename.replace(/\.\w+$/, "").split(" - ");

    return {
      title: title || "Unknown",
      artist: artist || "Unknown Artist",
      album: "Unknown Album",
    };
  }
}

3. Graceful Degradation

interface SafeMetadata {
  title: string;
  artist: string;
  album: string;
  year?: number;
  duration?: number;
  format?: string;
  error?: string;
}

async function safeReadMetadata(buffer: Uint8Array): Promise<SafeMetadata> {
  const defaults: SafeMetadata = {
    title: "Unknown Title",
    artist: "Unknown Artist",
    album: "Unknown Album",
  };

  try {
    const taglib = await TagLib.initialize();
    const file = taglib.openFile(buffer);

    if (!file.isValid()) {
      return { ...defaults, error: "Invalid file" };
    }

    try {
      const tags = file.tag();
      const props = file.audioProperties();

      return {
        title: tags.title || defaults.title,
        artist: tags.artist || defaults.artist,
        album: tags.album || defaults.album,
        year: tags.year || undefined,
        duration: props.length,
        format: file.getFormat(),
      };
    } finally {
      file.dispose();
    }
  } catch (error) {
    return {
      ...defaults,
      error: error.message,
    };
  }
}

Debugging Tips

1. Enable Debug Mode

const taglib = await TagLib.initialize({
  debug: true, // Enables console output
});

2. Detailed Error Logging

function logError(context: string, error: any, additionalInfo?: any) {
  console.error(`[${new Date().toISOString()}] ${context}:`, {
    message: error.message,
    stack: error.stack,
    type: error.constructor.name,
    ...additionalInfo,
  });
}

// Usage
try {
  const file = taglib.openFile(buffer);
} catch (error) {
  logError("openFile", error, {
    bufferSize: buffer.length,
    firstBytes: Array.from(buffer.slice(0, 4)),
  });
}

3. Memory Usage Monitoring

class MemoryMonitor {
  private taglib: TagLib;
  private filesProcessed = 0;
  private startMemory: number;

  constructor(taglib: TagLib) {
    this.taglib = taglib;
    this.startMemory = this.getMemoryUsage();
  }

  private getMemoryUsage(): number {
    if (typeof process !== "undefined" && process.memoryUsage) {
      return process.memoryUsage().heapUsed;
    }
    return 0;
  }

  async processFile(buffer: Uint8Array) {
    const beforeMemory = this.getMemoryUsage();
    const file = this.taglib.openFile(buffer);

    try {
      // Process...
      this.filesProcessed++;
    } finally {
      file.dispose();
    }

    const afterMemory = this.getMemoryUsage();
    const delta = afterMemory - beforeMemory;

    if (delta > 1024 * 1024) { // 1MB increase
      console.warn(`Memory increase: ${(delta / 1024 / 1024).toFixed(2)}MB`);
    }

    return {
      filesProcessed: this.filesProcessed,
      memoryDelta: afterMemory - this.startMemory,
    };
  }
}

4. File Format Validation

function validateAudioBuffer(buffer: Uint8Array): string | null {
  if (buffer.length < 4) {
    return "File too small";
  }

  // Check magic numbers
  const magic = Array.from(buffer.slice(0, 4));

  // MP3
  if (magic[0] === 0xFF && (magic[1] & 0xE0) === 0xE0) {
    return null; // Valid MP3
  }

  // ID3v2
  if (magic[0] === 0x49 && magic[1] === 0x44 && magic[2] === 0x33) {
    return null; // Valid MP3 with ID3
  }

  // FLAC
  if (magic.join(",") === "102,76,97,67") {
    return null; // Valid FLAC
  }

  // OGG
  if (magic.join(",") === "79,103,103,83") {
    return null; // Valid OGG
  }

  // WAV
  if (magic.join(",") === "82,73,70,70") {
    return null; // Valid WAV
  }

  // MP4/M4A
  const mp4Magic = Array.from(buffer.slice(4, 8));
  if (mp4Magic.join(",") === "102,116,121,112") {
    return null; // Valid MP4/M4A
  }

  return "Unknown or unsupported format";
}

// Usage
const error = validateAudioBuffer(buffer);
if (error) {
  throw new Error(error);
}

Detailed Error Types

taglib-wasm provides specific error types for better debugging and error handling:

Complete Error Type Reference

import {
  EnvironmentError,
  FileOperationError,
  InvalidFormatError,
  isTagLibError,
  MemoryError,
  MetadataError,
  TagLib,
  TagLibError,
  TagLibInitializationError,
  UnsupportedFormatError,
} from "taglib-wasm";

Error Types and Their Properties

TagLibInitializationError

  • Thrown when the Wasm module fails to initialize
  • Properties: code, context

InvalidFormatError

  • Thrown when a file is corrupted or invalid
  • Properties: code, bufferSize

UnsupportedFormatError

  • Thrown when a file format is valid but not supported
  • Properties: code, format, supportedFormats

FileOperationError

  • Thrown when file read/write operations fail
  • Properties: code, operation, path

MetadataError

  • Thrown when tag reading/writing fails
  • Properties: code, field

MemoryError

  • Thrown when Wasm memory allocation fails
  • Properties: code, requestedSize, availableSize

EnvironmentError

  • Thrown when runtime environment is incompatible
  • Properties: code, environment, requirement

Using Type Guards

The isTagLibError type guard helps identify any taglib-wasm error:

try {
  const file = await taglib.open("song.mp3");
  // Process file...
} catch (error) {
  if (error instanceof InvalidFormatError) {
    console.error(`Invalid audio file: ${error.message}`);
    console.error(`Buffer size: ${error.bufferSize} bytes`);
  } else if (error instanceof UnsupportedFormatError) {
    console.error(`Unsupported format: ${error.format}`);
    console.error(`Supported formats: ${error.supportedFormats.join(", ")}`);
  } else if (error instanceof FileOperationError) {
    console.error(`File operation failed: ${error.operation}`);
    console.error(`Path: ${error.path}`);
  } else if (isTagLibError(error)) {
    // Catches any other taglib-wasm specific error
    console.error(`TagLib error: ${error.code} - ${error.message}`);
  } else {
    // Non-taglib error (e.g., network error, permission denied)
    console.error(`Unexpected error: ${error}`);
  }
}
Edit this page on GitHub
Last Updated:: 6/17/25, 3:55 AM
Contributors: Charles Wiltgen, Claude
Prev
Performance Guide
Next
Implementation Guide