TagLib-Wasm
Guide
API Reference
Examples
NPM
JSR
GitHub
Guide
API Reference
Examples
NPM
JSR
GitHub
  • Getting Started

    • Introduction
    • Installation
    • Quick Start
    • Platform-Specific Examples
    • Using taglib-wasm with Deno Compile
  • Features

    • Working with Cover Art
    • Folder Operations Guide
    • Examples
    • Cloudflare Workers Setup
    • /features/codec-detection.html
  • Core Concepts

    • Runtime Compatibility
    • Memory Management
    • Performance Guide
    • WebAssembly Streaming Compilation
    • Error Handling Guide
  • API Reference

    • taglib-wasm API Reference
    • Folder API Reference
    • /api/tag-name-constants.html
    • Extended Metadata with PropertyMap API
  • Advanced

    • Implementation Guide
    • Troubleshooting Guide
    • Cloudflare Workers
  • Development

    • Testing Guide
    • Version Management
    • Future Improvements
    • Deno Compatibility Fix for taglib-wasm
    • Publishing Guide

Folder Operations Guide

The folder API provides efficient batch operations for processing multiple audio files, scanning directories, and managing music collections. This API is perfect for building music library managers, duplicate finders, and batch metadata editors.

Overview

The folder API is available through a dedicated import path:

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

Runtime Support Folder operations require filesystem access and are

available in:

  • ✅ Deno
  • ✅ Node.js
  • ✅ Bun
  • ❌ Browsers (no filesystem access)
  • ❌ Cloudflare Workers (no filesystem access) :::

Scanning Folders

The scanFolder() function recursively scans directories for audio files and reads their metadata:

const result = await scanFolder("/path/to/music", {
  recursive: true, // Scan subdirectories (default: true)
  extensions: [".mp3", ".flac"], // File types to include (default: common audio formats)
  maxFiles: 1000, // Limit number of files (default: unlimited)
  includeProperties: true, // Include audio properties (default: true)
  continueOnError: true, // Continue if files fail (default: true)
  concurrency: 4, // Parallel processing (default: 4)
  onProgress: (processed, total, file) => {
    console.log(`Processing ${processed}/${total}: ${file}`);
  },
});

// Access results
console.log(`Found ${result.totalFound} audio files`);
console.log(`Processed ${result.totalProcessed} successfully`);
console.log(`Errors: ${result.errors.length}`);
console.log(`Time taken: ${result.duration}ms`);

// Process each file
for (const file of result.files) {
  console.log(`Path: ${file.path}`);
  console.log(`Title: ${file.tags.title}`);
  console.log(`Artist: ${file.tags.artist}`);
  console.log(`Duration: ${file.properties?.duration}s`);
  console.log(`Bitrate: ${file.properties?.bitrate} kbps`);
}

Result Structure

interface FolderScanResult {
  files: AudioFileMetadata[]; // Successfully processed files
  errors: Array<{ // Failed files with errors
    path: string;
    error: Error;
  }>;
  totalFound: number; // Total audio files found
  totalProcessed: number; // Successfully processed count
  duration: number; // Time taken in milliseconds
}

interface AudioFileMetadata {
  path: string; // File path
  tags: Tag; // Metadata tags
  properties?: AudioProperties; // Audio properties (if requested)
  error?: Error; // Error if processing failed
}

Batch Tag Updates

Update metadata for multiple files efficiently:

const updates = [
  {
    path: "/music/song1.mp3",
    tags: { artist: "New Artist", album: "New Album" },
  },
  {
    path: "/music/song2.mp3",
    tags: { genre: "Electronic", year: 2024 },
  },
];

const result = await updateFolderTags(updates, {
  continueOnError: true, // Continue if some files fail
  concurrency: 4, // Process 4 files in parallel
});

console.log(`Updated ${result.successful} files`);
console.log(`Failed: ${result.failed.length}`);
console.log(`Time: ${result.duration}ms`);

// Check failures
for (const failure of result.failed) {
  console.error(`Failed to update ${failure.path}: ${failure.error.message}`);
}

Finding Duplicates

Find duplicate audio files based on metadata criteria:

// Find duplicates by artist and title
const duplicates = await findDuplicates("/path/to/music", ["artist", "title"]);

console.log(`Found ${duplicates.size} groups of duplicates`);

// Process each duplicate group
for (const [key, files] of duplicates) {
  console.log(`\nDuplicate group: ${key}`);
  for (const file of files) {
    console.log(`  - ${file.path}`);
    console.log(
      `    Size: ${file.properties?.duration}s @ ${file.properties?.bitrate}kbps`,
    );
  }
}

// Find duplicates by different criteria
const albumDuplicates = await findDuplicates("/music", ["album", "artist"]);
const exactDuplicates = await findDuplicates("/music", [
  "artist",
  "album",
  "title",
  "track",
]);

Exporting Metadata

Export your music library metadata to JSON for cataloging or analysis:

