music-metadata

repository·master·Indexed 23 days ago

https://github.com/borewit/music-metadata

A comprehensive, promise-based music metadata parser for Node.js (≥ 18) and browser environments. Version 11.14.0 supports a wide variety of audio formats (including MP3, FLAC, WAV, Ogg, and AAC) and tag headers (ID3, Vorbis, APE, and others). It provides specialized parsing functions such as parseFile, parseStream, parseWebStream, parseBlob, and parseBuffer to extract detailed encoding, format, and common metadata from audio files.

Tokens
34.7K
Snippets
15
Records
45
Agent score
74%

What's inside music-metadata

  1. Check music-metadata compatibility and requirements

    master

    Before integrating music-metadata, ensure your environment meets the following requirements:

    • Node.js: Requires version ≥ 18.
    • Module System: Since version 8, the module has migrated from CommonJS to pure ECMAScript Module (ESM). It is compliant with the ECMAScript 2020 (11th Edition) standard.
    • Browser Support: The module can be used in browser environments when bundled with a module bundler such as Webpack or Rollup.
  2. Understand the IAudioMetadata structure

    master

    The IAudioMetadata object returned by parsing functions contains several key sections:

    • format: (IFormat) Audio format information (container, codec, duration, bitrate, sample rate, etc.).
    • common: Generic, abstract way of accessing metadata (artist, album, title, etc.).
    • trackInfo: (Experimental) An array of trackInfo objects for containers with multiple tracks (like MKV or MP4), providing details for specific audio or video tracks.
    • native: A list of the original, native tags found in the file (e.g., ID3v2.3).
  3. Traverse a long list of files sequentially

    master

    When parsing a large number of audio files, do not use a standard loop that triggers all parses in parallel (e.g., audioFiles.map(parseFile)), as this can cause the application to hang due to resource exhaustion. Instead, ensure parsing is done sequentially.

    Recommended approaches:

    • Use an async function with a for...of loop and await each call.
    • Use recursion to process the next file only after the current one is finished.
    // Using async/await (Recommended)
    import { parseFile } from 'music-metadata';
    
    async function parseFiles(audioFiles) {
        for (const audioFile of audioFiles) {
            const metadata = await parseFile(audioFile);
            // Do great things with the metadata
        }
    }
    
    // Using recursion
    import { parseFile } from 'music-metadata';
    
    function parseFiles(audioFiles) {
      const audioFile = audioFiles.shift();
    
      if (audioFile) {
        return parseFile(audioFile).then(metadata => {
          // Do great things with the metadata
          return parseFiles(audioFiles); 
        })
      }
    }
  4. Import music-metadata in CommonJS projects

    master

    Since music-metadata is an ESM module, the method for importing it in CommonJS depends on your Node.js version:

    1. Node.js ≥ 22: You can use standard require().
    2. Node.js < 22: You must use a dynamic import().
    3. TypeScript (CommonJS module): Use the load-esm package to handle the dynamic import.
    // Node.js ≥ 22
    const mm = require('music-metadata');
    
    // Node.js < 22
    (async () => {
      const mm = await import('music-metadata');
    })();
    
    // CommonJS TypeScript with 'load-esm'
    import {loadEsm} from 'load-esm';
    
    (async () => {
      const mm = await loadEsm<typeof import('music-metadata')>('music-metadata');
    })();
  5. Configure parsing behavior with IOptions

    master

    The IOptions interface allows you to customize the parsing process. Key options include:

    • duration (boolean, default: false): If true, the parser will attempt to read the entire file if necessary to calculate duration.
    • includeChapters (boolean, default: false): If true, the MP4 parser scans the mdat atom for chapters.
    • mkvUseIndex (boolean, default: false): Experimental feature for Matroska (MKV) files to use the SeekHead index for performance. May cause some metadata to be skipped.
    • observer ((update: MetadataEvent) => void): Callback triggered when common tags or format properties are updated during parsing.
    • skipCovers (boolean, default: false): If true, embedded cover art (images) will not be extracted.
    • skipPostHeaders (boolean, default: false): If true, tag headers at the end of the file will not be read (useful for streaming).
  6. Troubleshoot module resolution in Next.js/Bundler environments

    master
    If your TypeScript moduleResolution is set to "bundler" (common in Next.js), the compiler may not set the ECMAScript "node" condition. This can cause Node-specific functions to fail during import. Refer to issue #2370 for specific resolution strategies.
  7. Read duration from a stream using `parseStream`

    master

    When reading the duration of a stream (excluding file streams), you must provide the file size in bytes in the options object. This allows the parser to correctly calculate the duration based on the stream length.

    Use parseStream from music-metadata and pass the size and mimeType in the options object, and set duration: true in the third argument (the options object for the parser itself).

    import { parseStream } from 'music-metadata';
    import { inspect } from 'util';
    
    (async () => {
        const metadata = await parseStream(someReadStream, {mimeType: 'audio/mpeg', size: 26838}, {duration: true});
        console.log(inspect(metadata, {showHidden: false, depth: null}));
        someReadStream.close();
      }
    )();
  8. Parse web-compatible ReadableStreams using parseWebStream

    master

    Use parseWebStream for cross-platform (Node.js and Browser) parsing of audio data from a web-compatible ReadableStream<Uint8Array>. This is ideal for network streams (e.g., from fetch). It is highly recommended to pass fileInfo including size (from Content-Length) and mimeType to ensure accurate parsing.

    import { parseWebStream } from 'music-metadata';
    
    (async () => {
      try {
        // Fetch the audio file
        const response = await fetch('https://github.com/Borewit/test-audio/raw/refs/heads/master/Various%20Artists%20-%202008%20-%20netBloc%20Vol%2013%20-%20Color%20in%20a%20World%20of%20Monochrome%20%5BAAC-40%5D/1.02.%20Solid%20Ground.m4a');
    
        // Extract the Content-Length header and convert it to a number
        const contentLength = response.headers.get('Content-Length');
        const size = contentLength ? parseInt(contentLength, 10) : undefined;
    
        // Parse the metadata from the web stream
        const metadata = await parseWebStream(response.body, {
          mimeType: response.headers.get('Content-Type'),
          size // Important to pass the content-length
        });
    
        console.log(metadata);
      } catch (error) {
        console.error('Error parsing metadata:', error.message);
      }
    })();
  9. Parse Blobs using parseBlob

    master

    Use parseBlob to extract metadata from a Web API Blob or File object. This is a cross-platform function suitable for browser environments. Note that it requires environments supporting ReadableStreamBYOBReader (available in Node.js 20+).

    import { parseBlob } from 'music-metadata';
    
    (async () => {
      const fileInput = document.querySelector('input[type="file"]');
      const file = fileInput.files[0];
      
      try {
        const metadata = await parseBlob(file);
        console.log(metadata);
      } catch (error) {
        console.error('Error parsing metadata:', error.message);
      }
    })();
  10. Use utility functions: orderTags, ratingToStars, and selectCover

    master

    The library provides several utility functions for processing metadata:

    • orderTags(nativeTags: ITag[]): [tagId: string]: any[]: Converts native tags into a dictionary indexed by tag identifier.
    • ratingToStars(rating: number): number: Converts a normalized rating value to a 0..5 star scale.
    • selectCover(pictures?: IPicture[]): IPicture | null: Selects the best cover image based on image type, falling back to the first available picture.
    • getSupportedMimeTypes(): Returns a list of all supported MIME-types.
  11. Parse audio files in Node.js using parseFile

    master

    Use parseFile to extract metadata from audio files located on the local filesystem. This function is specific to Node.js and is generally faster than stream-based parsing because it can perform direct file access (jumping to specific offsets).

    import { parseFile } from 'music-metadata';
    import { inspect } from 'node:util';
    
    (async () => {
      try {
        const filePath = 'test/samples/MusicBrainz - Beth Hart - Sinner\'s Prayer [id3v2.3].V2.mp3';
        const metadata = await parseFile(filePath);
    
        // Output the parsed metadata to the console in a readable format
        console.log(inspect(metadata, { showHidden: false, depth: null }));
      } catch (error) {
        console.error('Error parsing metadata:', error.message);
      }
    })();