Home

Awesome

Node.js CI Build status NPM version npm downloads Coverage Status Codacy Badge CodeQL DeepScan grade Known Vulnerabilities Discord

music-metadata

Key features:

The music-metadata module is ideal for developers working on media applications, music players, or any project that requires access to detailed audio file metadata.

Compatibility

Module: version 8 migrated from CommonJS to pure ECMAScript Module (ESM). The distributed JavaScript codebase is compliant with the ECMAScript 2020 (11th Edition) standard.

[!NOTE] See also CommonJS backward Compatibility

This module requires a Node.js ≥ 16 engine. It can also be used in a browser environment when bundled with a module bundler.

Support the Project

If you find this project useful and would like to support its development, consider sponsoring or contributing:

Features

Support for audio file types

Audio formatDescriptionWiki
AIFF / AIFF-CAudio Interchange File Format:link:<img src="https://upload.wikimedia.org/wikipedia/commons/8/84/Apple_Computer_Logo_rainbow.svg" width="40" alt="Apple rainbow logo">
AACADTS / Advanced Audio Coding:link:<img src="https://svgshare.com/i/UT8.svg" width="40" alt="AAC logo">
AMRAdaptive Multi-Rate audio codec:link:<img src="https://foreverhits.files.wordpress.com/2015/05/ape_audio.jpg" width="40" alt="Monkey's Audio logo">
APEMonkey's Audio:link:<img src="https://foreverhits.files.wordpress.com/2015/05/ape_audio.jpg" width="40" alt="Monkey's Audio logo">
ASFAdvanced Systems Format:link:
BWFBroadcast Wave Format:link:
DSDIFFPhilips DSDIFF:link:<img src="https://upload.wikimedia.org/wikipedia/commons/b/bc/DSDlogo.svg" width="80" alt="DSD logo">
DSFSony's DSD Stream File:link:<img src="https://upload.wikimedia.org/wikipedia/commons/b/bc/DSDlogo.svg" width="80" alt="DSD logo">
FLACFree Lossless Audio Codec:link:<img src="https://upload.wikimedia.org/wikipedia/commons/a/a2/FLAC_logo_vector.svg" width="80" alt="FLAC logo">
MP2MPEG-1 Audio Layer II:link:
MatroskaMatroska (EBML), mka, mkv:link:<img src="https://upload.wikimedia.org/wikipedia/commons/1/1a/Matroska_2010.svg" width="80" alt="Matroska logo">
MP3MPEG-1 / MPEG-2 Audio Layer III:link:<img src="https://upload.wikimedia.org/wikipedia/commons/e/ea/Mp3.svg" width="80" alt="MP3 logo">
MPCMusepack SV7:link:<img src="https://www.musepack.net/pictures/musepack_logo.png" width="80" alt="musepack logo">
MPEG 4mp4, m4a, m4v:link:<img src="https://svgshare.com/i/UU3.svg" width="80" alt="mpeg 4 logo">
OggOpen container format:link:<img src="https://upload.wikimedia.org/wikipedia/commons/a/a1/Ogg_Logo.svg" width="80" alt="Ogg logo">
Opus:link:<img src="https://upload.wikimedia.org/wikipedia/commons/0/02/Opus_logo2.svg" width="80" alt="Opus logo">
Speex:link:<img src="https://upload.wikimedia.org/wikipedia/commons/b/b5/Speex_logo_2006.svg" width="80" alt="Speex logo">
Theora:link:<img src="https://upload.wikimedia.org/wikipedia/commons/5/57/Theora_logo_2007.svg" width="70" alt="Theora logo">
VorbisVorbis audio compression:link:<img src="https://upload.wikimedia.org/wikipedia/commons/8/8d/Xiph.Org_logo_square.svg" width="70" alt="Vorbis logo">
WAVRIFF WAVE:link:
WebMwebm:link:<img src="https://upload.wikimedia.org/wikipedia/commons/3/34/WebM_logo.svg" width="80" alt="Matroska logo">
WVWavPack:link:<img src="http://www.wavpack.com/wavpacklogo.svg" width="60" alt="WavPack logo">
WMAWindows Media Audio:link:<img src="https://upload.wikimedia.org/wikipedia/commons/7/76/Windows_Media_Player_simplified_logo.svg" width="40" alt="Windows Media logo">

