Coder Social home page Coder Social logo

borewit / music-metadata Goto Github PK

View Code? Open in Web Editor NEW
888.0 6.0 90.0 147.85 MB

Stream and file based music metadata parser for node. Supporting a wide range of audio and tag formats.

License: MIT License

JavaScript 0.32% TypeScript 99.68%
metadata musicbrainz picard tag tags id3 flac mp3 mp4 vorbis

music-metadata's Introduction

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

music-metadata

Key features:

  • Comprehensive Format Support: Supports popular audio formats like MP3, MP4, FLAC, Ogg, WAV, AIFF, and more.
  • Extensive Metadata Extraction: Extracts detailed metadata, including ID3v1, ID3v2, APE, Vorbis, and iTunes/MP4 tags.
  • Streaming Support: Efficiently handles large audio files by reading metadata from streams, making it suitable for server-side and browser-based applications.
  • Promise-Based API: Provides a modern, promise-based API for easy integration into asynchronous workflows.
  • Cross-Platform: Works in both Node.js and browser environments with the help of bundlers like Webpack or Rollup.

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.

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

Sponsor

If you appreciate my work and want to support the development of open-source projects like music-metadata, file-type, and listFix(), consider becoming a sponsor or making a small contribution. Your support helps sustain ongoing development and improvements. Become a sponsor to Borewit

or

Buy me A coffee

Features

Support for audio file types

Audio format Description Wiki
AIFF / AIFF-C Audio Interchange File Format ๐Ÿ”— Apple rainbow logo
AAC ADTS / Advanced Audio Coding ๐Ÿ”— AAC logo
APE Monkey's Audio ๐Ÿ”— Monkey's Audio logo
ASF Advanced Systems Format ๐Ÿ”—
BWF Broadcast Wave Format ๐Ÿ”—
DSDIFF Philips DSDIFF ๐Ÿ”— DSD logo
DSF Sony's DSD Stream File ๐Ÿ”— DSD logo
FLAC Free Lossless Audio Codec ๐Ÿ”— FLAC logo
MP2 MPEG-1 Audio Layer II ๐Ÿ”—
Matroska Matroska (EBML), mka, mkv ๐Ÿ”— Matroska logo
MP3 MPEG-1 / MPEG-2 Audio Layer III ๐Ÿ”— MP3 logo
MPC Musepack SV7 ๐Ÿ”— musepack logo
MPEG 4 mp4, m4a, m4v ๐Ÿ”— mpeg 4 logo
Ogg Open container format ๐Ÿ”— Ogg logo
Opus ๐Ÿ”— Opus logo
Speex ๐Ÿ”— Speex logo
Theora ๐Ÿ”— Theora logo
Vorbis Vorbis audio compression ๐Ÿ”— Vorbis logo
WAV RIFF WAVE ๐Ÿ”—
WebM webm ๐Ÿ”— Matroska logo
WV WavPack ๐Ÿ”— WavPack logo
WMA Windows Media Audio ๐Ÿ”— 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
  • filePath: string

    The path to the media file from which metadata should be extracted. This should be a valid path to an audio file on the local filesystem.

  • options: IOptions (optional)

    An optional configuration object that allows customization of the parsing process. These options can include whether to calculate the file's duration, skip embedded cover art, or other parsing behaviors.

Returns
  • Promise<IAudioMetadata>:

    A promise that resolves to an IAudioMetadata object containing metadata about the audio file. The metadata includes details such as the file format, codec, duration, bit rate, and any embedded tags like album, artist, or track information.

Usage Notes
  • This function is Node.js-only and relies on Node.js-specific APIs to access the filesystem.

  • For browser environments, consider using the parseBlob to parse File object objects.

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:
  • stream: Readable:

    The Node.js Readable stream from which the audio data is read. This stream should provide the raw audio data to be analyzed.

  • fileInfo: IFileInfo (optional)

    An object containing file-related information or a string representing the MIME-type of the audio stream. The fileInfo parameter can help the parser to correctly identify the audio format and may include:

    • mimeType: A string representing the MIME-type (e.g., audio/mpeg).

      If provided, it is assumed the streamed file content is to be the MIME-type. If not provided, the parser will attempt to determine the format based on the content of the stream.

    • size: The total size of the audio stream in bytes (useful for streams with a known length).

    • path: A string representing the file path or filename, which can also assist in determining the format.

  • options: IOptions (optional)

    An optional object containing additional parsing options. These options allow you to customize the parsing process, such as whether to calculate the duration or skip cover art extraction.

