music-metadata 10.1.0 → 10.3.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.
- package/README.md +465 -141
- package/lib/ParseError.d.ts +87 -0
- package/lib/ParseError.js +39 -0
- package/lib/ParserFactory.d.ts +1 -1
- package/lib/ParserFactory.js +9 -8
- package/lib/aiff/AiffParser.js +6 -7
- package/lib/aiff/AiffToken.d.ts +15 -0
- package/lib/aiff/AiffToken.js +5 -2
- package/lib/apev2/APEv2Parser.d.ts +15 -0
- package/lib/apev2/APEv2Parser.js +6 -3
- package/lib/asf/AsfObject.d.ts +15 -0
- package/lib/asf/AsfObject.js +4 -1
- package/lib/asf/AsfParser.js +2 -1
- package/lib/common/CombinedTagMapper.js +2 -1
- package/lib/common/FourCC.js +3 -2
- package/lib/common/MetadataCollector.js +2 -4
- package/lib/common/Util.js +3 -2
- package/lib/core.d.ts +2 -1
- package/lib/core.js +1 -1
- package/lib/default.cjs +5 -0
- package/lib/dsdiff/DsdiffParser.d.ts +15 -0
- package/lib/dsdiff/DsdiffParser.js +6 -3
- package/lib/dsf/DsfParser.d.ts +15 -0
- package/lib/dsf/DsfParser.js +4 -1
- package/lib/ebml/EbmlIterator.d.ts +67 -0
- package/lib/ebml/EbmlIterator.js +223 -0
- package/lib/ebml/types.d.ts +36 -0
- package/lib/ebml/types.js +10 -0
- package/lib/flac/FlacParser.js +5 -2
- package/lib/id3v2/FrameParser.d.ts +14 -0
- package/lib/id3v2/FrameParser.js +7 -1
- package/lib/id3v2/ID3v2Parser.js +10 -8
- package/lib/matroska/MatroskaDtd.d.ts +2 -2
- package/lib/matroska/MatroskaDtd.js +246 -239
- package/lib/matroska/MatroskaParser.d.ts +8 -22
- package/lib/matroska/MatroskaParser.js +119 -204
- package/lib/matroska/types.d.ts +8 -44
- package/lib/matroska/types.js +0 -9
- package/lib/mp4/AtomToken.d.ts +14 -0
- package/lib/mp4/AtomToken.js +6 -3
- package/lib/mp4/MP4Parser.js +4 -3
- package/lib/mpeg/MpegParser.d.ts +15 -0
- package/lib/mpeg/MpegParser.js +7 -4
- package/lib/musepack/MusepackConentError.d.ts +15 -0
- package/lib/musepack/MusepackConentError.js +4 -0
- package/lib/musepack/index.js +4 -3
- package/lib/musepack/sv7/MpcSv7Parser.js +2 -1
- package/lib/musepack/sv8/MpcSv8Parser.js +3 -2
- package/lib/node.cjs +5 -0
- package/lib/ogg/OggParser.d.ts +15 -0
- package/lib/ogg/OggParser.js +5 -2
- package/lib/ogg/opus/Opus.d.ts +15 -0
- package/lib/ogg/opus/Opus.js +4 -1
- package/lib/ogg/opus/OpusParser.js +2 -1
- package/lib/ogg/vorbis/VorbisParser.d.ts +15 -0
- package/lib/ogg/vorbis/VorbisParser.js +6 -3
- package/lib/type.d.ts +9 -0
- package/lib/wav/WaveChunk.d.ts +15 -0
- package/lib/wav/WaveChunk.js +5 -2
- package/lib/wav/WaveParser.js +3 -2
- package/lib/wavpack/WavPackParser.d.ts +15 -0
- package/lib/wavpack/WavPackParser.js +6 -3
- package/package.json +20 -11
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export type UnionOfParseErrors = CouldNotDetermineFileTypeError | UnsupportedFileTypeError | UnexpectedFileContentError | FieldDecodingError | InternalParserError;
|
|
2
|
+
export declare const makeParseError: <Name extends string>(name: Name) => {
|
|
3
|
+
new (message: string): {
|
|
4
|
+
name: Name;
|
|
5
|
+
message: string;
|
|
6
|
+
stack?: string;
|
|
7
|
+
};
|
|
8
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
9
|
+
prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
|
|
10
|
+
stackTraceLimit: number;
|
|
11
|
+
};
|
|
12
|
+
declare const CouldNotDetermineFileTypeError_base: {
|
|
13
|
+
new (message: string): {
|
|
14
|
+
name: "CouldNotDetermineFileTypeError";
|
|
15
|
+
message: string;
|
|
16
|
+
stack?: string;
|
|
17
|
+
};
|
|
18
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
19
|
+
prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
|
|
20
|
+
stackTraceLimit: number;
|
|
21
|
+
};
|
|
22
|
+
export declare class CouldNotDetermineFileTypeError extends CouldNotDetermineFileTypeError_base {
|
|
23
|
+
}
|
|
24
|
+
declare const UnsupportedFileTypeError_base: {
|
|
25
|
+
new (message: string): {
|
|
26
|
+
name: "UnsupportedFileTypeError";
|
|
27
|
+
message: string;
|
|
28
|
+
stack?: string;
|
|
29
|
+
};
|
|
30
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
31
|
+
prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
|
|
32
|
+
stackTraceLimit: number;
|
|
33
|
+
};
|
|
34
|
+
export declare class UnsupportedFileTypeError extends UnsupportedFileTypeError_base {
|
|
35
|
+
}
|
|
36
|
+
declare const UnexpectedFileContentError_base: {
|
|
37
|
+
new (message: string): {
|
|
38
|
+
name: "UnexpectedFileContentError";
|
|
39
|
+
message: string;
|
|
40
|
+
stack?: string;
|
|
41
|
+
};
|
|
42
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
43
|
+
prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
|
|
44
|
+
stackTraceLimit: number;
|
|
45
|
+
};
|
|
46
|
+
declare class UnexpectedFileContentError extends UnexpectedFileContentError_base {
|
|
47
|
+
readonly fileType: string;
|
|
48
|
+
constructor(fileType: string, message: string);
|
|
49
|
+
toString(): string;
|
|
50
|
+
}
|
|
51
|
+
declare const FieldDecodingError_base: {
|
|
52
|
+
new (message: string): {
|
|
53
|
+
name: "FieldDecodingError";
|
|
54
|
+
message: string;
|
|
55
|
+
stack?: string;
|
|
56
|
+
};
|
|
57
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
58
|
+
prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
|
|
59
|
+
stackTraceLimit: number;
|
|
60
|
+
};
|
|
61
|
+
export declare class FieldDecodingError extends FieldDecodingError_base {
|
|
62
|
+
}
|
|
63
|
+
declare const InternalParserError_base: {
|
|
64
|
+
new (message: string): {
|
|
65
|
+
name: "InternalParserError";
|
|
66
|
+
message: string;
|
|
67
|
+
stack?: string;
|
|
68
|
+
};
|
|
69
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
70
|
+
prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
|
|
71
|
+
stackTraceLimit: number;
|
|
72
|
+
};
|
|
73
|
+
export declare class InternalParserError extends InternalParserError_base {
|
|
74
|
+
}
|
|
75
|
+
export declare const makeUnexpectedFileContentError: <FileType extends string>(fileType: FileType) => {
|
|
76
|
+
new (message: string): {
|
|
77
|
+
readonly fileType: string;
|
|
78
|
+
toString(): string;
|
|
79
|
+
name: "UnexpectedFileContentError";
|
|
80
|
+
message: string;
|
|
81
|
+
stack?: string;
|
|
82
|
+
};
|
|
83
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
84
|
+
prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
|
|
85
|
+
stackTraceLimit: number;
|
|
86
|
+
};
|
|
87
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const makeParseError = (name) => {
|
|
2
|
+
return class ParseError extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = name;
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
// Concrete error class representing a file type determination failure.
|
|
10
|
+
export class CouldNotDetermineFileTypeError extends makeParseError('CouldNotDetermineFileTypeError') {
|
|
11
|
+
}
|
|
12
|
+
// Concrete error class representing an unsupported file type.
|
|
13
|
+
export class UnsupportedFileTypeError extends makeParseError('UnsupportedFileTypeError') {
|
|
14
|
+
}
|
|
15
|
+
// Concrete error class representing unexpected file content.
|
|
16
|
+
class UnexpectedFileContentError extends makeParseError('UnexpectedFileContentError') {
|
|
17
|
+
constructor(fileType, message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.fileType = fileType;
|
|
20
|
+
}
|
|
21
|
+
// Override toString to include file type information.
|
|
22
|
+
toString() {
|
|
23
|
+
return `${this.name} (FileType: ${this.fileType}): ${this.message}`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// Concrete error class representing a field decoding error.
|
|
27
|
+
export class FieldDecodingError extends makeParseError('FieldDecodingError') {
|
|
28
|
+
}
|
|
29
|
+
export class InternalParserError extends makeParseError('InternalParserError') {
|
|
30
|
+
}
|
|
31
|
+
// Factory function to create a specific type of UnexpectedFileContentError.
|
|
32
|
+
export const makeUnexpectedFileContentError = (fileType) => {
|
|
33
|
+
return class extends UnexpectedFileContentError {
|
|
34
|
+
constructor(message) {
|
|
35
|
+
super(fileType, message);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=ParseError.js.map
|
package/lib/ParserFactory.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type MediaType } from 'media-typer';
|
|
2
2
|
import { type INativeMetadataCollector } from './common/MetadataCollector.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { IAudioMetadata, IOptions, ParserType } from './type.js';
|
|
4
4
|
import type { ITokenizer } from 'strtok3';
|
|
5
5
|
export interface ITokenParser {
|
|
6
6
|
/**
|
package/lib/ParserFactory.js
CHANGED
|
@@ -16,6 +16,7 @@ import { WavPackParser } from './wavpack/WavPackParser.js';
|
|
|
16
16
|
import { DsfParser } from './dsf/DsfParser.js';
|
|
17
17
|
import { DsdiffParser } from './dsdiff/DsdiffParser.js';
|
|
18
18
|
import { MatroskaParser } from './matroska/MatroskaParser.js';
|
|
19
|
+
import { CouldNotDetermineFileTypeError, InternalParserError, UnsupportedFileTypeError } from './ParseError.js';
|
|
19
20
|
const debug = initDebug('music-metadata:parser:factory');
|
|
20
21
|
export function parseHttpContentType(contentType) {
|
|
21
22
|
const type = ContentType.parse(contentType);
|
|
@@ -44,29 +45,29 @@ export async function parseOnContentType(tokenizer, opts) {
|
|
|
44
45
|
}
|
|
45
46
|
export async function parse(tokenizer, parserId, opts) {
|
|
46
47
|
if (!parserId) {
|
|
47
|
-
// Parser could not be determined on MIME-type or extension
|
|
48
|
-
debug('Guess parser on content...');
|
|
49
|
-
const buf = new Uint8Array(4100);
|
|
50
|
-
await tokenizer.peekBuffer(buf, { mayBeLess: true });
|
|
51
48
|
if (tokenizer.fileInfo.path) {
|
|
52
49
|
parserId = getParserIdForExtension(tokenizer.fileInfo.path);
|
|
53
50
|
}
|
|
54
51
|
if (!parserId) {
|
|
52
|
+
// Parser could not be determined on MIME-type or extension
|
|
53
|
+
debug('Guess parser on content...');
|
|
54
|
+
const buf = new Uint8Array(4100);
|
|
55
|
+
await tokenizer.peekBuffer(buf, { mayBeLess: true });
|
|
55
56
|
const guessedType = await fileTypeFromBuffer(buf);
|
|
56
57
|
if (!guessedType) {
|
|
57
|
-
throw new
|
|
58
|
+
throw new CouldNotDetermineFileTypeError('Failed to determine audio format');
|
|
58
59
|
}
|
|
59
60
|
debug(`Guessed file type is mime=${guessedType.mime}, extension=${guessedType.ext}`);
|
|
60
61
|
parserId = getParserIdForMimeType(guessedType.mime);
|
|
61
62
|
if (!parserId) {
|
|
62
|
-
throw new
|
|
63
|
+
throw new UnsupportedFileTypeError(`Guessed MIME-type not supported: ${guessedType.mime}`);
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
// Parser found, execute parser
|
|
67
68
|
const parser = await loadParser(parserId);
|
|
68
69
|
const metadata = new MetadataCollector(opts);
|
|
69
|
-
await parser.init(metadata, tokenizer, opts
|
|
70
|
+
await parser.init(metadata, tokenizer, opts ?? {}).parse();
|
|
70
71
|
return metadata.toCommonMetadata();
|
|
71
72
|
}
|
|
72
73
|
/**
|
|
@@ -149,7 +150,7 @@ export async function loadParser(moduleName) {
|
|
|
149
150
|
case 'wavpack': return new WavPackParser();
|
|
150
151
|
case 'matroska': return new MatroskaParser();
|
|
151
152
|
default:
|
|
152
|
-
throw new
|
|
153
|
+
throw new InternalParserError(`Unknown parser type: ${moduleName}`);
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
156
|
function getExtension(fname) {
|
package/lib/aiff/AiffParser.js
CHANGED
|
@@ -5,8 +5,8 @@ import { ID3v2Parser } from '../id3v2/ID3v2Parser.js';
|
|
|
5
5
|
import { FourCcToken } from '../common/FourCC.js';
|
|
6
6
|
import { BasicParser } from '../common/BasicParser.js';
|
|
7
7
|
import * as AiffToken from './AiffToken.js';
|
|
8
|
+
import { AiffContentError, compressionTypes } from './AiffToken.js';
|
|
8
9
|
import * as iff from '../iff/index.js';
|
|
9
|
-
import { compressionTypes } from './AiffToken.js';
|
|
10
10
|
const debug = initDebug('music-metadata:parser:aiff');
|
|
11
11
|
/**
|
|
12
12
|
* AIFF - Audio Interchange File Format
|
|
@@ -23,7 +23,7 @@ export class AIFFParser extends BasicParser {
|
|
|
23
23
|
async parse() {
|
|
24
24
|
const header = await this.tokenizer.readToken(iff.Header);
|
|
25
25
|
if (header.chunkID !== 'FORM')
|
|
26
|
-
throw new
|
|
26
|
+
throw new AiffContentError('Invalid Chunk-ID, expected \'FORM\''); // Not AIFF format
|
|
27
27
|
const type = await this.tokenizer.readToken(FourCcToken);
|
|
28
28
|
switch (type) {
|
|
29
29
|
case 'AIFF':
|
|
@@ -35,7 +35,7 @@ export class AIFFParser extends BasicParser {
|
|
|
35
35
|
this.isCompressed = true;
|
|
36
36
|
break;
|
|
37
37
|
default:
|
|
38
|
-
throw new
|
|
38
|
+
throw new AiffContentError(`Unsupported AIFF type: ${type}`);
|
|
39
39
|
}
|
|
40
40
|
this.metadata.setFormat('lossless', !this.isCompressed);
|
|
41
41
|
try {
|
|
@@ -57,11 +57,10 @@ export class AIFFParser extends BasicParser {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
async readData(header) {
|
|
60
|
-
var _a;
|
|
61
60
|
switch (header.chunkID) {
|
|
62
61
|
case 'COMM': { // The Common Chunk
|
|
63
62
|
if (this.isCompressed === null) {
|
|
64
|
-
throw new
|
|
63
|
+
throw new AiffContentError('Failed to parse AIFF.COMM chunk when compression type is unknown');
|
|
65
64
|
}
|
|
66
65
|
const common = await this.tokenizer.readToken(new AiffToken.Common(header, this.isCompressed));
|
|
67
66
|
this.metadata.setFormat('bitsPerSample', common.sampleSize);
|
|
@@ -70,7 +69,7 @@ export class AIFFParser extends BasicParser {
|
|
|
70
69
|
this.metadata.setFormat('numberOfSamples', common.numSampleFrames);
|
|
71
70
|
this.metadata.setFormat('duration', common.numSampleFrames / common.sampleRate);
|
|
72
71
|
if (common.compressionName || common.compressionType) {
|
|
73
|
-
this.metadata.setFormat('codec',
|
|
72
|
+
this.metadata.setFormat('codec', common.compressionName ?? compressionTypes[common.compressionType]);
|
|
74
73
|
}
|
|
75
74
|
return header.chunkSize;
|
|
76
75
|
}
|
|
@@ -97,7 +96,7 @@ export class AIFFParser extends BasicParser {
|
|
|
97
96
|
}
|
|
98
97
|
async readTextChunk(header) {
|
|
99
98
|
const value = await this.tokenizer.readToken(new Token.StringType(header.chunkSize, 'ascii'));
|
|
100
|
-
const values = value.split('\0').map(v => v.trim()).filter(v => v
|
|
99
|
+
const values = value.split('\0').map(v => v.trim()).filter(v => v?.length);
|
|
101
100
|
await Promise.all(values.map(v => this.metadata.addTag('AIFF', header.chunkID, v)));
|
|
102
101
|
return header.chunkSize;
|
|
103
102
|
}
|
package/lib/aiff/AiffToken.d.ts
CHANGED
|
@@ -12,6 +12,20 @@ export declare const compressionTypes: {
|
|
|
12
12
|
FL32: string;
|
|
13
13
|
};
|
|
14
14
|
export type CompressionTypeCode = keyof typeof compressionTypes;
|
|
15
|
+
declare const AiffContentError_base: {
|
|
16
|
+
new (message: string): {
|
|
17
|
+
readonly fileType: string;
|
|
18
|
+
toString(): string;
|
|
19
|
+
name: "UnexpectedFileContentError";
|
|
20
|
+
message: string;
|
|
21
|
+
stack?: string;
|
|
22
|
+
};
|
|
23
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
24
|
+
prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
|
|
25
|
+
stackTraceLimit: number;
|
|
26
|
+
};
|
|
27
|
+
export declare class AiffContentError extends AiffContentError_base {
|
|
28
|
+
}
|
|
15
29
|
/**
|
|
16
30
|
* The Common Chunk.
|
|
17
31
|
* Describes fundamental parameters of the waveform data such as sample rate, bit resolution, and how many channels of
|
|
@@ -31,3 +45,4 @@ export declare class Common implements IGetToken<ICommon> {
|
|
|
31
45
|
constructor(header: iff.IChunkHeader, isAifc: boolean);
|
|
32
46
|
get(buf: Uint8Array, off: number): ICommon;
|
|
33
47
|
}
|
|
48
|
+
export {};
|
package/lib/aiff/AiffToken.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as Token from 'token-types';
|
|
2
2
|
import { FourCcToken } from '../common/FourCC.js';
|
|
3
|
+
import { makeUnexpectedFileContentError } from '../ParseError.js';
|
|
3
4
|
export const compressionTypes = {
|
|
4
5
|
NONE: 'not compressed PCM Apple Computer',
|
|
5
6
|
sowt: 'PCM (byte swapped)',
|
|
@@ -11,12 +12,14 @@ export const compressionTypes = {
|
|
|
11
12
|
ALAW: 'CCITT G.711 A-law 8-bit ITU-T G.711 A-law',
|
|
12
13
|
FL32: 'Float 32 IEEE 32-bit float '
|
|
13
14
|
};
|
|
15
|
+
export class AiffContentError extends makeUnexpectedFileContentError('AIFF') {
|
|
16
|
+
}
|
|
14
17
|
export class Common {
|
|
15
18
|
constructor(header, isAifc) {
|
|
16
19
|
this.isAifc = isAifc;
|
|
17
20
|
const minimumChunkSize = isAifc ? 22 : 18;
|
|
18
21
|
if (header.chunkSize < minimumChunkSize)
|
|
19
|
-
throw new
|
|
22
|
+
throw new AiffContentError(`COMMON CHUNK size should always be at least ${minimumChunkSize}`);
|
|
20
23
|
this.len = header.chunkSize;
|
|
21
24
|
}
|
|
22
25
|
get(buf, off) {
|
|
@@ -39,7 +42,7 @@ export class Common {
|
|
|
39
42
|
res.compressionName = new Token.StringType(strLen, 'latin1').get(buf, off + 23);
|
|
40
43
|
}
|
|
41
44
|
else {
|
|
42
|
-
throw new
|
|
45
|
+
throw new AiffContentError('Illegal pstring length');
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
else {
|
|
@@ -3,6 +3,20 @@ import type { IOptions, IRandomReader, IApeHeader } from '../type.js';
|
|
|
3
3
|
import type { INativeMetadataCollector } from '../common/MetadataCollector.js';
|
|
4
4
|
import { BasicParser } from '../common/BasicParser.js';
|
|
5
5
|
import { type IFooter, type IHeader } from './APEv2Token.js';
|
|
6
|
+
declare const ApeContentError_base: {
|
|
7
|
+
new (message: string): {
|
|
8
|
+
readonly fileType: string;
|
|
9
|
+
toString(): string;
|
|
10
|
+
name: "UnexpectedFileContentError";
|
|
11
|
+
message: string;
|
|
12
|
+
stack?: string;
|
|
13
|
+
};
|
|
14
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
15
|
+
prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
|
|
16
|
+
stackTraceLimit: number;
|
|
17
|
+
};
|
|
18
|
+
export declare class ApeContentError extends ApeContentError_base {
|
|
19
|
+
}
|
|
6
20
|
export declare class APEv2Parser extends BasicParser {
|
|
7
21
|
static tryParseApeHeader(metadata: INativeMetadataCollector, tokenizer: strtok3.ITokenizer, options: IOptions): Promise<void>;
|
|
8
22
|
/**
|
|
@@ -28,3 +42,4 @@ export declare class APEv2Parser extends BasicParser {
|
|
|
28
42
|
private parseDescriptorExpansion;
|
|
29
43
|
private parseHeader;
|
|
30
44
|
}
|
|
45
|
+
export {};
|
package/lib/apev2/APEv2Parser.js
CHANGED
|
@@ -5,9 +5,12 @@ import { uint8ArrayToString } from 'uint8array-extras';
|
|
|
5
5
|
import * as util from '../common/Util.js';
|
|
6
6
|
import { BasicParser } from '../common/BasicParser.js';
|
|
7
7
|
import { DataType, DescriptorParser, Header, TagFooter, TagItemHeader } from './APEv2Token.js';
|
|
8
|
+
import { makeUnexpectedFileContentError } from '../ParseError.js';
|
|
8
9
|
const debug = initDebug('music-metadata:parser:APEv2');
|
|
9
10
|
const tagFormat = 'APEv2';
|
|
10
11
|
const preamble = 'APETAGEX';
|
|
12
|
+
export class ApeContentError extends makeUnexpectedFileContentError('APEv2') {
|
|
13
|
+
}
|
|
11
14
|
export class APEv2Parser extends BasicParser {
|
|
12
15
|
constructor() {
|
|
13
16
|
super(...arguments);
|
|
@@ -52,7 +55,7 @@ export class APEv2Parser extends BasicParser {
|
|
|
52
55
|
static parseTagFooter(metadata, buffer, options) {
|
|
53
56
|
const footer = TagFooter.get(buffer, buffer.length - TagFooter.len);
|
|
54
57
|
if (footer.ID !== preamble)
|
|
55
|
-
throw new
|
|
58
|
+
throw new ApeContentError('Unexpected APEv2 Footer ID preamble value');
|
|
56
59
|
strtok3.fromBuffer(buffer);
|
|
57
60
|
const apeParser = new APEv2Parser();
|
|
58
61
|
apeParser.init(metadata, strtok3.fromBuffer(buffer), options);
|
|
@@ -83,7 +86,7 @@ export class APEv2Parser extends BasicParser {
|
|
|
83
86
|
async parse() {
|
|
84
87
|
const descriptor = await this.tokenizer.readToken(DescriptorParser);
|
|
85
88
|
if (descriptor.ID !== 'MAC ')
|
|
86
|
-
throw new
|
|
89
|
+
throw new ApeContentError('Unexpected descriptor ID');
|
|
87
90
|
this.ape.descriptor = descriptor;
|
|
88
91
|
const lenExp = descriptor.descriptorBytes - DescriptorParser.len;
|
|
89
92
|
const header = await (lenExp > 0 ? this.parseDescriptorExpansion(lenExp) : this.parseHeader());
|
|
@@ -156,7 +159,7 @@ export class APEv2Parser extends BasicParser {
|
|
|
156
159
|
this.metadata.setFormat('numberOfChannels', header.channel);
|
|
157
160
|
this.metadata.setFormat('duration', APEv2Parser.calculateDuration(header));
|
|
158
161
|
if (!this.ape.descriptor) {
|
|
159
|
-
throw new
|
|
162
|
+
throw new ApeContentError('Missing APE descriptor');
|
|
160
163
|
}
|
|
161
164
|
return {
|
|
162
165
|
forwardBytes: this.ape.descriptor.seekTableBytes + this.ape.descriptor.headerDataBytes +
|
package/lib/asf/AsfObject.d.ts
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import type { IGetToken, ITokenizer } from 'strtok3';
|
|
2
2
|
import type { AnyTagValue, IPicture, ITag } from '../type.js';
|
|
3
3
|
import GUID from './GUID.js';
|
|
4
|
+
declare const AsfContentParseError_base: {
|
|
5
|
+
new (message: string): {
|
|
6
|
+
readonly fileType: string;
|
|
7
|
+
toString(): string;
|
|
8
|
+
name: "UnexpectedFileContentError";
|
|
9
|
+
message: string;
|
|
10
|
+
stack?: string;
|
|
11
|
+
};
|
|
12
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
13
|
+
prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
|
|
14
|
+
stackTraceLimit: number;
|
|
15
|
+
};
|
|
16
|
+
export declare class AsfContentParseError extends AsfContentParseError_base {
|
|
17
|
+
}
|
|
4
18
|
/**
|
|
5
19
|
* Data Type: Specifies the type of information being stored. The following values are recognized.
|
|
6
20
|
*/
|
|
@@ -307,3 +321,4 @@ export declare class WmPictureToken implements IGetToken<IWmPicture> {
|
|
|
307
321
|
constructor(len: number);
|
|
308
322
|
get(buffer: Uint8Array, offset: number): IWmPicture;
|
|
309
323
|
}
|
|
324
|
+
export {};
|
package/lib/asf/AsfObject.js
CHANGED
|
@@ -4,6 +4,9 @@ import * as util from '../common/Util.js';
|
|
|
4
4
|
import GUID from './GUID.js';
|
|
5
5
|
import { getParserForAttr, parseUnicodeAttr } from './AsfUtil.js';
|
|
6
6
|
import { AttachedPictureType } from '../id3v2/ID3v2Token.js';
|
|
7
|
+
import { makeUnexpectedFileContentError } from '../ParseError.js';
|
|
8
|
+
export class AsfContentParseError extends makeUnexpectedFileContentError('ASF') {
|
|
9
|
+
}
|
|
7
10
|
/**
|
|
8
11
|
* Data Type: Specifies the type of information being stored. The following values are recognized.
|
|
9
12
|
*/
|
|
@@ -73,7 +76,7 @@ export class State {
|
|
|
73
76
|
else {
|
|
74
77
|
const parseAttr = getParserForAttr(valueType);
|
|
75
78
|
if (!parseAttr) {
|
|
76
|
-
throw new
|
|
79
|
+
throw new AsfContentParseError(`unexpected value headerType: ${valueType}`);
|
|
77
80
|
}
|
|
78
81
|
tags.push({ id: name, value: parseAttr(data) });
|
|
79
82
|
}
|
package/lib/asf/AsfParser.js
CHANGED
|
@@ -3,6 +3,7 @@ import { TrackType } from '../type.js';
|
|
|
3
3
|
import GUID from './GUID.js';
|
|
4
4
|
import * as AsfObject from './AsfObject.js';
|
|
5
5
|
import { BasicParser } from '../common/BasicParser.js';
|
|
6
|
+
import { AsfContentParseError } from './AsfObject.js';
|
|
6
7
|
const debug = initDebug('music-metadata:parser:ASF');
|
|
7
8
|
const headerType = 'asf';
|
|
8
9
|
/**
|
|
@@ -19,7 +20,7 @@ export class AsfParser extends BasicParser {
|
|
|
19
20
|
async parse() {
|
|
20
21
|
const header = await this.tokenizer.readToken(AsfObject.TopLevelHeaderObjectToken);
|
|
21
22
|
if (!header.objectId.equals(GUID.HeaderObject)) {
|
|
22
|
-
throw new
|
|
23
|
+
throw new AsfContentParseError(`expected asf header; but was not found; got: ${header.objectId.str}`);
|
|
23
24
|
}
|
|
24
25
|
try {
|
|
25
26
|
await this.parseObjectHeader(header.numberOfHeaderObjects);
|
|
@@ -8,6 +8,7 @@ import { VorbisTagMapper } from '../ogg/vorbis/VorbisTagMapper.js';
|
|
|
8
8
|
import { RiffInfoTagMapper } from '../riff/RiffInfoTagMap.js';
|
|
9
9
|
import { MatroskaTagMapper } from '../matroska/MatroskaTagMapper.js';
|
|
10
10
|
import { AiffTagMapper } from '../aiff/AiffTagMap.js';
|
|
11
|
+
import { InternalParserError } from '../ParseError.js';
|
|
11
12
|
export class CombinedTagMapper {
|
|
12
13
|
constructor() {
|
|
13
14
|
this.tagMappers = {};
|
|
@@ -39,7 +40,7 @@ export class CombinedTagMapper {
|
|
|
39
40
|
if (tagMapper) {
|
|
40
41
|
return this.tagMappers[tagType].mapGenericTag(tag, warnings);
|
|
41
42
|
}
|
|
42
|
-
throw new
|
|
43
|
+
throw new InternalParserError(`No generic tag mapper defined for tag-format: ${tagType}`);
|
|
43
44
|
}
|
|
44
45
|
registerTagMapper(genericTagMapper) {
|
|
45
46
|
for (const tagType of genericTagMapper.tagTypes) {
|
package/lib/common/FourCC.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { stringToUint8Array, uint8ArrayToString } from 'uint8array-extras';
|
|
2
2
|
import * as util from './Util.js';
|
|
3
|
+
import { InternalParserError, FieldDecodingError } from '../ParseError.js';
|
|
3
4
|
const validFourCC = /^[\x21-\x7e©][\x20-\x7e\x00()]{3}/;
|
|
4
5
|
/**
|
|
5
6
|
* Token for read FourCC
|
|
@@ -10,14 +11,14 @@ export const FourCcToken = {
|
|
|
10
11
|
get: (buf, off) => {
|
|
11
12
|
const id = uint8ArrayToString(buf.slice(off, off + FourCcToken.len), 'latin1');
|
|
12
13
|
if (!id.match(validFourCC)) {
|
|
13
|
-
throw new
|
|
14
|
+
throw new FieldDecodingError(`FourCC contains invalid characters: ${util.a2hex(id)} "${id}"`);
|
|
14
15
|
}
|
|
15
16
|
return id;
|
|
16
17
|
},
|
|
17
18
|
put: (buffer, offset, id) => {
|
|
18
19
|
const str = stringToUint8Array(id);
|
|
19
20
|
if (str.length !== 4)
|
|
20
|
-
throw new
|
|
21
|
+
throw new InternalParserError('Invalid length');
|
|
21
22
|
buffer.set(str, offset);
|
|
22
23
|
return offset + 4;
|
|
23
24
|
}
|
|
@@ -54,10 +54,9 @@ export class MetadataCollector {
|
|
|
54
54
|
this.format.trackInfo.push(streamInfo);
|
|
55
55
|
}
|
|
56
56
|
setFormat(key, value) {
|
|
57
|
-
var _a;
|
|
58
57
|
debug(`format: ${key} = ${value}`);
|
|
59
58
|
this.format[key] = value; // as any to override readonly
|
|
60
|
-
if (
|
|
59
|
+
if (this.opts?.observer) {
|
|
61
60
|
this.opts.observer({ metadata: this, tag: { type: 'format', id: key, value } });
|
|
62
61
|
}
|
|
63
62
|
}
|
|
@@ -239,7 +238,6 @@ export class MetadataCollector {
|
|
|
239
238
|
* Set generic tag
|
|
240
239
|
*/
|
|
241
240
|
setGenericTag(tagType, tag) {
|
|
242
|
-
var _a;
|
|
243
241
|
debug(`common.${tag.id} = ${tag.value}`);
|
|
244
242
|
const prio0 = this.commonOrigin[tag.id] || 1000;
|
|
245
243
|
const prio1 = this.originPriority[tagType];
|
|
@@ -270,7 +268,7 @@ export class MetadataCollector {
|
|
|
270
268
|
return debug(`Ignore native tag (list): ${tagType}.${tag.id} = ${tag.value}`);
|
|
271
269
|
}
|
|
272
270
|
}
|
|
273
|
-
if (
|
|
271
|
+
if (this.opts?.observer) {
|
|
274
272
|
this.opts.observer({ metadata: this, tag: { type: 'common', id: tag.id, value: tag.value } });
|
|
275
273
|
}
|
|
276
274
|
// ToDo: trigger metadata event
|
package/lib/common/Util.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { StringType } from 'token-types';
|
|
2
|
+
import { FieldDecodingError } from '../ParseError.js';
|
|
2
3
|
export function getBit(buf, off, bit) {
|
|
3
4
|
return (buf[off] & (1 << bit)) !== 0;
|
|
4
5
|
}
|
|
@@ -34,7 +35,7 @@ export function trimRightNull(x) {
|
|
|
34
35
|
function swapBytes(uint8Array) {
|
|
35
36
|
const l = uint8Array.length;
|
|
36
37
|
if ((l & 1) !== 0)
|
|
37
|
-
throw new
|
|
38
|
+
throw new FieldDecodingError('Buffer length must be even');
|
|
38
39
|
for (let i = 0; i < l; i += 2) {
|
|
39
40
|
const a = uint8Array[i];
|
|
40
41
|
uint8Array[i] = uint8Array[i + 1];
|
|
@@ -54,7 +55,7 @@ export function decodeString(uint8Array, encoding) {
|
|
|
54
55
|
if (encoding === 'utf-16le' && uint8Array[0] === 0xFE && uint8Array[1] === 0xFF) {
|
|
55
56
|
// BOM, indicating big endian decoding
|
|
56
57
|
if ((uint8Array.length & 1) !== 0)
|
|
57
|
-
throw new
|
|
58
|
+
throw new FieldDecodingError('Expected even number of octets for 16-bit unicode string');
|
|
58
59
|
return decodeString(swapBytes(uint8Array), encoding);
|
|
59
60
|
}
|
|
60
61
|
return new StringType(uint8Array.length, encoding).get(uint8Array, 0);
|
package/lib/core.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { type AnyWebByteStream, type IFileInfo, type ITokenizer } from 'strtok3'
|
|
|
5
5
|
import type { IAudioMetadata, INativeTagDict, IOptions, IPicture, IPrivateOptions, IRandomReader, ITag } from './type.js';
|
|
6
6
|
export type { IFileInfo } from 'strtok3';
|
|
7
7
|
export { type IAudioMetadata, type IOptions, type ITag, type INativeTagDict, type ICommonTagsResult, type IFormat, type IPicture, type IRatio, type IChapter, type ILyricsTag, LyricsContentType, TimestampFormat, IMetadataEventTag, IMetadataEvent } from './type.js';
|
|
8
|
+
export type * from './ParseError.js';
|
|
8
9
|
/**
|
|
9
10
|
* Parse Web API File
|
|
10
11
|
* Requires Blob to be able to stream using a ReadableStreamBYOBReader, only available since Node.js ≥ 20
|
|
@@ -22,7 +23,7 @@ export declare function parseBlob(blob: Blob, options?: IOptions): Promise<IAudi
|
|
|
22
23
|
*/
|
|
23
24
|
export declare function parseWebStream(webStream: AnyWebByteStream, fileInfo?: IFileInfo | string, options?: IOptions): Promise<IAudioMetadata>;
|
|
24
25
|
/**
|
|
25
|
-
* Parse audio from
|
|
26
|
+
* Parse audio from memory
|
|
26
27
|
* @param uint8Array - Uint8Array holding audio data
|
|
27
28
|
* @param fileInfo - File information object or MIME-type string
|
|
28
29
|
* @param options - Parsing options
|
package/lib/core.js
CHANGED
|
@@ -33,7 +33,7 @@ export function parseWebStream(webStream, fileInfo, options = {}) {
|
|
|
33
33
|
return parseFromTokenizer(fromWebStream(webStream, { fileInfo: typeof fileInfo === 'string' ? { mimeType: fileInfo } : fileInfo }), options);
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
|
-
* Parse audio from
|
|
36
|
+
* Parse audio from memory
|
|
37
37
|
* @param uint8Array - Uint8Array holding audio data
|
|
38
38
|
* @param fileInfo - File information object or MIME-type string
|
|
39
39
|
* @param options - Parsing options
|
package/lib/default.cjs
ADDED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import { BasicParser } from '../common/BasicParser.js';
|
|
2
|
+
declare const DsdiffContentParseError_base: {
|
|
3
|
+
new (message: string): {
|
|
4
|
+
readonly fileType: string;
|
|
5
|
+
toString(): string;
|
|
6
|
+
name: "UnexpectedFileContentError";
|
|
7
|
+
message: string;
|
|
8
|
+
stack?: string;
|
|
9
|
+
};
|
|
10
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
11
|
+
prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
|
|
12
|
+
stackTraceLimit: number;
|
|
13
|
+
};
|
|
14
|
+
export declare class DsdiffContentParseError extends DsdiffContentParseError_base {
|
|
15
|
+
}
|
|
2
16
|
/**
|
|
3
17
|
* DSDIFF - Direct Stream Digital Interchange File Format (Phillips)
|
|
4
18
|
*
|
|
@@ -12,3 +26,4 @@ export declare class DsdiffParser extends BasicParser {
|
|
|
12
26
|
private handleSoundPropertyChunks;
|
|
13
27
|
private handleChannelChunks;
|
|
14
28
|
}
|
|
29
|
+
export {};
|
|
@@ -5,7 +5,10 @@ import { FourCcToken } from '../common/FourCC.js';
|
|
|
5
5
|
import { BasicParser } from '../common/BasicParser.js';
|
|
6
6
|
import { ID3v2Parser } from '../id3v2/ID3v2Parser.js';
|
|
7
7
|
import { ChunkHeader64 } from './DsdiffToken.js';
|
|
8
|
+
import { makeUnexpectedFileContentError } from '../ParseError.js';
|
|
8
9
|
const debug = initDebug('music-metadata:parser:aiff');
|
|
10
|
+
export class DsdiffContentParseError extends makeUnexpectedFileContentError('DSDIFF') {
|
|
11
|
+
}
|
|
9
12
|
/**
|
|
10
13
|
* DSDIFF - Direct Stream Digital Interchange File Format (Phillips)
|
|
11
14
|
*
|
|
@@ -16,7 +19,7 @@ export class DsdiffParser extends BasicParser {
|
|
|
16
19
|
async parse() {
|
|
17
20
|
const header = await this.tokenizer.readToken(ChunkHeader64);
|
|
18
21
|
if (header.chunkID !== 'FRM8')
|
|
19
|
-
throw new
|
|
22
|
+
throw new DsdiffContentParseError('Unexpected chunk-ID');
|
|
20
23
|
const type = (await this.tokenizer.readToken(FourCcToken)).trim();
|
|
21
24
|
switch (type) {
|
|
22
25
|
case 'DSD':
|
|
@@ -24,7 +27,7 @@ export class DsdiffParser extends BasicParser {
|
|
|
24
27
|
this.metadata.setFormat('lossless', true);
|
|
25
28
|
return this.readFmt8Chunks(header.chunkSize - BigInt(FourCcToken.len));
|
|
26
29
|
default:
|
|
27
|
-
throw new
|
|
30
|
+
throw new DsdiffContentParseError(`Unsupported DSDIFF type: ${type}`);
|
|
28
31
|
}
|
|
29
32
|
}
|
|
30
33
|
async readFmt8Chunks(remainingSize) {
|
|
@@ -48,7 +51,7 @@ export class DsdiffParser extends BasicParser {
|
|
|
48
51
|
case 'PROP': { // 3.2 PROPERTY CHUNK
|
|
49
52
|
const propType = await this.tokenizer.readToken(FourCcToken);
|
|
50
53
|
if (propType !== 'SND ')
|
|
51
|
-
throw new
|
|
54
|
+
throw new DsdiffContentParseError('Unexpected PROP-chunk ID');
|
|
52
55
|
await this.handleSoundPropertyChunks(header.chunkSize - BigInt(FourCcToken.len));
|
|
53
56
|
break;
|
|
54
57
|
}
|
package/lib/dsf/DsfParser.d.ts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import { AbstractID3Parser } from '../id3v2/AbstractID3Parser.js';
|
|
2
|
+
declare const DsdContentParseError_base: {
|
|
3
|
+
new (message: string): {
|
|
4
|
+
readonly fileType: string;
|
|
5
|
+
toString(): string;
|
|
6
|
+
name: "UnexpectedFileContentError";
|
|
7
|
+
message: string;
|
|
8
|
+
stack?: string;
|
|
9
|
+
};
|
|
10
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
11
|
+
prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
|
|
12
|
+
stackTraceLimit: number;
|
|
13
|
+
};
|
|
14
|
+
export declare class DsdContentParseError extends DsdContentParseError_base {
|
|
15
|
+
}
|
|
2
16
|
/**
|
|
3
17
|
* DSF (dsd stream file) File Parser
|
|
4
18
|
* Ref: https://dsd-guide.com/sites/default/files/white-papers/DSFFileFormatSpec_E.pdf
|
|
@@ -7,3 +21,4 @@ export declare class DsfParser extends AbstractID3Parser {
|
|
|
7
21
|
postId3v2Parse(): Promise<void>;
|
|
8
22
|
private parseChunks;
|
|
9
23
|
}
|
|
24
|
+
export {};
|