Supported tag headers

Following tag header formats are supported:

It allows many tags to be accessed in audio format, and tag format independent way.

Support for MusicBrainz tags as written by Picard. ReplayGain tags are supported.

Audio format & encoding details

Support for encoding / format details:

Online demo's

Usage

Installation

Install using npm:

npm install music-metadata

or using yarn:

yarn add music-metadata

API Documentation

Overview

Node.js specific functions to read an audio file or stream:

  1. File Parsing: Parse audio files directly from the filesystem using the parseFile function
  2. Stream Parsing: Parse audio metadata from a Node.js Readable stream using the parseStream function.

Cross-platform functions available to read an audio file or stream:

There are multiple ways to parse (read) audio tracks:

  1. Web Stream Parsing: Parse audio data from a web-compatible ReadableStream using the parseWebStream function.
  2. Blob Parsing: Parse audio metadata from a (Web API) Blob or File using the parseBlob function.
  3. Buffer Parsing: Parse audio metadata from a Uint8Array or Buffer using the parseBuffer function.
  4. Tokenizer Parsing: Use a custom or third-party strtok3 ITokenizer to parse using the parseFromTokenizer function.

[!NOTE] Direct file access in Node.js is generally faster because it can 'jump' to various parts of the file without reading intermediate data.

Node.js specific function

These functions are tailored for Node.js environments and leverage Node.js-specific APIs, making them incompatible with browser-based JavaScript engines.

parseFile function

The parseFile function is intended for extracting metadata from audio files on the local filesystem in a Node.js environment. It reads the specified file, parses its audio metadata, and returns a promise that resolves with this information.

Syntax
parseFile(filePath: string, options?: IOptions): Promise<IAudioMetadata>
Parameters
Returns
Usage Notes
Example:

The following example demonstrates how to use the parseFile function to read metadata from an audio file:

import { parseFile } from 'music-metadata';
import { inspect } from 'util';

(async () => {
  try {
    const filePath = '../music-metadata/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);
  }
})();

parseStream function

The parseStream function is used to parse metadata from an audio track provided as a Node.js Readable stream. This is particularly useful for processing audio data that is being streamed or piped from another source, such as a web server or file system.

Syntax:
parseStream(stream: Readable, fileInfo?: IFileInfo | string, options?: IOptions): Promise<IAudioMetadata>
Parameters:
Returns
Usage Notes
Example:

The following example demonstrates how to use the parseStream function to read metadata from an audio stream:

import { parseStream } from 'music-metadata';
import { createReadStream } from 'fs';

(async () => {
  try {
    // Create a readable stream from a file
    const audioStream = createReadStream('path/to/audio/file.mp3');

    // Parse the metadata from the stream
    const metadata = await parseStream(audioStream, { mimeType: 'audio/mpeg'});

    // Log the parsed metadata
    console.log(metadata);
  } catch (error) {
    console.error('Error parsing metadata:', error.message);
  }
})();

Cross-platform functions

These functions are designed to be cross-platform, meaning it can be used in both Node.js and web browsers.

parseWebStream function

The parseWebStream function is used to extract metadata from an audio track provided as a web-compatible ReadableStream. This function is ideal for applications running in web environments, such as browsers, where audio data is streamed over the network or read from other web-based sources.

Syntax
parseWebStream(webStream: ReadableStream<Uint8Array>, fileInfo?: IFileInfo | string, options?: IOptions): Promise<IAudioMetadata>
Parameters
Returns
Example

Here’s an example of how to use the parseWebStream function to extract metadata from an audio stream in a web application:

import { parseWebStream } from 'music-metadata';

(async () => {
try {
// Assuming you have a ReadableStream of an audio file
const response = await fetch('https://example.com/path/to/audio/file.mp3');
const webStream = response.body;

    // Parse the metadata from the web stream
    const metadata = await parseWebStream(webStream, 'audio/mpeg');

    // Log the parsed metadata
    console.log(metadata);
} catch (error) {
console.error('Error parsing metadata:', error.message);
}
})();