Returns
  • Promise<IAudioMetadata>:

    A promise that resolves to an IAudioMetadata object containing detailed metadata about the audio stream. This metadata includes information about the format, codec, duration, bitrate, and any embedded tags such as artist, album, or track information.

Usage Notes
  • This function is only available in Node.js environments, as it relies on the Node.js stream API.
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
  • webStream: ReadableStream<Uint8Array>

    A ReadableStream that provides the audio data to be parsed. This stream should emit Uint8Array chunks, representing the raw audio data.

  • fileInfo: IFileInfo (optional)

    An object containing file-related information or a string representing the MIME-type of the audio stream. The fileInfo parameter can help the parser to correctly identify the audio format and may include:

    • mimeType: A string representing the MIME-type (e.g., audio/mpeg).

      If provided, it is assumed the streamed file content is to be the MIME-type. If not provided, the parser will attempt to determine the format based on the content of the stream.

    • size: The total size of the audio stream in bytes (useful for streams with a known length).

    • path: A string representing the file path or filename, which can also assist in determining the format.

  • options: IOptions (optional)

    An optional object containing additional parsing options. These options allow you to customize the parsing process, such as whether to calculate the duration or skip cover art extraction.

Returns
  • Promise<IAudioMetadata>:

    A promise that resolves to an IAudioMetadata object containing detailed metadata about the audio stream. This metadata includes information about the format, codec, duration, bitrate, and any embedded tags such as artist, album, or track information.

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
  • blob: Blob

    The Blob object containing the audio data to be parsed. This can be a file or any binary data. If the Blob is an instance of File, its name will be used as the file path in the metadata.

  • options: IOptions (optional)

    An optional configuration object that specifies parsing options.

Returns
  • Promise<IAudioMetadata>:

    A promise that resolves to the metadata of the audio file.

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
  • uint8Array: Uint8Array

    A Uint8Array containing the audio data to be parsed.

  • fileInfo: IFileInfo | string (optional)

    An object containing file information such as mimeType and size. Alternatively, you can pass a MIME-type string directly. This helps the parser understand the format of the audio data.

  • options: IOptions (optional)

    An optional configuration object that specifies parsing options.

Returns
  • Promise<IAudioMetadata>:

    A promise that resolves to the metadata of the audio file.

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
  • tokenizer: ITokenizer

    An instance of an ITokenizer that provides access to the audio data. The tokenizer abstracts the reading process, enabling support for various types of sources, including streams, buffers, or custom data readers.

  • options: IOptions (optional)

    An optional configuration object that specifies parsing options.

Returns
  • Promise<IAudioMetadata>:

    A promise that resolves to the metadata of the audio source, including information like the title, artist, album, and more.

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
  • strtok3 - Learn more about the ITokenizer interface and how to implement it for various use cases.
  • AWS SDK for JavaScript - Documentation on using the AWS SDK to interact with S3 and other AWS services.
  • @tokenizer/s3 - Example of ITokenizer implementation.

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

  • duration: boolean (default: false)

    If set to true, the parser will analyze the entire media file, if necessary, to determine its duration. This option ensures accurate duration calculation but may increase processing time for large files.

  • observer: (update: MetadataEvent) => void;:

    A callback function that is invoked whenever there is an update to the common (generic) tag or format properties during parsing. This allows for real-time updates on metadata changes.

  • skipCovers: boolean (default: false)

    If set to true, the parser will skip the extraction of embedded cover art (images) from the media file. This can be useful to avoid processing unnecessary data if cover images are not required.

  • mkvUseIndex: boolean (default: false)

    If set to true, the parser will use the SeekHead element index to skip segment/cluster elements in Matroska-based files. This is an experimental feature and can significantly impact performance. It may also result in some metadata being skipped if it is not indexed. If the SeekHead element is absent in the Matroska file, this flag has no effect.