await exportFolderMetadata("/path/to/music", "./music-catalog.json", {
  recursive: true,
  includeProperties: true
});

// The exported JSON contains:
{
  "folder": "/path/to/music",
  "scanDate": "2024-01-20T10:30:00.000Z",
  "summary": {
    "totalFiles": 1234,
    "processedFiles": 1230,
    "errors": 4,
    "duration": 5678
  },
  "files": [
    {
      "path": "/path/to/music/song.mp3",
      "tags": {
        "title": "Song Title",
        "artist": "Artist Name",
        // ... all tags
      },
      "properties": {
        "duration": 180,
        "bitrate": 320,
        // ... all properties
      }
    }
    // ... more files
  ],
  "errors": [
    {
      "path": "/path/to/music/corrupt.mp3",
      "error": "Invalid audio file format"
    }
  ]
}

Performance Optimization

Concurrency Control

The concurrency option controls how many files are processed in parallel:

// Sequential processing (slower but uses less memory)
const sequential = await scanFolder("/music", { concurrency: 1 });

// Parallel processing (faster but uses more memory)
const parallel = await scanFolder("/music", { concurrency: 8 });

Memory Management

When processing large collections:

// Process in smaller batches
const result = await scanFolder("/huge-library", {
  maxFiles: 100, // Process only 100 files at a time
  includeProperties: false, // Skip audio properties to save memory
  concurrency: 2, // Lower concurrency for memory savings
});

Progress Monitoring

For long-running operations:

let lastUpdate = Date.now();

const result = await scanFolder("/music", {
  onProgress: (processed, total, file) => {
    const now = Date.now();
    if (now - lastUpdate > 1000) { // Update every second
      const percent = ((processed / total) * 100).toFixed(1);
      console.log(`Progress: ${percent}% (${processed}/${total})`);
      console.log(`Current: ${file}`);

      const elapsed = now - startTime;
      const rate = processed / (elapsed / 1000);
      const remaining = (total - processed) / rate;
      console.log(`ETA: ${Math.round(remaining)}s`);

      lastUpdate = now;
    }
  },
});

Common Use Cases

Music Library Organization

// Organize music by artist/album structure
const result = await scanFolder("/unsorted-music");

for (const file of result.files) {
  const artist = file.tags.artist || "Unknown Artist";
  const album = file.tags.album || "Unknown Album";
  const title = file.tags.title || path.basename(file.path);

  // Create organized structure
  const newPath = path.join("/organized-music", artist, album, `${title}.mp3`);
  // Move file to new location (using your preferred file operation method)
}

Metadata Cleanup

// Find and fix missing metadata
const result = await scanFolder("/music");

const needsFixing = result.files.filter((file) =>
  !file.tags.artist ||
  !file.tags.title ||
  !file.tags.album
);

console.log(`Found ${needsFixing.length} files with missing metadata`);

// Batch update missing fields
const updates = needsFixing.map((file) => ({
  path: file.path,
  tags: {
    artist: file.tags.artist || "Unknown Artist",
    album: file.tags.album || "Unknown Album",
    title: file.tags.title || path.basename(file.path, path.extname(file.path)),
  },
}));

await updateFolderTags(updates);

Duplicate Cleanup

// Find and handle duplicates
const duplicates = await findDuplicates("/music", ["artist", "title"]);

for (const [key, files] of duplicates) {
  // Sort by quality (highest bitrate first)
  const sorted = files.sort((a, b) =>
    (b.properties?.bitrate || 0) - (a.properties?.bitrate || 0)
  );

  const [keep, ...remove] = sorted;
  console.log(`Keeping: ${keep.path} (${keep.properties?.bitrate}kbps)`);

  for (const file of remove) {
    console.log(
      `Consider removing: ${file.path} (${file.properties?.bitrate}kbps)`,
    );
  }
}

Error Handling

The folder API provides detailed error information:

const result = await scanFolder("/music", {
  continueOnError: true, // Don't stop on errors
});

// Check for errors
if (result.errors.length > 0) {
  console.error(`Failed to process ${result.errors.length} files:`);

  for (const { path, error } of result.errors) {
    if (error.message.includes("Invalid audio file format")) {
      console.error(`Corrupted file: ${path}`);
    } else if (error.message.includes("Permission denied")) {
      console.error(`No access: ${path}`);
    } else {
      console.error(`Unknown error in ${path}: ${error.message}`);
    }
  }
}

Best Practices

  1. Start with small directories when testing to understand performance characteristics
  2. Use progress callbacks for user feedback on long operations
  3. Handle errors gracefully - some files may be corrupted or inaccessible
  4. Consider memory usage when processing large collections
  5. Use appropriate concurrency based on your system resources
  6. Filter by extensions to avoid processing non-audio files
  7. Export metadata regularly for backup and analysis

API Reference

For detailed API documentation, see the Folder API Reference.

Edit this page on GitHub
Last Updated:: 6/20/25, 10:52 PM
Contributors: Charles Wiltgen, Claude
Prev
Working with Cover Art
Next
Examples