The example uses the fetch API to retrieve an audio file from a URL. The response.body provides a ReadableStream that is then passed to parseWebStream.

parseBlob function

Parses metadata from an audio file represented as a Blob. This function is suitable for use in environments that support the ReadableStreamBYOBReader, which is available in Node.js 20 and above.

Syntax
parseBlob(blob: Blob, options?: IOptions = {}): Promise<IAudioMetadata>
Parameters
Returns
Example
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);
  }
})();

parseBuffer function

Parses metadata from an audio file where the audio data is held in a Uint8Array or Buffer. This function is particularly useful when you already have audio data in memory.

Syntax
parseBuffer(buffer: Uint8Array, fileInfo?: IFileInfo | string, opts?: IOptions = {}): Promise<IAudioMetadata>
Parameters
Returns
Example
import { parseBuffer } from 'music-metadata';
import fs from 'fs';

(async () => {
  const buffer = fs.readFileSync('path/to/audio/file.mp3');

  try {
    const metadata = await parseBuffer(buffer, { mimeType: 'audio/mpeg' });
    console.log(metadata);
  } catch (error) {
    console.error('Error parsing metadata:', error.message);
  }
})();

parseFromTokenizer function

Parses metadata from an audio source that implements the strtok3 ITokenizer interface. This is a low-level function that provides flexibility for advanced use cases, such as parsing metadata from streaming audio or custom data sources.

This also enables special read modules like:

Syntax
parseFromTokenizer(tokenizer: ITokenizer, options?: IOptions): Promise<IAudioMetadata>
Parameters
Returns
Example
import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
import { S3Client } from '@aws-sdk/client-s3';
import { makeTokenizer } from '@tokenizer/s3';
import { parseFromTokenizer as mmParseFromTokenizer } from 'music-metadata';

// Configure the S3 client
const s3 = new S3Client({
  region: 'eu-west-2',
  credentials: fromNodeProviderChain(),
});

// Helper function to create a tokenizer for S3 objects
async function makeS3TestDataTokenizer(key, options) {
  return await makeTokenizer(s3, {
    Bucket: 'music-metadata',
    Key: key,
  }, options);
}

// Function to read and log metadata from an S3 object
async function readMetadata() {
  try {
    // Create a tokenizer for the specified S3 object
    const tokenizer = await makeS3TestDataTokenizer('path/to/audio/file.mp3', { disableChunked: false });

    // Parse the metadata from the tokenizer
    const metadata = await mmParseFromTokenizer(tokenizer);

    // Log the retrieved metadata
    console.log(metadata);
  } catch (error) {
    console.error('Error parsing metadata:', error.message);
  }
}

// Execute the metadata reading function
readMetadata();
Additional Resources

Handling Parse Errors

music-metadata provides a robust and extensible error handling system with custom error classes that inherit from the standard JavaScript Error. All possible parsing errors are part of a union type UnionOfParseErrors, ensuring that every error scenario is accounted for in your code.

Union of Parse Errors

All parsing errors extend from the base class ParseError and are included in the UnionOfParseErrors type:

export type UnionOfParseErrors =
  | CouldNotDetermineFileTypeError
  | UnsupportedFileTypeError
  | UnexpectedFileContentError
  | FieldDecodingError
  | InternalParserError;

Error Types

Other functions

orderTags function

Utility to Converts the native tags to a dictionary index on the tag identifier

orderTags(nativeTags: ITag[]): [tagId: string]: any[]
import { parseFile, orderTags } from 'music-metadata';
import { inspect } from 'util';

(async () => {
  try {
    const metadata = await parseFile('../test/samples/MusicBrainz - Beth Hart - Sinner\'s Prayer [id3v2.3].V2.mp3');
    const orderedTags = orderTags(metadata.native['ID3v2.3']);
    console.log(inspect(orderedTags, { showHidden: false, depth: null }));
  } catch (error) {
    console.error(error.message);
  }
})();

ratingToStars function