Note

  • The duration option is typically included in most cases, but setting it to true ensures that the entire file is parsed if necessary to get an accurate duration.
  • Using mkvUseIndex can improve performance in Matroska files, but be aware of potential side effects, such as missing metadata due to skipped elements.

IAudioMetadata interface

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

  • metadata.format Audio format information
  • metadata.common Is a generic (abstract) way of reading metadata information.
  • metadata.trackInfo Is a generic (abstract) way of reading metadata information.
  • metadata.native List of native (original) tags found in the parsed audio file.

metadata.format

The questionmark ? indicates the property is optional.

Audio format information. Defined in the TypeScript IFormat interface:

  • format.container?: string Audio encoding format. e.g.: 'flac'
  • format.codec? Name of the codec (algorithm used for the audio compression)
  • format.codecProfile?: string Codec profile / settings
  • format.tagTypes?: TagType[] List of tagging formats found in parsed audio file
  • format.duration?: number Duration in seconds
  • format.bitrate?: number Number bits per second of encoded audio file
  • format.sampleRate?: number Sampling rate in Samples per second (S/s)
  • format.bitsPerSample?: number Audio bit depth
  • format.lossless?: boolean True if lossless, false for lossy encoding
  • format.numberOfChannels?: number Number of audio channels
  • format.creationTime?: Date Track creation time
  • format.modificationTime?: Date Track modification / tag update time
  • format.trackGain?: number Track gain in dB
  • format.albumGain?: number Album gain in dB

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.type?: TrackType Track type
  • trackInfo.codecName?: string Codec name
  • trackInfo.codecSettings?: string Codec settings
  • trackInfo.flagEnabled?: boolean Set if the track is usable, default: true
  • trackInfo.flagDefault?: boolean Set if that track (audio, video or subs) SHOULD be active if no language found matches the user preference.
  • trackInfo.flagLacing?: boolean Set if the track may contain blocks using lacing
  • trackInfo.name?: string A human-readable track name.
  • trackInfo.language?: string Specifies the language of the track
  • trackInfo.audio?: IAudioTrack, see trackInfo.audioTrack
  • trackInfo.video?: IVideoTrack, see trackInfo.videoTrack
trackInfo.audioTrack
  • audioTrack.samplingFrequency?: number
  • audioTrack.outputSamplingFrequency?: number
  • audioTrack.channels?: number
  • audioTrack.channelPositions?: Buffer
  • audioTrack.bitDepth?: number
trackInfo.videoTrack
  • videoTrack.flagInterlaced?: boolean
  • videoTrack.stereoMode?: number
  • videoTrack.pixelWidth?: number
  • videoTrack.pixelHeight?: number
  • videoTrack.displayWidth?: number
  • videoTrack.displayHeight?: number
  • videoTrack.displayUnit?: number
  • videoTrack.aspectRatioType?: number
  • videoTrack.colourSpace?: Buffer
  • videoTrack.gammaValue?: number

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
Loading

Dependency list:

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.

music-metadata's People

Contributors

andrewrk avatar aodev avatar arthi-chaud avatar bjornstar avatar borewit avatar certuna avatar cobalamin avatar dependabot-preview[bot] avatar dependabot[bot] avatar dertseha avatar drvirtuozov avatar horaceli avatar jpage-godaddy avatar leetreveil avatar motabass avatar mutewinter avatar nirbhayk avatar ondras avatar onerpm avatar pmarques avatar rasuni avatar rodu avatar rossgrady avatar sayem314 avatar scottgarner avatar snyk-bot avatar tim-smart avatar trustedtomato avatar tyhchoi avatar ws avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

music-metadata's Issues

Use straight file access instead of streams

Move away from the streaming approach and use straight file access; a stream is very inefficient in some cases (cannot jump (seek) to the end of the file, without reading everything in between).

