music-metadata 10.3.0 → 10.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +8 -8
  2. package/lib/ParserFactory.d.ts +22 -20
  3. package/lib/ParserFactory.js +74 -131
  4. package/lib/aiff/AiffLoader.d.ts +2 -0
  5. package/lib/aiff/AiffLoader.js +8 -0
  6. package/lib/apev2/APEv2Parser.js +2 -4
  7. package/lib/apev2/Apev2Loader.d.ts +2 -0
  8. package/lib/apev2/Apev2Loader.js +8 -0
  9. package/lib/asf/AsfLoader.d.ts +2 -0
  10. package/lib/asf/AsfLoader.js +8 -0
  11. package/lib/common/BasicParser.d.ts +5 -5
  12. package/lib/common/BasicParser.js +1 -7
  13. package/lib/common/ParserLoader.d.ts +1 -0
  14. package/lib/common/ParserLoader.js +1 -0
  15. package/lib/core.d.ts +2 -1
  16. package/lib/core.js +4 -3
  17. package/lib/dsdiff/DsdiffLoader.d.ts +2 -0
  18. package/lib/dsdiff/DsdiffLoader.js +8 -0
  19. package/lib/dsf/DsfLoader.d.ts +2 -0
  20. package/lib/dsf/DsfLoader.js +8 -0
  21. package/lib/flac/FlacLoader.d.ts +2 -0
  22. package/lib/flac/FlacLoader.js +8 -0
  23. package/lib/flac/FlacParser.d.ts +0 -11
  24. package/lib/flac/FlacParser.js +1 -11
  25. package/lib/id3v1/ID3v1Parser.d.ts +5 -1
  26. package/lib/id3v1/ID3v1Parser.js +8 -5
  27. package/lib/id3v2/AbstractID3Parser.js +2 -2
  28. package/lib/index.js +5 -4
  29. package/lib/matroska/MatroskaLoader.d.ts +2 -0
  30. package/lib/matroska/MatroskaLoader.js +8 -0
  31. package/lib/matroska/MatroskaParser.d.ts +0 -11
  32. package/lib/matroska/MatroskaParser.js +1 -12
  33. package/lib/mp4/Atom.js +5 -5
  34. package/lib/mp4/Mp4Loader.d.ts +2 -0
  35. package/lib/mp4/Mp4Loader.js +8 -0
  36. package/lib/mpeg/MpegLoader.d.ts +2 -0
  37. package/lib/mpeg/MpegLoader.js +8 -0
  38. package/lib/musepack/MusepackLoader.d.ts +2 -0
  39. package/lib/musepack/MusepackLoader.js +8 -0
  40. package/lib/musepack/{index.d.ts → MusepackParser.d.ts} +1 -2
  41. package/lib/musepack/{index.js → MusepackParser.js} +4 -6
  42. package/lib/ogg/OggLoader.d.ts +2 -0
  43. package/lib/ogg/OggLoader.js +8 -0
  44. package/lib/wav/WaveLoader.d.ts +2 -0
  45. package/lib/wav/WaveLoader.js +8 -0
  46. package/lib/wavpack/WavPackLoader.d.ts +2 -0
  47. package/lib/wavpack/WavPackLoader.js +8 -0
  48. package/package.json +3 -3
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  [![Node.js CI](https://github.com/Borewit/music-metadata/actions/workflows/nodejs-ci.yml/badge.svg?branch=master)](https://github.com/Borewit/music-metadata/actions?query=branch%3Amaster)
2
2
  [![Build status](https://ci.appveyor.com/api/projects/status/tgtqynlon8t99qq5/branch/master?svg=true)](https://ci.appveyor.com/project/Borewit/music-metadata/branch/master)
3
3
  [![NPM version](https://img.shields.io/npm/v/music-metadata.svg)](https://npmjs.org/package/music-metadata)
4
- [![npm downloads](http://img.shields.io/npm/dm/music-metadata.svg)](https://npmcharts.com/compare/music-metadata,jsmediatags,musicmetadata,node-id3,mp3-parser,id3-parser,wav-file-info?start=600)
4
+ [![npm downloads](http://img.shields.io/npm/dm/music-metadata.svg)](https://npmcharts.com/compare/music-metadata,jsmediatags,musicmetadata,node-id3,mp3-parser,id3-parser,wav-file-info?start=600&interval=30)
5
5
  [![Coverage Status](https://coveralls.io/repos/github/Borewit/music-metadata/badge.svg?branch=master)](https://coveralls.io/github/Borewit/music-metadata?branch=master)
6
6
  [![Codacy Badge](https://api.codacy.com/project/badge/Grade/57d731b05c9e41889a2a17cb4b0384d7)](https://app.codacy.com/app/Borewit/music-metadata?utm_source=github.com&utm_medium=referral&utm_content=Borewit/music-metadata&utm_campaign=Badge_Grade_Dashboard)
7
7
  [![CodeQL](https://github.com/Borewit/music-metadata/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/Borewit/music-metadata/actions/workflows/codeql-analysis.yml)
@@ -31,14 +31,14 @@ The distributed JavaScript codebase is compliant with the [ECMAScript 2020 (11th
31
31
  This module requires a [Node.js ≥ 16](https://nodejs.org/en/about/previous-releases) engine.
32
32
  It can also be used in a browser environment when bundled with a module bundler.
33
33
 
34
- ## Sponsor
35
- If you appreciate my work and want to support the development of open-source projects like [music-metadata](https://github.com/Borewit/music-metadata), [file-type](https://github.com/sindresorhus/file-type), and [listFix()](https://github.com/Borewit/listFix), consider becoming a sponsor or making a small contribution.
36
- Your support helps sustain ongoing development and improvements.
37
- [Become a sponsor to Borewit](https://github.com/sponsors/Borewit)
34
+ ## Support the Project
35
+ If you find this project useful and would like to support its development, consider sponsoring or contributing:
38
36
 
39
- or
37
+ - [Become a sponsor to Borewit](https://github.com/sponsors/Borewit)
40
38
 
41
- <a href="https://www.buymeacoffee.com/borewit" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy me A coffee" height="41" width="174"></a>
39
+ - Buy me a coffee:
40
+
41
+ <a href="https://www.buymeacoffee.com/borewit" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy me A coffee" height="41" width="174"></a>
42
42
 
43
43
  ## Features
44
44
 
@@ -783,7 +783,7 @@ const { loadMusicMetadata } = require('music-metadata');
783
783
  ```
784
784
 
785
785
  > [!NOTE]
786
- > The `loadMusicMetadata` function is experimental and is not currently covered by any TypeScript typings.
786
+ > The `loadMusicMetadata` function is experimental.
787
787
 
788
788
  ## Frequently Asked Questions
789
789
 
@@ -2,14 +2,18 @@ import { type MediaType } from 'media-typer';
2
2
  import { type INativeMetadataCollector } from './common/MetadataCollector.js';
3
3
  import type { IAudioMetadata, IOptions, ParserType } from './type.js';
4
4
  import type { ITokenizer } from 'strtok3';
5
- export interface ITokenParser {
5
+ export interface IParserLoader {
6
+ /**
7
+ * Returns a list of supported file extensions
8
+ */
9
+ extensions: string[];
10
+ parserType: ParserType;
6
11
  /**
7
- * Initialize parser with output (metadata), input (tokenizer) & parsing options (options).
8
- * @param metadata - Output
9
- * @param tokenizer - Input
10
- * @param options - Parsing options
12
+ * Lazy load the parser
11
13
  */
12
- init(metadata: INativeMetadataCollector, tokenizer: ITokenizer, options: IOptions): ITokenParser;
14
+ load(metadata: INativeMetadataCollector, tokenizer: ITokenizer, options: IOptions): Promise<ITokenParser>;
15
+ }
16
+ export interface ITokenParser {
13
17
  /**
14
18
  * Parse audio track.
15
19
  * Called after init(...).
@@ -23,18 +27,16 @@ interface IContentType extends MediaType {
23
27
  };
24
28
  }
25
29
  export declare function parseHttpContentType(contentType: string): IContentType;
26
- /**
27
- * Parse metadata from tokenizer
28
- * @param tokenizer - Tokenizer
29
- * @param opts - Options
30
- * @returns Native metadata
31
- */
32
- export declare function parseOnContentType(tokenizer: ITokenizer, opts?: IOptions): Promise<IAudioMetadata>;
33
- export declare function parse(tokenizer: ITokenizer, parserId?: ParserType, opts?: IOptions): Promise<IAudioMetadata>;
34
- /**
35
- * @param filePath - Path, filename or extension to audio file
36
- * @return Parser submodule name
37
- */
38
- export declare function getParserIdForExtension(filePath: string | undefined): ParserType | undefined;
39
- export declare function loadParser(moduleName: ParserType): Promise<ITokenParser>;
30
+ export declare class ParserFactory {
31
+ parsers: IParserLoader[];
32
+ constructor();
33
+ registerParser(parser: IParserLoader): void;
34
+ parse(tokenizer: ITokenizer, parserLoader: IParserLoader | undefined, opts?: IOptions): Promise<IAudioMetadata>;
35
+ /**
36
+ * @param filePath - Path, filename or extension to audio file
37
+ * @return Parser submodule name
38
+ */
39
+ findLoaderForExtension(filePath: string | undefined): IParserLoader | undefined;
40
+ findLoaderForType(moduleName: ParserType | undefined): IParserLoader | undefined;
41
+ }
40
42
  export {};
@@ -3,20 +3,20 @@ import ContentType from 'content-type';
3
3
  import { parse as mimeTypeParse } from 'media-typer';
4
4
  import initDebug from 'debug';
5
5
  import { MetadataCollector } from './common/MetadataCollector.js';
6
- import { AIFFParser } from './aiff/AiffParser.js';
7
- import { APEv2Parser } from './apev2/APEv2Parser.js';
8
- import { AsfParser } from './asf/AsfParser.js';
9
- import { FlacParser } from './flac/FlacParser.js';
10
- import { MP4Parser } from './mp4/MP4Parser.js';
11
- import { MpegParser } from './mpeg/MpegParser.js';
12
- import MusepackParser from './musepack/index.js';
13
- import { OggParser } from './ogg/OggParser.js';
14
- import { WaveParser } from './wav/WaveParser.js';
15
- import { WavPackParser } from './wavpack/WavPackParser.js';
16
- import { DsfParser } from './dsf/DsfParser.js';
17
- import { DsdiffParser } from './dsdiff/DsdiffParser.js';
18
- import { MatroskaParser } from './matroska/MatroskaParser.js';
19
- import { CouldNotDetermineFileTypeError, InternalParserError, UnsupportedFileTypeError } from './ParseError.js';
6
+ import { mpegParserLoader } from './mpeg/MpegLoader.js';
7
+ import { CouldNotDetermineFileTypeError, UnsupportedFileTypeError } from './ParseError.js';
8
+ import { apeParserLoader } from './apev2/Apev2Loader.js';
9
+ import { asfParserLoader } from './asf/AsfLoader.js';
10
+ import { dsdiffParserLoader } from './dsdiff/DsdiffLoader.js';
11
+ import { aiffParserLoader } from './aiff/AiffLoader.js';
12
+ import { dsfParserLoader } from './dsf/DsfLoader.js';
13
+ import { flacParserLoader } from './flac/FlacLoader.js';
14
+ import { matroskaParserLoader } from './matroska/MatroskaLoader.js';
15
+ import { mp4ParserLoader } from './mp4/Mp4Loader.js';
16
+ import { musepackParserLoader } from './musepack/MusepackLoader.js';
17
+ import { oggParserLoader } from './ogg/OggLoader.js';
18
+ import { wavpackParserLoader } from './wavpack/WavPackLoader.js';
19
+ import { riffParserLoader } from './wav/WaveLoader.js';
20
20
  const debug = initDebug('music-metadata:parser:factory');
21
21
  export function parseHttpContentType(contentType) {
22
22
  const type = ContentType.parse(contentType);
@@ -28,129 +28,72 @@ export function parseHttpContentType(contentType) {
28
28
  parameters: type.parameters
29
29
  };
30
30
  }
31
- /**
32
- * Parse metadata from tokenizer
33
- * @param tokenizer - Tokenizer
34
- * @param opts - Options
35
- * @returns Native metadata
36
- */
37
- export async function parseOnContentType(tokenizer, opts) {
38
- const { mimeType, path, url } = tokenizer.fileInfo;
39
- // Resolve parser based on MIME-type or file extension
40
- const parserId = getParserIdForMimeType(mimeType) || getParserIdForExtension(path) || getParserIdForExtension(url);
41
- if (!parserId) {
42
- debug(`No parser found for MIME-type / extension: ${mimeType}`);
31
+ export class ParserFactory {
32
+ constructor() {
33
+ this.parsers = [];
34
+ [
35
+ flacParserLoader,
36
+ mpegParserLoader,
37
+ apeParserLoader,
38
+ mp4ParserLoader,
39
+ matroskaParserLoader,
40
+ riffParserLoader,
41
+ oggParserLoader,
42
+ asfParserLoader,
43
+ aiffParserLoader,
44
+ wavpackParserLoader,
45
+ musepackParserLoader,
46
+ dsfParserLoader,
47
+ dsdiffParserLoader
48
+ ].forEach(parser => this.registerParser(parser));
43
49
  }
44
- return parse(tokenizer, parserId, opts);
45
- }
46
- export async function parse(tokenizer, parserId, opts) {
47
- if (!parserId) {
48
- if (tokenizer.fileInfo.path) {
49
- parserId = getParserIdForExtension(tokenizer.fileInfo.path);
50
- }
51
- if (!parserId) {
52
- // Parser could not be determined on MIME-type or extension
53
- debug('Guess parser on content...');
50
+ registerParser(parser) {
51
+ this.parsers.push(parser);
52
+ }
53
+ async parse(tokenizer, parserLoader, opts) {
54
+ if (!parserLoader) {
54
55
  const buf = new Uint8Array(4100);
55
- await tokenizer.peekBuffer(buf, { mayBeLess: true });
56
- const guessedType = await fileTypeFromBuffer(buf);
57
- if (!guessedType) {
58
- throw new CouldNotDetermineFileTypeError('Failed to determine audio format');
56
+ if (tokenizer.fileInfo.mimeType) {
57
+ parserLoader = this.findLoaderForType(getParserIdForMimeType(tokenizer.fileInfo.mimeType));
58
+ }
59
+ if (!parserLoader && tokenizer.fileInfo.path) {
60
+ parserLoader = this.findLoaderForExtension(tokenizer.fileInfo.path);
59
61
  }
60
- debug(`Guessed file type is mime=${guessedType.mime}, extension=${guessedType.ext}`);
61
- parserId = getParserIdForMimeType(guessedType.mime);
62
- if (!parserId) {
63
- throw new UnsupportedFileTypeError(`Guessed MIME-type not supported: ${guessedType.mime}`);
62
+ if (!parserLoader) {
63
+ // Parser could not be determined on MIME-type or extension
64
+ debug('Guess parser on content...');
65
+ await tokenizer.peekBuffer(buf, { mayBeLess: true });
66
+ const guessedType = await fileTypeFromBuffer(buf);
67
+ if (!guessedType || !guessedType.mime) {
68
+ throw new CouldNotDetermineFileTypeError('Failed to determine audio format');
69
+ }
70
+ debug(`Guessed file type is mime=${guessedType.mime}, extension=${guessedType.ext}`);
71
+ parserLoader = this.findLoaderForType(getParserIdForMimeType(guessedType.mime));
72
+ if (!parserLoader) {
73
+ throw new UnsupportedFileTypeError(`Guessed MIME-type not supported: ${guessedType.mime}`);
74
+ }
64
75
  }
65
76
  }
77
+ // Parser found, execute parser
78
+ debug(`Loading ${parserLoader.parserType} parser...`);
79
+ const metadata = new MetadataCollector(opts);
80
+ const parser = await parserLoader.load(metadata, tokenizer, opts ?? {});
81
+ debug(`Parser ${parserLoader.parserType} loaded`);
82
+ await parser.parse();
83
+ return metadata.toCommonMetadata();
66
84
  }
67
- // Parser found, execute parser
68
- const parser = await loadParser(parserId);
69
- const metadata = new MetadataCollector(opts);
70
- await parser.init(metadata, tokenizer, opts ?? {}).parse();
71
- return metadata.toCommonMetadata();
72
- }
73
- /**
74
- * @param filePath - Path, filename or extension to audio file
75
- * @return Parser submodule name
76
- */
77
- export function getParserIdForExtension(filePath) {
78
- if (!filePath)
79
- return;
80
- const extension = getExtension(filePath).toLocaleLowerCase() || filePath;
81
- switch (extension) {
82
- case '.mp2':
83
- case '.mp3':
84
- case '.m2a':
85
- case '.aac': // Assume it is ADTS-container
86
- return 'mpeg';
87
- case '.ape':
88
- return 'apev2';
89
- case '.mp4':
90
- case '.m4a':
91
- case '.m4b':
92
- case '.m4pa':
93
- case '.m4v':
94
- case '.m4r':
95
- case '.3gp':
96
- return 'mp4';
97
- case '.wma':
98
- case '.wmv':
99
- case '.asf':
100
- return 'asf';
101
- case '.flac':
102
- return 'flac';
103
- case '.ogg':
104
- case '.ogv':
105
- case '.oga':
106
- case '.ogm':
107
- case '.ogx':
108
- case '.opus': // recommended filename extension for Ogg Opus
109
- case '.spx': // recommended filename extension for Ogg Speex
110
- return 'ogg';
111
- case '.aif':
112
- case '.aiff':
113
- case '.aifc':
114
- return 'aiff';
115
- case '.wav':
116
- case '.bwf': // Broadcast Wave Format
117
- return 'riff';
118
- case '.wv':
119
- case '.wvp':
120
- return 'wavpack';
121
- case '.mpc':
122
- return 'musepack';
123
- case '.dsf':
124
- return 'dsf';
125
- case '.dff':
126
- return 'dsdiff';
127
- case '.mka':
128
- case '.mkv':
129
- case '.mk3d':
130
- case '.mks':
131
- case '.webm':
132
- return 'matroska';
85
+ /**
86
+ * @param filePath - Path, filename or extension to audio file
87
+ * @return Parser submodule name
88
+ */
89
+ findLoaderForExtension(filePath) {
90
+ if (!filePath)
91
+ return;
92
+ const extension = getExtension(filePath).toLocaleLowerCase() || filePath;
93
+ return this.parsers.find(parser => parser.extensions.indexOf(extension) !== -1);
133
94
  }
134
- }
135
- export async function loadParser(moduleName) {
136
- switch (moduleName) {
137
- case 'aiff': return new AIFFParser();
138
- case 'adts':
139
- case 'mpeg':
140
- return new MpegParser();
141
- case 'apev2': return new APEv2Parser();
142
- case 'asf': return new AsfParser();
143
- case 'dsf': return new DsfParser();
144
- case 'dsdiff': return new DsdiffParser();
145
- case 'flac': return new FlacParser();
146
- case 'mp4': return new MP4Parser();
147
- case 'musepack': return new MusepackParser();
148
- case 'ogg': return new OggParser();
149
- case 'riff': return new WaveParser();
150
- case 'wavpack': return new WavPackParser();
151
- case 'matroska': return new MatroskaParser();
152
- default:
153
- throw new InternalParserError(`Unknown parser type: ${moduleName}`);
95
+ findLoaderForType(moduleName) {
96
+ return moduleName ? this.parsers.find(parser => parser.parserType === moduleName) : undefined;
154
97
  }
155
98
  }
156
99
  function getExtension(fname) {
@@ -181,7 +124,7 @@ function getParserIdForMimeType(httpContentType) {
181
124
  return 'mpeg';
182
125
  case 'aac':
183
126
  case 'aacp':
184
- return 'adts';
127
+ return 'mpeg'; // adts
185
128
  case 'flac':
186
129
  return 'flac';
187
130
  case 'ape':
@@ -0,0 +1,2 @@
1
+ import type { IParserLoader } from '../ParserFactory.js';
2
+ export declare const aiffParserLoader: IParserLoader;
@@ -0,0 +1,8 @@
1
+ export const aiffParserLoader = {
2
+ parserType: 'aiff',
3
+ extensions: ['.aif', 'aiff', 'aifc'],
4
+ async load(metadata, tokenizer, options) {
5
+ return new (await import('./AiffParser.js')).AIFFParser(metadata, tokenizer, options);
6
+ }
7
+ };
8
+ //# sourceMappingURL=AiffLoader.js.map
@@ -17,8 +17,7 @@ export class APEv2Parser extends BasicParser {
17
17
  this.ape = {};
18
18
  }
19
19
  static tryParseApeHeader(metadata, tokenizer, options) {
20
- const apeParser = new APEv2Parser();
21
- apeParser.init(metadata, tokenizer, options);
20
+ const apeParser = new APEv2Parser(metadata, tokenizer, options);
22
21
  return apeParser.tryParseApeHeader();
23
22
  }
24
23
  /**
@@ -57,8 +56,7 @@ export class APEv2Parser extends BasicParser {
57
56
  if (footer.ID !== preamble)
58
57
  throw new ApeContentError('Unexpected APEv2 Footer ID preamble value');
59
58
  strtok3.fromBuffer(buffer);
60
- const apeParser = new APEv2Parser();
61
- apeParser.init(metadata, strtok3.fromBuffer(buffer), options);
59
+ const apeParser = new APEv2Parser(metadata, strtok3.fromBuffer(buffer), options);
62
60
  return apeParser.parseTags(footer);
63
61
  }
64
62
  /**
@@ -0,0 +1,2 @@
1
+ import type { IParserLoader } from '../ParserFactory.js';
2
+ export declare const apeParserLoader: IParserLoader;
@@ -0,0 +1,8 @@
1
+ export const apeParserLoader = {
2
+ parserType: 'apev2',
3
+ extensions: ['.ape'],
4
+ async load(metadata, tokenizer, options) {
5
+ return new (await import('./APEv2Parser.js')).APEv2Parser(metadata, tokenizer, options);
6
+ }
7
+ };
8
+ //# sourceMappingURL=Apev2Loader.js.map
@@ -0,0 +1,2 @@
1
+ import type { IParserLoader } from '../ParserFactory.js';
2
+ export declare const asfParserLoader: IParserLoader;
@@ -0,0 +1,8 @@
1
+ export const asfParserLoader = {
2
+ parserType: 'asf',
3
+ extensions: ['.asf'],
4
+ async load(metadata, tokenizer, options) {
5
+ return new (await import('./AsfParser.js')).AsfParser(metadata, tokenizer, options);
6
+ }
7
+ };
8
+ //# sourceMappingURL=AsfLoader.js.map
@@ -1,17 +1,17 @@
1
1
  import type { ITokenizer } from 'strtok3';
2
2
  import type { ITokenParser } from '../ParserFactory.js';
3
- import type { IOptions, IPrivateOptions } from '../type.js';
3
+ import type { IOptions } from '../type.js';
4
4
  import type { INativeMetadataCollector } from './MetadataCollector.js';
5
5
  export declare abstract class BasicParser implements ITokenParser {
6
- protected metadata: INativeMetadataCollector;
7
- protected tokenizer: ITokenizer;
8
- protected options: IPrivateOptions;
6
+ protected readonly metadata: INativeMetadataCollector;
7
+ protected readonly tokenizer: ITokenizer;
8
+ protected readonly options: IOptions;
9
9
  /**
10
10
  * Initialize parser with output (metadata), input (tokenizer) & parsing options (options).
11
11
  * @param {INativeMetadataCollector} metadata Output
12
12
  * @param {ITokenizer} tokenizer Input
13
13
  * @param {IOptions} options Parsing options
14
14
  */
15
- init(metadata: INativeMetadataCollector, tokenizer: ITokenizer, options: IOptions): ITokenParser;
15
+ constructor(metadata: INativeMetadataCollector, tokenizer: ITokenizer, options: IOptions);
16
16
  abstract parse(): Promise<void>;
17
17
  }
@@ -1,20 +1,14 @@
1
1
  export class BasicParser {
2
- constructor() {
3
- this.metadata = undefined;
4
- this.tokenizer = undefined;
5
- this.options = undefined;
6
- }
7
2
  /**
8
3
  * Initialize parser with output (metadata), input (tokenizer) & parsing options (options).
9
4
  * @param {INativeMetadataCollector} metadata Output
10
5
  * @param {ITokenizer} tokenizer Input
11
6
  * @param {IOptions} options Parsing options
12
7
  */
13
- init(metadata, tokenizer, options) {
8
+ constructor(metadata, tokenizer, options) {
14
9
  this.metadata = metadata;
15
10
  this.tokenizer = tokenizer;
16
11
  this.options = options;
17
- return this;
18
12
  }
19
13
  }
20
14
  //# sourceMappingURL=BasicParser.js.map
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/lib/core.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Primary entry point, Node.js specific entry point is index.ts
2
+ * Primary entry point, Node.js specific entry point is MusepackParser.ts
3
3
  */
4
4
  import { type AnyWebByteStream, type IFileInfo, type ITokenizer } from 'strtok3';
5
5
  import type { IAudioMetadata, INativeTagDict, IOptions, IPicture, IPrivateOptions, IRandomReader, ITag } from './type.js';
@@ -57,3 +57,4 @@ export declare function ratingToStars(rating: number | undefined): number;
57
57
  */
58
58
  export declare function selectCover(pictures?: IPicture[]): IPicture | null;
59
59
  export declare function scanAppendingHeaders(randomReader: IRandomReader, options?: IPrivateOptions): Promise<void>;
60
+ export declare function loadMusicMetadata(): Promise<typeof import('music-metadata')>;
package/lib/core.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Primary entry point, Node.js specific entry point is index.ts
2
+ * Primary entry point, Node.js specific entry point is MusepackParser.ts
3
3
  */
4
4
  import { fromWebStream, fromBuffer } from 'strtok3';
5
- import { parseOnContentType } from './ParserFactory.js';
5
+ import { ParserFactory } from './ParserFactory.js';
6
6
  import { RandomUint8ArrayReader } from './common/RandomUint8ArrayReader.js';
7
7
  import { APEv2Parser } from './apev2/APEv2Parser.js';
8
8
  import { hasID3v1Header } from './id3v1/ID3v1Parser.js';
@@ -53,7 +53,8 @@ export async function parseBuffer(uint8Array, fileInfo, options = {}) {
53
53
  * @returns Metadata
54
54
  */
55
55
  export function parseFromTokenizer(tokenizer, options) {
56
- return parseOnContentType(tokenizer, options);
56
+ const parserFactory = new ParserFactory();
57
+ return parserFactory.parse(tokenizer, undefined, options);
57
58
  }
58
59
  /**
59
60
  * Create a dictionary ordered by their tag id (key)
@@ -0,0 +1,2 @@
1
+ import type { IParserLoader } from '../ParserFactory.js';
2
+ export declare const dsdiffParserLoader: IParserLoader;
@@ -0,0 +1,8 @@
1
+ export const dsdiffParserLoader = {
2
+ parserType: 'dsdiff',
3
+ extensions: ['.dff'],
4
+ async load(metadata, tokenizer, options) {
5
+ return new (await import('./DsdiffParser.js')).DsdiffParser(metadata, tokenizer, options);
6
+ }
7
+ };
8
+ //# sourceMappingURL=DsdiffLoader.js.map
@@ -0,0 +1,2 @@
1
+ import type { IParserLoader } from '../ParserFactory.js';
2
+ export declare const dsfParserLoader: IParserLoader;
@@ -0,0 +1,8 @@
1
+ export const dsfParserLoader = {
2
+ parserType: 'dsf',
3
+ extensions: ['.dsf'],
4
+ async load(metadata, tokenizer, options) {
5
+ return new (await import('./DsfParser.js')).DsfParser(metadata, tokenizer, options);
6
+ }
7
+ };
8
+ //# sourceMappingURL=DsfLoader.js.map
@@ -0,0 +1,2 @@
1
+ import type { IParserLoader } from '../ParserFactory.js';
2
+ export declare const flacParserLoader: IParserLoader;
@@ -0,0 +1,8 @@
1
+ export const flacParserLoader = {
2
+ parserType: 'flac',
3
+ extensions: ['.flac'],
4
+ async load(metadata, tokenizer, options) {
5
+ return new (await import('./FlacParser.js')).FlacParser(metadata, tokenizer, options);
6
+ }
7
+ };
8
+ //# sourceMappingURL=FlacLoader.js.map
@@ -1,18 +1,7 @@
1
- import type { ITokenizer } from 'strtok3';
2
1
  import { AbstractID3Parser } from '../id3v2/AbstractID3Parser.js';
3
- import type { INativeMetadataCollector } from '../common/MetadataCollector.js';
4
- import type { IOptions } from '../type.js';
5
- import type { ITokenParser } from '../ParserFactory.js';
6
2
  export declare class FlacParser extends AbstractID3Parser {
7
3
  private vorbisParser;
8
4
  private padding;
9
- /**
10
- * Initialize parser with output (metadata), input (tokenizer) & parsing options (options).
11
- * @param {INativeMetadataCollector} metadata Output
12
- * @param {ITokenizer} tokenizer Input
13
- * @param {IOptions} options Parsing options
14
- */
15
- init(metadata: INativeMetadataCollector, tokenizer: ITokenizer, options: IOptions): ITokenParser;
16
5
  postId3v2Parse(): Promise<void>;
17
6
  private parseDataBlock;
18
7
  /**
@@ -27,19 +27,9 @@ var BlockType;
27
27
  export class FlacParser extends AbstractID3Parser {
28
28
  constructor() {
29
29
  super(...arguments);
30
+ this.vorbisParser = new VorbisParser(this.metadata, this.options);
30
31
  this.padding = 0;
31
32
  }
32
- /**
33
- * Initialize parser with output (metadata), input (tokenizer) & parsing options (options).
34
- * @param {INativeMetadataCollector} metadata Output
35
- * @param {ITokenizer} tokenizer Input
36
- * @param {IOptions} options Parsing options
37
- */
38
- init(metadata, tokenizer, options) {
39
- super.init(metadata, tokenizer, options);
40
- this.vorbisParser = new VorbisParser(metadata, options);
41
- return this;
42
- }
43
33
  async postId3v2Parse() {
44
34
  const fourCC = await this.tokenizer.readToken(FourCcToken);
45
35
  if (fourCC.toString() !== 'fLaC') {
@@ -1,11 +1,15 @@
1
+ import type { ITokenizer } from 'strtok3';
1
2
  import { BasicParser } from '../common/BasicParser.js';
2
- import type { IRandomReader } from '../type.js';
3
+ import type { IPrivateOptions, IRandomReader } from '../type.js';
4
+ import type { INativeMetadataCollector } from '../common/MetadataCollector.js';
3
5
  /**
4
6
  * ID3v1 Genre mappings
5
7
  * Ref: https://de.wikipedia.org/wiki/Liste_der_ID3v1-Genres
6
8
  */
7
9
  export declare const Genres: string[];
8
10
  export declare class ID3v1Parser extends BasicParser {
11
+ private apeHeader;
12
+ constructor(metadata: INativeMetadataCollector, tokenizer: ITokenizer, options: IPrivateOptions);
9
13
  private static getGenre;
10
14
  parse(): Promise<void>;
11
15
  private addTag;
@@ -79,6 +79,10 @@ class Id3v1StringType {
79
79
  }
80
80
  }
81
81
  export class ID3v1Parser extends BasicParser {
82
+ constructor(metadata, tokenizer, options) {
83
+ super(metadata, tokenizer, options);
84
+ this.apeHeader = options.apeHeader;
85
+ }
82
86
  static getGenre(genreIndex) {
83
87
  if (genreIndex < Genres.length) {
84
88
  return Genres[genreIndex];
@@ -90,11 +94,10 @@ export class ID3v1Parser extends BasicParser {
90
94
  debug('Skip checking for ID3v1 because the file-size is unknown');
91
95
  return;
92
96
  }
93
- if (this.options.apeHeader) {
94
- this.tokenizer.ignore(this.options.apeHeader.offset - this.tokenizer.position);
95
- const apeParser = new APEv2Parser();
96
- apeParser.init(this.metadata, this.tokenizer, this.options);
97
- await apeParser.parseTags(this.options.apeHeader.footer);
97
+ if (this.apeHeader) {
98
+ this.tokenizer.ignore(this.apeHeader.offset - this.tokenizer.position);
99
+ const apeParser = new APEv2Parser(this.metadata, this.tokenizer, this.options);
100
+ await apeParser.parseTags(this.apeHeader.footer);
98
101
  }
99
102
  const offset = this.tokenizer.fileInfo.size - Iid3v1Token.len;
100
103
  if (this.tokenizer.position > offset) {
@@ -40,8 +40,8 @@ export class AbstractID3Parser extends BasicParser {
40
40
  this.finalize();
41
41
  }
42
42
  else {
43
- const id3v1parser = new ID3v1Parser();
44
- await id3v1parser.init(this.metadata, this.tokenizer, this.options).parse();
43
+ const id3v1parser = new ID3v1Parser(this.metadata, this.tokenizer, this.options);
44
+ await id3v1parser.parse();
45
45
  this.finalize();
46
46
  }
47
47
  }
package/lib/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { fromFile, fromStream } from 'strtok3';
5
5
  import initDebug from 'debug';
6
6
  import { parseFromTokenizer, scanAppendingHeaders } from './core.js';
7
- import { getParserIdForExtension, parse } from './ParserFactory.js';
7
+ import { ParserFactory } from './ParserFactory.js';
8
8
  import { RandomFileReader } from './common/RandomFileReader.js';
9
9
  export * from './core.js';
10
10
  const debug = initDebug('music-metadata:parser');
@@ -35,11 +35,12 @@ export async function parseFile(filePath, options = {}) {
35
35
  finally {
36
36
  await fileReader.close();
37
37
  }
38
+ const parserFactory = new ParserFactory();
38
39
  try {
39
- const parserName = getParserIdForExtension(filePath);
40
- if (!parserName)
40
+ const parserLoader = parserFactory.findLoaderForExtension(filePath);
41
+ if (!parserLoader)
41
42
  debug(' Parser could not be determined by file extension');
42
- return await parse(fileTokenizer, parserName, options);
43
+ return await parserFactory.parse(fileTokenizer, parserLoader, options);
43
44
  }
44
45
  finally {
45
46
  await fileTokenizer.close();
@@ -0,0 +1,2 @@
1
+ import type { IParserLoader } from '../ParserFactory.js';
2
+ export declare const matroskaParserLoader: IParserLoader;
@@ -0,0 +1,8 @@
1
+ export const matroskaParserLoader = {
2
+ parserType: 'matroska',
3
+ extensions: ['.mka', '.mkv', '.mk3d', '.mks', 'webm'],
4
+ async load(metadata, tokenizer, options) {
5
+ return new (await import('./MatroskaParser.js')).MatroskaParser(metadata, tokenizer, options);
6
+ }
7
+ };
8
+ //# sourceMappingURL=MatroskaLoader.js.map
@@ -1,8 +1,4 @@
1
- import type { ITokenizer } from 'strtok3';
2
- import type { INativeMetadataCollector } from '../common/MetadataCollector.js';
3
1
  import { BasicParser } from '../common/BasicParser.js';
4
- import type { IOptions } from '../type.js';
5
- import type { ITokenParser } from '../ParserFactory.js';
6
2
  /**
7
3
  * Extensible Binary Meta Language (EBML) parser
8
4
  * https://en.wikipedia.org/wiki/Extensible_Binary_Meta_Language
@@ -18,13 +14,6 @@ export declare class MatroskaParser extends BasicParser {
18
14
  * Significant performance impact
19
15
  */
20
16
  private flagUseIndexToSkipClusters;
21
- /**
22
- * Initialize parser with output (metadata), input (tokenizer) & parsing options (options).
23
- * @param {INativeMetadataCollector} metadata Output
24
- * @param {ITokenizer} tokenizer Input
25
- * @param {IOptions} options Parsing options
26
- */
27
- init(metadata: INativeMetadataCollector, tokenizer: ITokenizer, options: IOptions): ITokenParser;
28
17
  parse(): Promise<void>;
29
18
  private addTag;
30
19
  }
@@ -19,18 +19,7 @@ export class MatroskaParser extends BasicParser {
19
19
  * Use index to skip multiple segment/cluster elements at once.
20
20
  * Significant performance impact
21
21
  */
22
- this.flagUseIndexToSkipClusters = false;
23
- }
24
- /**
25
- * Initialize parser with output (metadata), input (tokenizer) & parsing options (options).
26
- * @param {INativeMetadataCollector} metadata Output
27
- * @param {ITokenizer} tokenizer Input
28
- * @param {IOptions} options Parsing options
29
- */
30
- init(metadata, tokenizer, options) {
31
- super.init(metadata, tokenizer, options);
32
- this.flagUseIndexToSkipClusters = options.mkvUseIndex ?? false;
33
- return this;
22
+ this.flagUseIndexToSkipClusters = this.options.mkvUseIndex ?? false;
34
23
  }
35
24
  async parse() {
36
25
  const containerSize = this.tokenizer.fileInfo.size ?? Number.MAX_SAFE_INTEGER;
package/lib/mp4/Atom.js CHANGED
@@ -6,13 +6,13 @@ export class Atom {
6
6
  static async readAtom(tokenizer, dataHandler, parent, remaining) {
7
7
  // Parse atom header
8
8
  const offset = tokenizer.position;
9
- // debug(`Reading next token on offset=${offset}...`); // buf.toString('ascii')
9
+ debug(`Reading next token on offset=${offset}...`); // buf.toString('ascii')
10
10
  const header = await tokenizer.readToken(AtomToken.Header);
11
- const extended = header.length === BigInt(1);
11
+ const extended = header.length === 1n;
12
12
  if (extended) {
13
13
  header.length = await tokenizer.readToken(AtomToken.ExtendedSize);
14
14
  }
15
- const atomBean = new Atom(header, header.length === BigInt(1), parent);
15
+ const atomBean = new Atom(header, extended, parent);
16
16
  const payloadLength = atomBean.getPayloadLength(remaining);
17
17
  debug(`parse atom name=${atomBean.atomPath}, extended=${atomBean.extended}, offset=${offset}, len=${atomBean.header.length}`); // buf.toString('ascii')
18
18
  await atomBean.readData(tokenizer, dataHandler, payloadLength);
@@ -29,13 +29,13 @@ export class Atom {
29
29
  return this.extended ? 16 : 8;
30
30
  }
31
31
  getPayloadLength(remaining) {
32
- return (this.header.length === BigInt(0) ? remaining : Number(this.header.length)) - this.getHeaderLength();
32
+ return (this.header.length === 0n ? remaining : Number(this.header.length)) - this.getHeaderLength();
33
33
  }
34
34
  async readAtoms(tokenizer, dataHandler, size) {
35
35
  while (size > 0) {
36
36
  const atomBean = await Atom.readAtom(tokenizer, dataHandler, this, size);
37
37
  this.children.push(atomBean);
38
- size -= atomBean.header.length === BigInt(0) ? size : Number(atomBean.header.length);
38
+ size -= atomBean.header.length === 0n ? size : Number(atomBean.header.length);
39
39
  }
40
40
  }
41
41
  async readData(tokenizer, dataHandler, remaining) {
@@ -0,0 +1,2 @@
1
+ import type { IParserLoader } from '../ParserFactory.js';
2
+ export declare const mp4ParserLoader: IParserLoader;
@@ -0,0 +1,8 @@
1
+ export const mp4ParserLoader = {
2
+ parserType: 'mp4',
3
+ extensions: ['.mp4', '.m4a', '.m4b', '.m4pa', 'm4v', 'm4r', '3gp'],
4
+ async load(metadata, tokenizer, options) {
5
+ return new (await import('./MP4Parser.js')).MP4Parser(metadata, tokenizer, options);
6
+ }
7
+ };
8
+ //# sourceMappingURL=Mp4Loader.js.map
@@ -0,0 +1,2 @@
1
+ import type { IParserLoader } from '../ParserFactory.js';
2
+ export declare const mpegParserLoader: IParserLoader;
@@ -0,0 +1,8 @@
1
+ export const mpegParserLoader = {
2
+ parserType: 'mpeg',
3
+ extensions: ['.mp2', '.mp3', '.m2a', '.aac', 'aacp'],
4
+ async load(metadata, tokenizer, options) {
5
+ return new (await import('./MpegParser.js')).MpegParser(metadata, tokenizer, options);
6
+ }
7
+ };
8
+ //# sourceMappingURL=MpegLoader.js.map
@@ -0,0 +1,2 @@
1
+ import type { IParserLoader } from '../ParserFactory.js';
2
+ export declare const musepackParserLoader: IParserLoader;
@@ -0,0 +1,8 @@
1
+ export const musepackParserLoader = {
2
+ parserType: 'musepack',
3
+ extensions: ['.mpc'],
4
+ async load(metadata, tokenizer, options) {
5
+ return new (await import('./MusepackParser.js')).MusepackParser(metadata, tokenizer, options);
6
+ }
7
+ };
8
+ //# sourceMappingURL=MusepackLoader.js.map
@@ -1,5 +1,4 @@
1
1
  import { AbstractID3Parser } from '../id3v2/AbstractID3Parser.js';
2
- declare class MusepackParser extends AbstractID3Parser {
2
+ export declare class MusepackParser extends AbstractID3Parser {
3
3
  postId3v2Parse(): Promise<void>;
4
4
  }
5
- export default MusepackParser;
@@ -5,28 +5,26 @@ import { MpcSv8Parser } from './sv8/MpcSv8Parser.js';
5
5
  import { MpcSv7Parser } from './sv7/MpcSv7Parser.js';
6
6
  import { MusepackContentError } from './MusepackConentError.js';
7
7
  const debug = initDebug('music-metadata:parser:musepack');
8
- class MusepackParser extends AbstractID3Parser {
8
+ export class MusepackParser extends AbstractID3Parser {
9
9
  async postId3v2Parse() {
10
10
  const signature = await this.tokenizer.peekToken(new Token.StringType(3, 'latin1'));
11
11
  let mpcParser;
12
12
  switch (signature) {
13
13
  case 'MP+': {
14
14
  debug('Stream-version 7');
15
- mpcParser = new MpcSv7Parser();
15
+ mpcParser = new MpcSv7Parser(this.metadata, this.tokenizer, this.options);
16
16
  break;
17
17
  }
18
18
  case 'MPC': {
19
19
  debug('Stream-version 8');
20
- mpcParser = new MpcSv8Parser();
20
+ mpcParser = new MpcSv8Parser(this.metadata, this.tokenizer, this.options);
21
21
  break;
22
22
  }
23
23
  default: {
24
24
  throw new MusepackContentError('Invalid signature prefix');
25
25
  }
26
26
  }
27
- mpcParser.init(this.metadata, this.tokenizer, this.options);
28
27
  return mpcParser.parse();
29
28
  }
30
29
  }
31
- export default MusepackParser;
32
- //# sourceMappingURL=index.js.map
30
+ //# sourceMappingURL=MusepackParser.js.map
@@ -0,0 +1,2 @@
1
+ import type { IParserLoader } from '../ParserFactory.js';
2
+ export declare const oggParserLoader: IParserLoader;
@@ -0,0 +1,8 @@
1
+ export const oggParserLoader = {
2
+ parserType: 'ogg',
3
+ extensions: ['.ogg', '.ogv', '.oga', '.ogm', '.ogx', '.opus', '.spx'],
4
+ async load(metadata, tokenizer, options) {
5
+ return new (await import('./OggParser.js')).OggParser(metadata, tokenizer, options);
6
+ }
7
+ };
8
+ //# sourceMappingURL=OggLoader.js.map
@@ -0,0 +1,2 @@
1
+ import type { IParserLoader } from '../ParserFactory.js';
2
+ export declare const riffParserLoader: IParserLoader;
@@ -0,0 +1,8 @@
1
+ export const riffParserLoader = {
2
+ parserType: 'riff',
3
+ extensions: ['.wav', 'wave', '.bwf'],
4
+ async load(metadata, tokenizer, options) {
5
+ return new (await import('./WaveParser.js')).WaveParser(metadata, tokenizer, options);
6
+ }
7
+ };
8
+ //# sourceMappingURL=WaveLoader.js.map
@@ -0,0 +1,2 @@
1
+ import type { IParserLoader } from '../ParserFactory.js';
2
+ export declare const wavpackParserLoader: IParserLoader;
@@ -0,0 +1,8 @@
1
+ export const wavpackParserLoader = {
2
+ parserType: 'wavpack',
3
+ extensions: ['.wv', '.wvp'],
4
+ async load(metadata, tokenizer, options) {
5
+ return new (await import('./WavPackParser.js')).WavPackParser(metadata, tokenizer, options);
6
+ }
7
+ };
8
+ //# sourceMappingURL=WavPackLoader.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "music-metadata",
3
3
  "description": "Music metadata parser for Node.js, supporting virtual any audio and tag format.",
4
- "version": "10.3.0",
4
+ "version": "10.4.0",
5
5
  "author": {
6
6
  "name": "Borewit",
7
7
  "url": "https://github.com/Borewit"
@@ -108,7 +108,7 @@
108
108
  "debug": "^4.3.4",
109
109
  "file-type": "^19.4.1",
110
110
  "media-typer": "^1.1.0",
111
- "strtok3": "^8.1.0",
111
+ "strtok3": "^9.0.0",
112
112
  "token-types": "^6.0.0",
113
113
  "uint8array-extras": "^1.4.0"
114
114
  },
@@ -120,7 +120,7 @@
120
120
  "@types/debug": "^4.1.12",
121
121
  "@types/media-typer": "^1.1.3",
122
122
  "@types/mocha": "^10.0.7",
123
- "@types/node": "^22.5.1",
123
+ "@types/node": "^22.5.4",
124
124
  "c8": "^10.1.2",
125
125
  "chai": "^5.1.1",
126
126
  "chai-as-promised": "^8.0.0",