Can be used to convert the normalized rating value to the 0..5 stars, where 0 an undefined rating, 1 the star the lowest rating and 5 the highest rating.

ratingToStars(rating: number): number

selectCover function

Select cover image based on image type field, otherwise the first picture in file.

export function selectCover(pictures?: IPicture[]): IPicture | null
import { parseFile, selectCover } from 'music-metadata';

(async () => {
  const {common} = await parseFile(filePath);
  const cover = selectCover(common.picture); // pick the cover image
}
)();

IOptions Interface

[!NOTE]

IAudioMetadata interface

If the returned promise resolves, the metadata (TypeScript IAudioMetadata interface) contains:

metadata.format

The questionmark ? indicates the property is optional.

Audio format information. Defined in the TypeScript IFormat interface:

metadata.trackInfo

To support advanced containers like Matroska or MPEG-4, which may contain multiple audio and video tracks, the experimental- metadata.trackInfo has been added,

metadata.trackInfo is either undefined or has an array of trackInfo

trackInfo

Audio format information. Defined in the TypeScript IFormat interface:

trackInfo.audioTrack
trackInfo.videoTrack

metadata.common

Common tag documentation is automatically generated.

Examples

In order to read the duration of a stream (with the exception of file streams), in some cases you should pass the size of the file in bytes.

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();
  }
)();

Access cover art

Via metadata.common.picture you can access an array of cover art if present. Each picture has this interface:

/**
 * Attached picture, typically used for cover art
 */
export interface IPicture {
  /**
   * Image mime type
   */
  format: string;
  /**
   * Image data
   */
  data: Buffer;
  /**
   * Optional description
   */
  description?: string;
  /**
   * Picture type
   */
  type?: string;
}

To assign img HTML-object you can do something like:

import {uint8ArrayToBase64} from 'uint8array-extras';

img.src = `data:${picture.format};base64,${uint8ArrayToBase64(picture.data)}`;

Dependencies

Dependency diagram:

graph TD;
    MMN("music-metadata (Node.js entry point)")-->MMP
    MMN-->FTN
    MMP("music-metadata (primary entry point)")-->S(strtok3)
    MMP-->TY(token-types)
    MMP-->FTP
    MMP-->UAE
    FTN("file-type (Node.js entry point)")-->FTP
    FTP("file-type (primary entry point)")-->S
    S(strtok3)-->P(peek-readable)
    S(strtok3)-->TO("@tokenizer/token")
    TY(token-types)-->TO
    TY-->IE("ieee754")
    FTP-->TY
    NS("node:stream")
    FTN-->NS
    FTP-->UAE(uint8array-extras)
    style NS fill:#F88,stroke:#A44
    style IE fill:#CCC,stroke:#888
    style FTN fill:#FAA,stroke:#A44
    style MMN fill:#FAA,stroke:#A44

Dependency list:

CommonJS backward compatibility

For legacy CommonJS projects needing to load the music-metadata ESM module, you can use the loadMusicMetadata function:

const { loadMusicMetadata } = require('music-metadata');

(async () => {
  // Dynamically loads the ESM module in a CommonJS project
  const mm = await loadMusicMetadata();

  const metadata = await mm.parseFile('/path/to/your/file');
})();

[!NOTE] The loadMusicMetadata function is experimental.

Frequently Asked Questions

  1. How can I traverse (a long) list of files?

    What is important that file parsing should be done in a sequential manner. In a plain loop, due to the asynchronous character (like most JavaScript functions), it would cause all the files to run in parallel which is will cause your application to hang in no time. There are multiple ways of achieving this:

    1. 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); // process rest of the files AFTER we are finished
          })
        }
      }
      
      
    2. Use async/await

      Use async/await

      import { parseFile } from 'music-metadata';
      
      // it is required to declare the function 'async' to allow the use of await
      async function parseFiles(audioFiles) {
      
          for (const audioFile of audioFiles) {
      
              // await will ensure the metadata parsing is completed before we move on to the next file
              const metadata = await parseFile(audioFile);
              // Do great things with the metadata
          }
      }
      

Licence

The MIT License (MIT)

Copyright © 2024 Borewit

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.