Pros:

  • Better performance.
  • Opens the door for writing meta-data in addition to read meta-data.

Cons:

  • Would make this module less suitable for running in a browser.

ID3v2.3 MusicBrainz Picard mapping not accurate

I used a common mapping table fot both ID3v2.3 and ID3v2.4 mappings. It looks like the v2.3 is pretty different. Unit tests survived because they contain ID3v2.4 tag keys stored in ID3v2.3 format.

Question: Error: EMFILE: too many open files

At first, thanks for this reader.

I want to read over 80k mp3 tags for my Software.
But after 7k the performance break down an finished with:

Error: EMFILE: too many open files....

Can someone help me to queue the async calls, with async or something like that:
https://caolan.github.io/async/docs.html#queue

Thanks

me code now is:

walker = walk.walk("/Volumes/..");
var mm = require('music-metadata');
const util = require('util');
walker.on("file", function (root, fileStats, next) {
 //todo, noch die anderen file extension freigeben
 if (path.extname(fileStats.name) === ".mp3") {
   mm.parseFile(path.join(root, fileStats.name)).then(function (metadata) {
     console.log(util.inspect(metadata, { showHidden: false, depth: null }));
   }).catch(function (err) {
     console.error(err.message);
   });
 }
 next();
});

Very slow parsing on files that need `duration: true`

I've come across some files that don't parse out a duration property. Setting the duration: true option works, but it results in a very slow parse:

{duration: true}: 12238.523ms  # duration: true
{duration: false}: 2.982ms # duration: false

(example repo: https://github.com/ballpit/parsing-speed uses LFS)'

Any ideas why this file takes so long to parse out a duration property? Are there any optimizations to this case that could be done to speed it up?

Fantastic module and work btw!

Incorrect metadata retrieval again

After I had learned that some of my mp3's had multiple headers, I reset them to make every kind of headers (IDv1, IDv2 and so on) have the same tag values, but music-metadata still keeps failing to get correct metadata from some of them. I double-checked the files in question had headers properly set with at least two metadata editors.

I attach two samples to help you figure out the cause.

sample1.mp3.zip
sample2.mp3.zip

Support calculating approximate duration

Giving the duration: true option makes all media files be fully scanned to calculate their accurate duration, but when a large number of files should be handled, it is enough to get approximate duration of songs; most clients to play them do not trust what the server gives anyway.

How about adding an option like approximate: true to duration: true for approximate duration? As I recall, an old version of musicmetadata did that by assuming CBR when the first three frames have the same bit-rate; I ported that to mp3-duration (mycoboco/mp3-duration@abdb1bc).

Memory leak by parsing special mp3 file

On parsing special single file, the node enviremont used more then 1,4 gb of ram and exit with:

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

@Borewit I sended you one file by mail for test, if u or another want more example please contact me.

Possible issue with ID3 titles containing a forward slash

ID3v2.3 title tags containing forward slash characters appear to be parsed as multiple separate title tags, breaking on the slash. Is this expected behaviour / part of the ID3 spec, or a bug?

I have not noticed this behaviour in any other player / tag editor / parser when opening the same file.

Example:
A song with the title "This/That" returns the following two ID3v2.3 native tags:

[{
  id: "TIT2",
  value: "This"
}, {
  id: "TIT2",
  value: "That
}]

Thank you!

FIxes for #38 and #39 have side effects

After upgrading to 0.8.3, I don't have errors anymore when parsing mp3 files, but some files have incorrect field values. For the attached file, e.g., 0.8.2 gives:

{ format: 
   { dataformat: 'mp3',
     lossless: false,
     bitrate: 192000,
     sampleRate: 44100,
     numberOfChannels: 2,
     codecProfile: 'CBR',
     encoder: 'LAME3.92 ',
     duration: 154.85387755102042,
     tagTypes: [ 'ID3v2.3', 'ID3v1.1' ] },
  native: undefined,
  common: 
   { track: { no: 7, of: null },
     disk: { no: null, of: null },
     album: 'Come Away With Me',
     artist: 'Norah Jones',
     genre: [ 'Jazz' ],
     label: 'Blue Note',
     title: 'Turn Me On',
     year: 2002,
     picture: [ [Object] ],
     artists: [ 'Norah Jones' ] } }

while 0.8.3 gives:

{ format: 
   { lossless: false,
     dataformat: 'mp3',
     bitrate: 192000,
     sampleRate: 44100,
     numberOfChannels: 2,
     codecProfile: 'CBR',
     encoder: 'LAME3.92 ',
     duration: 154.85387755102042,
     tagTypes: [ 'ID3v2.3', 'ID3v2.4', 'ID3v1.1' ] },
  native: undefined,
  common: 
   { track: { no: 7, of: null },
     disk: { no: null, of: null },
     title: 'Turn Me On',
     artist: 'Norah Jones',
     album: 'Come Away With Me',
     comment: [ [Object] ],
     genre: [ 'www.mp3-ogg.ru' ],
     copyright: 'Make Love Not War',
     artists: [ 'Norah Jones' ] } }

That is, incorrect genre, copyright and comment fields.

Similar issues arises for other files, but I suspect they are from the same reason.

sample.zip

Support for opus and question about cover art

This is AWESOME, it's almost an all in one.

Would it be possible in the future to add support for opus file? :)

Also, can I read cover art from FLAC files? Some of my files have this - no idea how they did it.

Thanks ๐Ÿ‘

Roadmap v1.0

I just copied the goals into this issue to keep track of them. We can later just create a checklist out of them.

  • Re-factoring:
    • Move away from the readstream approach and use straight file access; a stream is very inefficient in some cases (cannot jump to the end of the file, without reading everything in between) and cumbersome in my opinion. For instance, if a FLAC file prefixed with an id3 header (bad practice, non-standard, but it is done) this code gets completely confused, because it expects mpeg.
      Moved to issue #7
    • Use Promises.
  • Improve tests:
    • Use a test framework like mocha or jest. I used jest for the last project, super easy to work with and supports multi-threaded tests.
      Moved to issue #8
    • Translate test case code to TypeScript: This brings up more problems than it actually brings benefits.
  • Improve documentation:
    • Write detailed documentation on the GitHib Wiki how the tag mapping is done
  • New features / functionality:
    • In addition to MusicBrainz tags, support Discogs tags (see #9), maybe iTunes tags
    • Report on errors (see #60)
    • Be able to write tags (see #10)
    • Support audio CRC values

Edit:
Concerning the refactoring. It would probably be better to start from scratch and use the current code base as a reference.
Changed the title to Roadmap v1.0. Refactoring will probably break the API. Bumping the version up might be a good idea.

Parse Discogs tags.

Current implementation is strongly based on MusicBrainz tags.

In addition to MusicBrainz, support for Discogs tags, e.g.:

  • discogs_artist_id
  • discogs_artist_name
  • discogs_catalog
  • discogs_country
  • discogs_date
  • discogs_label
  • discogs_label_id
  • discogs_master_release_id
  • discogs_rating
  • discogs_release_id
  • discogs_released
  • discogs_votes

ToDo:

  • Map Discogs rating to common.rating
  • Avoid duplicate values
    • Catalog (label) number
    • Artist name
    • Country

Remove .ts files from published npm module

The published npm module contains both the typescript and javascript files.
When using the module with tsloader and webpack, it throws about 117 errors.

ERROR in ./node_modules/music-metadata/lib/index.ts
Module parse failed: E:\electron\nighthawk\node_modules\music-metadata\lib\index.ts Unexpected token (6:20)
You may need an appropriate loader to handle this file type.
| import common from './common';
| import TagMap, {HeaderType} from './tagmap';
| import EventEmitter = NodeJS.EventEmitter;
| import {ParserFactory} from "./ParserFactory";
| import * as Stream from "stream";
 @ ./src/background/library.ts 3:0-37
 @ ./src/ui/pages.tsx
 @ ./src/ui/shell.tsx
 @ ./src/index.tsx
 @ multi (webpack)-dev-server/client?http://localhost:8080 webpack/hot/only-dev-server react-hot-loader/patch ./src/index.tsx

ERROR in [at-loader] ./node_modules/music-metadata/lib/ParserFactory.ts:168:11
    TS6133: 'warning' is declared but never used.

ERROR in [at-loader] ./node_modules/music-metadata/lib/Windows1292Decoder.ts:23:26
    TS7006: Parameter 'a' implicitly has an 'any' type.

ERROR in [at-loader] ./node_modules/music-metadata/lib/Windows1292Decoder.ts:23:29
    TS7006: Parameter 'min' implicitly has an 'any' type.

This occurs due to tsloader trying to load the .ts files instead of .js ones. Setting allowsJs to true does not resolve the errors.

When i manually delete the .ts files while keeping the .js and .d.ts files, the module works perfectly.

This occurs for both music-metadata and strtok3.

Typing on ICommonTagsResult.comment appears incorrect

The type of ICommonTagsResult.comment is declared as string[] in index.d.ts in the root of the package, but in practise it appears to actually return:

{
    description: string,
    language: string,
    text: string,
}[]

Observed when installing v0.9.2.
I had a very brief look through some of the source but I couldn't immediately trace through how the comment tag data was being populated (due to my lack of familiarity with the project).
I can provide further detail if needed.

Support rating tag

Related issue: leetreveil/musicmetadata#137

Parse rating tags and map it to a common tag.
In line with MusicBrainz Picard, normalize the common tag to a scale from 0 to 5.0:

            elif frameid == 'POPM':
                # Rating in ID3 ranges from 0 to 255, normalize this to the range 0 to 5
                if frame.email == config.setting['rating_user_email']:
                    rating = string_(int(round(frame.rating / 255.0 * (config.setting['rating_steps'] - 1))))
                    metadata.add('~rating', rating)

source: https://github.com/metabrainz/picard/blob/master/picard/formats/id3.py

Update common type definition of the barcode

Currently the barcode (common.barcode) is an JavaScript number.
One of the disadvantages is that a barcode like 0 64027 11642 7 becomes 64027 11642.

Therefor I suggest to change the type to a string.

Error when executing example

Just wanted to try the drag and drop example. I get the following error:

angular.js:13920init controller...
angular.js:13920 handleDropFiles: [File]
angular.js:13920 Retrieving metadata of file "01 How You Love Me (Axero Remix).m4a"....
music-metadata.js:317 Uncaught TypeError: Cannot read property 'queue' of undefined
    at Stream.through.autoDestroy (music-metadata.js:317)
    at Stream.stream.write (music-metadata.js:9655)
    at Class.ondata (music-metadata.js:7552)
    at Class.EventEmitter.emit (music-metadata.js:6104)
    at readableAddChunk (music-metadata.js:7223)
    at Class.Readable.push (music-metadata.js:7182)
    at check (music-metadata.js:6424)
    at FileReader.loaded (music-metadata.js:6343)
through.autoDestroy @ music-metadata.js:317
stream.write @ music-metadata.js:9655
ondata @ music-metadata.js:7552
EventEmitter.emit @ music-metadata.js:6104
readableAddChunk @ music-metadata.js:7223
Readable.push @ music-metadata.js:7182
check @ music-metadata.js:6424
loaded @ music-metadata.js:6343
angular.js:13920 handleDropFiles: [File]
angular.js:13920 Retrieving metadata of file "Light Into Darkness [Premiere].mp3"....
music-metadata.js:317 Uncaught TypeError: Cannot read property 'queue' of undefined
    at Stream.through.autoDestroy (music-metadata.js:317)
    at Stream.stream.write (music-metadata.js:9655)
    at Class.ondata (music-metadata.js:7552)
    at Class.EventEmitter.emit (music-metadata.js:6104)
    at readableAddChunk (music-metadata.js:7223)
    at Class.Readable.push (music-metadata.js:7182)
    at check (music-metadata.js:6424)
    at FileReader.loaded (music-metadata.js:6343)
through.autoDestroy @ music-metadata.js:317
stream.write @ music-metadata.js:9655
ondata @ music-metadata.js:7552
EventEmitter.emit @ music-metadata.js:6104
readableAddChunk @ music-metadata.js:7223
Readable.push @ music-metadata.js:7182
check @ music-metadata.js:6424
loaded @ music-metadata.js:6343

Both files contain music metadata, I checked it afterwards using ID3Tag.

Encoding windows-1250

Hi, We are processing MP3 files from our supplier and some ID3 strings are saved in exotic encoding. I guess windows-1250. In this case, strings with accented chars are read corrupted.

this.s.once is not a function

While trying to read an audio stream, the package throw below error.

Uncaught TypeError: this.s.once is not a function at new StreamReader (node_modules/then-read-stream/lib/index.js:28:16) at new ReadStreamTokenizer (node_modules/strtok3/lib/ReadStreamTokenizer.js:20:30) at Object.fromStream (node_modules/strtok3/lib/index.js:37:28) at Function.ParserFactory.parseStream (node_modules/music-metadata/lib/ParserFactory.js:54:24) at MusicMetadataParser.parseStream (node_modules/music-metadata/lib/index.js:103:46) at Object.parseStream (node_modules/music-metadata/lib/index.js:289:46) at audioType (app/controllers/uploads.js:63:15) at IncomingMessage.<anonymous> (app/controllers/uploads.js:104:11) at IncomingMessage.Readable.read (_stream_readable.js:381:10) at flow (_stream_readable.js:761:34) at resume_ (_stream_readable.js:743:3) at _combinedTickCallback (internal/process/next_tick.js:74:11) at process._tickDomainCallback (internal/process/next_tick.js:122:9)

EBADF error when parsing a large media collection

I'm running into a strange issue after porting some code from the original musicmetadata package. When the console.log is uncommented, the example below can parse 2000+ music files without ever having a single issue.

However, the moment you comment out that line. calling .close() on the fileStream will occasionally throw a EBADF: bad file descriptor, read after parsing some of the files.

MUSIC-METADATA:

import * as Chokidar from "chokidar"
import * as Path from "path"
import * as Fs from "fs"
import * as Metadata  from "music-metadata"

let tags = []
let watcher = Chokidar.watch(
  "/Music/**/*.mp3",
  {
    ignoreInitial: false,
    alwaysStat: false
  }
)

watcher.on("add", (file, fstat) =>
{
  let fileStream = Fs.createReadStream(file)
  Metadata.parseStream(fileStream, { native: true }, (error, metadata) =>
  {
    // console.log("no EBADF ever if this line uncommented")
    fileStream.close() // will occasionally throw `EBADF: bad file descriptor, read`
    tags.push(metadata)
  })
})

The same code using the original musicmetadata package will iterate over the same files without ever once throwing an EBADF error, and without the need of the console.log

MUSICMETADATA:

import * as Chokidar from "chokidar"
import * as Path from "path"
import * as Fs from "fs"
import * as Metadata  from "musicmetadata"

let tags = []
let watcher = Chokidar.watch(
  "/Music/**/*.mp3",
  {
    ignoreInitial: false,
    alwaysStat: false
  }
)

watcher.on("add", (file, fstat) =>
{
  let fileStream = Fs.createReadStream(file)
  Metadata(fileStream, (error, metadata) =>
  {
    fileStream.close()
    tags.push(metadata)
  })
})

No metadata found in files with metadata

I have two files that have music tags, but they are not seen by this parser and object is returned with nulls in properties while thousands of other files do not have such problem. I can't figure out what is wrong with these two songs, maybe I'm missing something.

Songs.zip

mpeg parsing fails for irrelevant attributes

I have some files in which the parsing fails on:

Invalid MPEG Audio version

or

expected frame header but was not found

I've "fixed" both errors by disabling them in the code (file: mpeg.js):

if (this.version === null) {
    this.version = 1;
    //throw new Error('Invalid MPEG Audio version');
}
if (this.frameCount > 0) {
    //return this.done(new Error('expected frame header but was not found'));
}

While this is definitely not a real fix, I am now able to read the song title and artist successfully which is all I currently need.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.