music-metadata 11.16.1 → 12.0.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 +6 -5
- package/lib/ParseError.d.ts +10 -3
- package/lib/ParseError.js +2 -0
- package/lib/ParserFactory.js +4 -4
- package/lib/aiff/AiffParser.js +1 -4
- package/lib/aiff/AiffToken.d.ts +4 -3
- package/lib/aiff/AiffToken.js +2 -0
- package/lib/apev2/APEv2Parser.d.ts +4 -3
- package/lib/apev2/APEv2Parser.js +1 -4
- package/lib/asf/AsfGuid.js +49 -49
- package/lib/asf/AsfObject.d.ts +4 -3
- package/lib/asf/AsfObject.js +14 -9
- package/lib/common/BasicParser.js +3 -0
- package/lib/common/CombinedTagMapper.js +1 -1
- package/lib/common/GenericTagMapper.js +3 -1
- package/lib/common/MetadataCollector.d.ts +1 -0
- package/lib/common/MetadataCollector.js +31 -24
- package/lib/core.js +5 -10
- package/lib/dsdiff/DsdiffParser.d.ts +4 -3
- package/lib/dsf/DsfParser.d.ts +4 -3
- package/lib/ebml/EbmlIterator.d.ts +4 -3
- package/lib/ebml/EbmlIterator.js +4 -3
- package/lib/flac/FlacParser.js +1 -4
- package/lib/id3v1/ID3v1Parser.js +3 -0
- package/lib/id3v2/AbstractID3Parser.js +1 -4
- package/lib/id3v2/FrameParser.d.ts +4 -3
- package/lib/id3v2/FrameParser.js +2 -0
- package/lib/id3v2/ID3v2Parser.js +5 -7
- package/lib/matroska/MatroskaParser.js +7 -9
- package/lib/mp4/Atom.js +6 -0
- package/lib/mp4/AtomToken.d.ts +4 -4
- package/lib/mp4/AtomToken.js +14 -1
- package/lib/mp4/MP4Parser.js +78 -81
- package/lib/mpeg/MpegParser.d.ts +4 -3
- package/lib/mpeg/MpegParser.js +82 -69
- package/lib/musepack/MusepackConentError.d.ts +4 -3
- package/lib/musepack/sv7/BitReader.js +3 -2
- package/lib/musepack/sv7/MpcSv7Parser.js +3 -6
- package/lib/musepack/sv8/MpcSv8Parser.js +1 -4
- package/lib/musepack/sv8/StreamVersion8.js +1 -0
- package/lib/ogg/OggParser.d.ts +4 -3
- package/lib/ogg/OggParser.js +7 -6
- package/lib/ogg/OggToken.js +1 -0
- package/lib/ogg/flac/FlacStream.js +5 -1
- package/lib/ogg/opus/Opus.d.ts +4 -3
- package/lib/ogg/opus/Opus.js +1 -0
- package/lib/ogg/opus/OpusStream.js +3 -2
- package/lib/ogg/speex/SpeexStream.js +1 -1
- package/lib/ogg/theora/TheoraStream.js +2 -1
- package/lib/ogg/vorbis/Vorbis.js +1 -0
- package/lib/ogg/vorbis/VorbisDecoder.js +2 -0
- package/lib/ogg/vorbis/VorbisStream.d.ts +4 -3
- package/lib/ogg/vorbis/VorbisStream.js +6 -3
- package/lib/riff/RiffChunk.js +3 -0
- package/lib/wav/WaveChunk.d.ts +4 -3
- package/lib/wav/WaveChunk.js +2 -0
- package/lib/wav/WaveParser.js +7 -6
- package/lib/wavpack/WavPackParser.d.ts +4 -3
- package/lib/wavpack/WavPackParser.js +1 -4
- package/package.json +20 -16
package/README.md
CHANGED
|
@@ -23,12 +23,12 @@ The [`music-metadata`](https://github.com/Borewit/music-metadata) module is idea
|
|
|
23
23
|
## Compatibility
|
|
24
24
|
|
|
25
25
|
Module: version 8 migrated from [CommonJS](https://en.wikipedia.org/wiki/CommonJS) to [pure ECMAScript Module (ESM)](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).
|
|
26
|
-
The distributed JavaScript codebase
|
|
26
|
+
The distributed JavaScript codebase targets [ECMAScript 2023 (14th Edition)](https://262.ecma-international.org/14.0/).
|
|
27
27
|
|
|
28
28
|
> [!NOTE]
|
|
29
29
|
> See also [CommonJS backward Compatibility](#commonjs-backward-compatibility)
|
|
30
30
|
|
|
31
|
-
This module requires a [Node.js ≥
|
|
31
|
+
This module requires a [Node.js ≥ 22](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
34
|
## Support the Project
|
|
@@ -99,11 +99,12 @@ Support for encoding / format details:
|
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
## Online demo's
|
|
102
|
-
- [<img src="https://raw.githubusercontent.com/Borewit/audio-tag-analyzer/master/
|
|
103
|
-
- [<img src="https://cdn.sanity.io/images/3do82whm/next/ba8c847f13a5fa39d88f8bc9b7846b7886531b18-2500x2500.svg" width="40"> Webamp](https://webamp.org/)
|
|
102
|
+
- [<img src="https://raw.githubusercontent.com/Borewit/audio-tag-analyzer-vite/master/public/favicon.svg" width="40" alt="Audio Tag Analyzer logo"> Audio Tag Analyzer](https://audio-tag-analyzer.netlify.app/) ([source code](https://github.com/Borewit/audio-tag-analyzer-vite))
|
|
104
103
|
- [ICY Radio Stream Player](https://icy-radio-stream-player.netlify.app/)
|
|
105
104
|
- Expected to be released soon: [Overtone](https://overtone.pro/) by [Johannes Schickling](https://github.com/schickling)
|
|
106
105
|
|
|
106
|
+
Used by [around 50k+ GitHub projects](https://github.com/Borewit/music-metadata/network/dependents).
|
|
107
|
+
|
|
107
108
|
## Usage
|
|
108
109
|
|
|
109
110
|
### Installation
|
|
@@ -799,7 +800,7 @@ Using Node.js ≥ 22, which is support loading ESM module via require
|
|
|
799
800
|
const mm = require('music-metadata');
|
|
800
801
|
```
|
|
801
802
|
|
|
802
|
-
|
|
803
|
+
Alternatively, dynamically import **music-metadata**:
|
|
803
804
|
```js
|
|
804
805
|
(async () => {
|
|
805
806
|
// Dynamically loads the ESM module in a CommonJS project
|
package/lib/ParseError.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export declare const makeParseError: <Name extends string>(name: Name) => {
|
|
|
4
4
|
name: Name;
|
|
5
5
|
message: string;
|
|
6
6
|
stack?: string;
|
|
7
|
+
cause?: unknown;
|
|
7
8
|
};
|
|
8
9
|
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
9
10
|
prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
|
|
@@ -14,6 +15,7 @@ declare const CouldNotDetermineFileTypeError_base: {
|
|
|
14
15
|
name: "CouldNotDetermineFileTypeError";
|
|
15
16
|
message: string;
|
|
16
17
|
stack?: string;
|
|
18
|
+
cause?: unknown;
|
|
17
19
|
};
|
|
18
20
|
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
19
21
|
prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
|
|
@@ -26,6 +28,7 @@ declare const UnsupportedFileTypeError_base: {
|
|
|
26
28
|
name: "UnsupportedFileTypeError";
|
|
27
29
|
message: string;
|
|
28
30
|
stack?: string;
|
|
31
|
+
cause?: unknown;
|
|
29
32
|
};
|
|
30
33
|
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
31
34
|
prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
|
|
@@ -38,6 +41,7 @@ declare const UnexpectedFileContentError_base: {
|
|
|
38
41
|
name: "UnexpectedFileContentError";
|
|
39
42
|
message: string;
|
|
40
43
|
stack?: string;
|
|
44
|
+
cause?: unknown;
|
|
41
45
|
};
|
|
42
46
|
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
43
47
|
prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
|
|
@@ -53,6 +57,7 @@ declare const FieldDecodingError_base: {
|
|
|
53
57
|
name: "FieldDecodingError";
|
|
54
58
|
message: string;
|
|
55
59
|
stack?: string;
|
|
60
|
+
cause?: unknown;
|
|
56
61
|
};
|
|
57
62
|
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
58
63
|
prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
|
|
@@ -65,6 +70,7 @@ declare const InternalParserError_base: {
|
|
|
65
70
|
name: "InternalParserError";
|
|
66
71
|
message: string;
|
|
67
72
|
stack?: string;
|
|
73
|
+
cause?: unknown;
|
|
68
74
|
};
|
|
69
75
|
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
70
76
|
prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
|
|
@@ -74,11 +80,12 @@ export declare class InternalParserError extends InternalParserError_base {
|
|
|
74
80
|
}
|
|
75
81
|
export declare const makeUnexpectedFileContentError: <FileType extends string>(fileType: FileType) => {
|
|
76
82
|
new (message: string): {
|
|
77
|
-
readonly fileType: string;
|
|
78
|
-
toString(): string;
|
|
79
|
-
name: "UnexpectedFileContentError";
|
|
80
83
|
message: string;
|
|
81
84
|
stack?: string;
|
|
85
|
+
cause?: unknown;
|
|
86
|
+
name: "UnexpectedFileContentError";
|
|
87
|
+
readonly fileType: string;
|
|
88
|
+
toString(): string;
|
|
82
89
|
};
|
|
83
90
|
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
84
91
|
prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
|
package/lib/ParseError.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export const makeParseError = (name) => {
|
|
2
2
|
return class ParseError extends Error {
|
|
3
|
+
name;
|
|
3
4
|
constructor(message) {
|
|
4
5
|
super(message);
|
|
5
6
|
this.name = name;
|
|
@@ -14,6 +15,7 @@ export class UnsupportedFileTypeError extends makeParseError('UnsupportedFileTyp
|
|
|
14
15
|
}
|
|
15
16
|
// Concrete error class representing unexpected file content.
|
|
16
17
|
export class UnexpectedFileContentError extends makeParseError('UnexpectedFileContentError') {
|
|
18
|
+
fileType;
|
|
17
19
|
constructor(fileType, message) {
|
|
18
20
|
super(message);
|
|
19
21
|
this.fileType = fileType;
|
package/lib/ParserFactory.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { parse as parseContentType } from 'content-type';
|
|
2
2
|
import initDebug from 'debug';
|
|
3
3
|
import { fileTypeFromBuffer } from 'file-type';
|
|
4
4
|
import { parse as mimeTypeParse } from 'media-typer';
|
|
@@ -21,7 +21,7 @@ import { riffParserLoader } from './wav/WaveLoader.js';
|
|
|
21
21
|
import { wavpackParserLoader } from './wavpack/WavPackLoader.js';
|
|
22
22
|
const debug = initDebug('music-metadata:parser:factory');
|
|
23
23
|
export function parseHttpContentType(contentType) {
|
|
24
|
-
const type =
|
|
24
|
+
const type = parseContentType(contentType);
|
|
25
25
|
const mime = mimeTypeParse(type.type);
|
|
26
26
|
return {
|
|
27
27
|
type: mime.type,
|
|
@@ -31,8 +31,8 @@ export function parseHttpContentType(contentType) {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
export class ParserFactory {
|
|
34
|
+
parsers = [];
|
|
34
35
|
constructor() {
|
|
35
|
-
this.parsers = [];
|
|
36
36
|
[
|
|
37
37
|
flacParserLoader,
|
|
38
38
|
mpegParserLoader,
|
|
@@ -54,7 +54,7 @@ export class ParserFactory {
|
|
|
54
54
|
registerParser(parser) {
|
|
55
55
|
this.parsers.push(parser);
|
|
56
56
|
}
|
|
57
|
-
async parse(tokenizer, parserLoader, opts) {
|
|
57
|
+
async parse(tokenizer, parserLoader, opts = {}) {
|
|
58
58
|
if (tokenizer.supportsRandomAccess()) {
|
|
59
59
|
debug('tokenizer supports random-access, scanning for appending headers');
|
|
60
60
|
await scanAppendingHeaders(tokenizer, opts);
|
package/lib/aiff/AiffParser.js
CHANGED
|
@@ -16,10 +16,7 @@ const debug = initDebug('music-metadata:parser:aiff');
|
|
|
16
16
|
* - http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/AIFF/Docs/AIFF-1.3.pdf
|
|
17
17
|
*/
|
|
18
18
|
export class AIFFParser extends BasicParser {
|
|
19
|
-
|
|
20
|
-
super(...arguments);
|
|
21
|
-
this.isCompressed = null;
|
|
22
|
-
}
|
|
19
|
+
isCompressed = null;
|
|
23
20
|
async parse() {
|
|
24
21
|
const header = await this.tokenizer.readToken(iff.Header);
|
|
25
22
|
if (header.chunkID !== 'FORM') {
|
package/lib/aiff/AiffToken.d.ts
CHANGED
|
@@ -14,11 +14,12 @@ export declare const compressionTypes: {
|
|
|
14
14
|
export type CompressionTypeCode = keyof typeof compressionTypes;
|
|
15
15
|
declare const AiffContentError_base: {
|
|
16
16
|
new (message: string): {
|
|
17
|
-
readonly fileType: string;
|
|
18
|
-
toString(): string;
|
|
19
|
-
name: "UnexpectedFileContentError";
|
|
20
17
|
message: string;
|
|
21
18
|
stack?: string;
|
|
19
|
+
cause?: unknown;
|
|
20
|
+
name: "UnexpectedFileContentError";
|
|
21
|
+
readonly fileType: string;
|
|
22
|
+
toString(): string;
|
|
22
23
|
};
|
|
23
24
|
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
24
25
|
prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
|
package/lib/aiff/AiffToken.js
CHANGED
|
@@ -15,6 +15,8 @@ export const compressionTypes = {
|
|
|
15
15
|
export class AiffContentError extends makeUnexpectedFileContentError('AIFF') {
|
|
16
16
|
}
|
|
17
17
|
export class Common {
|
|
18
|
+
len;
|
|
19
|
+
isAifc;
|
|
18
20
|
constructor(header, isAifc) {
|
|
19
21
|
this.isAifc = isAifc;
|
|
20
22
|
const minimumChunkSize = isAifc ? 22 : 18;
|
|
@@ -6,11 +6,12 @@ import type { IApeHeader, IOptions } from '../type.js';
|
|
|
6
6
|
import { type IFooter, type IHeader } from './APEv2Token.js';
|
|
7
7
|
declare const ApeContentError_base: {
|
|
8
8
|
new (message: string): {
|
|
9
|
-
readonly fileType: string;
|
|
10
|
-
toString(): string;
|
|
11
|
-
name: "UnexpectedFileContentError";
|
|
12
9
|
message: string;
|
|
13
10
|
stack?: string;
|
|
11
|
+
cause?: unknown;
|
|
12
|
+
name: "UnexpectedFileContentError";
|
|
13
|
+
readonly fileType: string;
|
|
14
|
+
toString(): string;
|
|
14
15
|
};
|
|
15
16
|
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
16
17
|
prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
|
package/lib/apev2/APEv2Parser.js
CHANGED
|
@@ -16,10 +16,6 @@ export function tryParseApeHeader(metadata, tokenizer, options) {
|
|
|
16
16
|
return apeParser.tryParseApeHeader();
|
|
17
17
|
}
|
|
18
18
|
export class APEv2Parser extends BasicParser {
|
|
19
|
-
constructor() {
|
|
20
|
-
super(...arguments);
|
|
21
|
-
this.ape = {};
|
|
22
|
-
}
|
|
23
19
|
/**
|
|
24
20
|
* Calculate the media file duration
|
|
25
21
|
* @param ah ApeHeader
|
|
@@ -68,6 +64,7 @@ export class APEv2Parser extends BasicParser {
|
|
|
68
64
|
const apeParser = new APEv2Parser(metadata, strtok3.fromBuffer(buffer), options);
|
|
69
65
|
return apeParser.parseTags(footer);
|
|
70
66
|
}
|
|
67
|
+
ape = {};
|
|
71
68
|
/**
|
|
72
69
|
* Parse APEv1 / APEv2 header if header signature found
|
|
73
70
|
*/
|
package/lib/asf/AsfGuid.js
CHANGED
|
@@ -13,7 +13,54 @@ import { Guid, parseWindowsGuid } from 'win-guid';
|
|
|
13
13
|
* - http://drang.s4.xrea.com/program/tips/id3tag/wmp/10_asf_guids.html
|
|
14
14
|
* - https://github.com/dji-sdk/FFmpeg/blob/master/libavformat/asf.c
|
|
15
15
|
*/
|
|
16
|
-
class AsfGuid {
|
|
16
|
+
export default class AsfGuid {
|
|
17
|
+
// 10.1 Top-level ASF object GUIDs
|
|
18
|
+
static HeaderObject = new AsfGuid('75B22630-668E-11CF-A6D9-00AA0062CE6C');
|
|
19
|
+
static DataObject = new AsfGuid('75B22636-668E-11CF-A6D9-00AA0062CE6C');
|
|
20
|
+
static SimpleIndexObject = new AsfGuid('33000890-E5B1-11CF-89F4-00A0C90349CB');
|
|
21
|
+
static IndexObject = new AsfGuid('D6E229D3-35DA-11D1-9034-00A0C90349BE');
|
|
22
|
+
static MediaObjectIndexObject = new AsfGuid('FEB103F8-12AD-4C64-840F-2A1D2F7AD48C');
|
|
23
|
+
static TimecodeIndexObject = new AsfGuid('3CB73FD0-0C4A-4803-953D-EDF7B6228F0C');
|
|
24
|
+
// 10.2 Header Object GUIDs
|
|
25
|
+
static FilePropertiesObject = new AsfGuid('8CABDCA1-A947-11CF-8EE4-00C00C205365');
|
|
26
|
+
static StreamPropertiesObject = new AsfGuid('B7DC0791-A9B7-11CF-8EE6-00C00C205365');
|
|
27
|
+
static HeaderExtensionObject = new AsfGuid('5FBF03B5-A92E-11CF-8EE3-00C00C205365');
|
|
28
|
+
static CodecListObject = new AsfGuid('86D15240-311D-11D0-A3A4-00A0C90348F6');
|
|
29
|
+
static ScriptCommandObject = new AsfGuid('1EFB1A30-0B62-11D0-A39B-00A0C90348F6');
|
|
30
|
+
static MarkerObject = new AsfGuid('F487CD01-A951-11CF-8EE6-00C00C205365');
|
|
31
|
+
static BitrateMutualExclusionObject = new AsfGuid('D6E229DC-35DA-11D1-9034-00A0C90349BE');
|
|
32
|
+
static ErrorCorrectionObject = new AsfGuid('75B22635-668E-11CF-A6D9-00AA0062CE6C');
|
|
33
|
+
static ContentDescriptionObject = new AsfGuid('75B22633-668E-11CF-A6D9-00AA0062CE6C');
|
|
34
|
+
static ExtendedContentDescriptionObject = new AsfGuid('D2D0A440-E307-11D2-97F0-00A0C95EA850');
|
|
35
|
+
static ContentBrandingObject = new AsfGuid('2211B3FA-BD23-11D2-B4B7-00A0C955FC6E');
|
|
36
|
+
static StreamBitratePropertiesObject = new AsfGuid('7BF875CE-468D-11D1-8D82-006097C9A2B2');
|
|
37
|
+
static ContentEncryptionObject = new AsfGuid('2211B3FB-BD23-11D2-B4B7-00A0C955FC6E');
|
|
38
|
+
static ExtendedContentEncryptionObject = new AsfGuid('298AE614-2622-4C17-B935-DAE07EE9289C');
|
|
39
|
+
static DigitalSignatureObject = new AsfGuid('2211B3FC-BD23-11D2-B4B7-00A0C955FC6E');
|
|
40
|
+
static PaddingObject = new AsfGuid('1806D474-CADF-4509-A4BA-9AABCB96AAE8');
|
|
41
|
+
// 10.3 Header Extension Object GUIDs
|
|
42
|
+
static ExtendedStreamPropertiesObject = new AsfGuid('14E6A5CB-C672-4332-8399-A96952065B5A');
|
|
43
|
+
static AdvancedMutualExclusionObject = new AsfGuid('A08649CF-4775-4670-8A16-6E35357566CD');
|
|
44
|
+
static GroupMutualExclusionObject = new AsfGuid('D1465A40-5A79-4338-B71B-E36B8FD6C249');
|
|
45
|
+
static StreamPrioritizationObject = new AsfGuid('D4FED15B-88D3-454F-81F0-ED5C45999E24');
|
|
46
|
+
static BandwidthSharingObject = new AsfGuid('A69609E6-517B-11D2-B6AF-00C04FD908E9');
|
|
47
|
+
static LanguageListObject = new AsfGuid('7C4346A9-EFE0-4BFC-B229-393EDE415C85');
|
|
48
|
+
static MetadataObject = new AsfGuid('C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA');
|
|
49
|
+
static MetadataLibraryObject = new AsfGuid('44231C94-9498-49D1-A141-1D134E457054');
|
|
50
|
+
static IndexParametersObject = new AsfGuid('D6E229DF-35DA-11D1-9034-00A0C90349BE');
|
|
51
|
+
static MediaObjectIndexParametersObject = new AsfGuid('6B203BAD-3F11-48E4-ACA8-D7613DE2CFA7');
|
|
52
|
+
static TimecodeIndexParametersObject = new AsfGuid('F55E496D-9797-4B5D-8C8B-604DFE9BFB24');
|
|
53
|
+
static CompatibilityObject = new AsfGuid('26F18B5D-4584-47EC-9F5F-0E651F0452C9');
|
|
54
|
+
static AdvancedContentEncryptionObject = new AsfGuid('43058533-6981-49E6-9B74-AD12CB86D58C');
|
|
55
|
+
// 10.4 Stream Properties Object Stream Type GUIDs
|
|
56
|
+
static AudioMedia = new AsfGuid('F8699E40-5B4D-11CF-A8FD-00805F5C442B');
|
|
57
|
+
static VideoMedia = new AsfGuid('BC19EFC0-5B4D-11CF-A8FD-00805F5C442B');
|
|
58
|
+
static CommandMedia = new AsfGuid('59DACFC0-59E6-11D0-A3AC-00A0C90348F6');
|
|
59
|
+
static JFIF_Media = new AsfGuid('B61BE100-5B4E-11CF-A8FD-00805F5C442B');
|
|
60
|
+
static Degradable_JPEG_Media = new AsfGuid('35907DE0-E415-11CF-A917-00805F5C442B');
|
|
61
|
+
static FileTransferMedia = new AsfGuid('91BD222C-F21C-497A-8B6D-5AA86BFC0185');
|
|
62
|
+
static BinaryMedia = new AsfGuid('3AFB65E2-47EF-40F2-AC2C-70A90D71D343');
|
|
63
|
+
static ASF_Index_Placeholder_Object = new AsfGuid('D9AADE20-7C17-4F9C-BC28-8555DD98E2A2');
|
|
17
64
|
static fromBin(bin, offset = 0) {
|
|
18
65
|
return new AsfGuid(AsfGuid.decode(bin, offset));
|
|
19
66
|
}
|
|
@@ -55,6 +102,7 @@ class AsfGuid {
|
|
|
55
102
|
static encode(guid) {
|
|
56
103
|
return parseWindowsGuid(guid);
|
|
57
104
|
}
|
|
105
|
+
str;
|
|
58
106
|
constructor(str) {
|
|
59
107
|
this.str = str;
|
|
60
108
|
}
|
|
@@ -65,51 +113,3 @@ class AsfGuid {
|
|
|
65
113
|
return AsfGuid.encode(this.str);
|
|
66
114
|
}
|
|
67
115
|
}
|
|
68
|
-
// 10.1 Top-level ASF object GUIDs
|
|
69
|
-
AsfGuid.HeaderObject = new AsfGuid('75B22630-668E-11CF-A6D9-00AA0062CE6C');
|
|
70
|
-
AsfGuid.DataObject = new AsfGuid('75B22636-668E-11CF-A6D9-00AA0062CE6C');
|
|
71
|
-
AsfGuid.SimpleIndexObject = new AsfGuid('33000890-E5B1-11CF-89F4-00A0C90349CB');
|
|
72
|
-
AsfGuid.IndexObject = new AsfGuid('D6E229D3-35DA-11D1-9034-00A0C90349BE');
|
|
73
|
-
AsfGuid.MediaObjectIndexObject = new AsfGuid('FEB103F8-12AD-4C64-840F-2A1D2F7AD48C');
|
|
74
|
-
AsfGuid.TimecodeIndexObject = new AsfGuid('3CB73FD0-0C4A-4803-953D-EDF7B6228F0C');
|
|
75
|
-
// 10.2 Header Object GUIDs
|
|
76
|
-
AsfGuid.FilePropertiesObject = new AsfGuid('8CABDCA1-A947-11CF-8EE4-00C00C205365');
|
|
77
|
-
AsfGuid.StreamPropertiesObject = new AsfGuid('B7DC0791-A9B7-11CF-8EE6-00C00C205365');
|
|
78
|
-
AsfGuid.HeaderExtensionObject = new AsfGuid('5FBF03B5-A92E-11CF-8EE3-00C00C205365');
|
|
79
|
-
AsfGuid.CodecListObject = new AsfGuid('86D15240-311D-11D0-A3A4-00A0C90348F6');
|
|
80
|
-
AsfGuid.ScriptCommandObject = new AsfGuid('1EFB1A30-0B62-11D0-A39B-00A0C90348F6');
|
|
81
|
-
AsfGuid.MarkerObject = new AsfGuid('F487CD01-A951-11CF-8EE6-00C00C205365');
|
|
82
|
-
AsfGuid.BitrateMutualExclusionObject = new AsfGuid('D6E229DC-35DA-11D1-9034-00A0C90349BE');
|
|
83
|
-
AsfGuid.ErrorCorrectionObject = new AsfGuid('75B22635-668E-11CF-A6D9-00AA0062CE6C');
|
|
84
|
-
AsfGuid.ContentDescriptionObject = new AsfGuid('75B22633-668E-11CF-A6D9-00AA0062CE6C');
|
|
85
|
-
AsfGuid.ExtendedContentDescriptionObject = new AsfGuid('D2D0A440-E307-11D2-97F0-00A0C95EA850');
|
|
86
|
-
AsfGuid.ContentBrandingObject = new AsfGuid('2211B3FA-BD23-11D2-B4B7-00A0C955FC6E');
|
|
87
|
-
AsfGuid.StreamBitratePropertiesObject = new AsfGuid('7BF875CE-468D-11D1-8D82-006097C9A2B2');
|
|
88
|
-
AsfGuid.ContentEncryptionObject = new AsfGuid('2211B3FB-BD23-11D2-B4B7-00A0C955FC6E');
|
|
89
|
-
AsfGuid.ExtendedContentEncryptionObject = new AsfGuid('298AE614-2622-4C17-B935-DAE07EE9289C');
|
|
90
|
-
AsfGuid.DigitalSignatureObject = new AsfGuid('2211B3FC-BD23-11D2-B4B7-00A0C955FC6E');
|
|
91
|
-
AsfGuid.PaddingObject = new AsfGuid('1806D474-CADF-4509-A4BA-9AABCB96AAE8');
|
|
92
|
-
// 10.3 Header Extension Object GUIDs
|
|
93
|
-
AsfGuid.ExtendedStreamPropertiesObject = new AsfGuid('14E6A5CB-C672-4332-8399-A96952065B5A');
|
|
94
|
-
AsfGuid.AdvancedMutualExclusionObject = new AsfGuid('A08649CF-4775-4670-8A16-6E35357566CD');
|
|
95
|
-
AsfGuid.GroupMutualExclusionObject = new AsfGuid('D1465A40-5A79-4338-B71B-E36B8FD6C249');
|
|
96
|
-
AsfGuid.StreamPrioritizationObject = new AsfGuid('D4FED15B-88D3-454F-81F0-ED5C45999E24');
|
|
97
|
-
AsfGuid.BandwidthSharingObject = new AsfGuid('A69609E6-517B-11D2-B6AF-00C04FD908E9');
|
|
98
|
-
AsfGuid.LanguageListObject = new AsfGuid('7C4346A9-EFE0-4BFC-B229-393EDE415C85');
|
|
99
|
-
AsfGuid.MetadataObject = new AsfGuid('C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA');
|
|
100
|
-
AsfGuid.MetadataLibraryObject = new AsfGuid('44231C94-9498-49D1-A141-1D134E457054');
|
|
101
|
-
AsfGuid.IndexParametersObject = new AsfGuid('D6E229DF-35DA-11D1-9034-00A0C90349BE');
|
|
102
|
-
AsfGuid.MediaObjectIndexParametersObject = new AsfGuid('6B203BAD-3F11-48E4-ACA8-D7613DE2CFA7');
|
|
103
|
-
AsfGuid.TimecodeIndexParametersObject = new AsfGuid('F55E496D-9797-4B5D-8C8B-604DFE9BFB24');
|
|
104
|
-
AsfGuid.CompatibilityObject = new AsfGuid('26F18B5D-4584-47EC-9F5F-0E651F0452C9');
|
|
105
|
-
AsfGuid.AdvancedContentEncryptionObject = new AsfGuid('43058533-6981-49E6-9B74-AD12CB86D58C');
|
|
106
|
-
// 10.4 Stream Properties Object Stream Type GUIDs
|
|
107
|
-
AsfGuid.AudioMedia = new AsfGuid('F8699E40-5B4D-11CF-A8FD-00805F5C442B');
|
|
108
|
-
AsfGuid.VideoMedia = new AsfGuid('BC19EFC0-5B4D-11CF-A8FD-00805F5C442B');
|
|
109
|
-
AsfGuid.CommandMedia = new AsfGuid('59DACFC0-59E6-11D0-A3AC-00A0C90348F6');
|
|
110
|
-
AsfGuid.JFIF_Media = new AsfGuid('B61BE100-5B4E-11CF-A8FD-00805F5C442B');
|
|
111
|
-
AsfGuid.Degradable_JPEG_Media = new AsfGuid('35907DE0-E415-11CF-A917-00805F5C442B');
|
|
112
|
-
AsfGuid.FileTransferMedia = new AsfGuid('91BD222C-F21C-497A-8B6D-5AA86BFC0185');
|
|
113
|
-
AsfGuid.BinaryMedia = new AsfGuid('3AFB65E2-47EF-40F2-AC2C-70A90D71D343');
|
|
114
|
-
AsfGuid.ASF_Index_Placeholder_Object = new AsfGuid('D9AADE20-7C17-4F9C-BC28-8555DD98E2A2');
|
|
115
|
-
export default AsfGuid;
|
package/lib/asf/AsfObject.d.ts
CHANGED
|
@@ -3,11 +3,12 @@ import type { AnyTagValue, IPicture, ITag } from '../type.js';
|
|
|
3
3
|
import AsfGuid from './AsfGuid.js';
|
|
4
4
|
declare const AsfContentParseError_base: {
|
|
5
5
|
new (message: string): {
|
|
6
|
-
readonly fileType: string;
|
|
7
|
-
toString(): string;
|
|
8
|
-
name: "UnexpectedFileContentError";
|
|
9
6
|
message: string;
|
|
10
7
|
stack?: string;
|
|
8
|
+
cause?: unknown;
|
|
9
|
+
name: "UnexpectedFileContentError";
|
|
10
|
+
readonly fileType: string;
|
|
11
|
+
toString(): string;
|
|
11
12
|
};
|
|
12
13
|
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
13
14
|
prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
|
package/lib/asf/AsfObject.js
CHANGED
|
@@ -73,6 +73,7 @@ export const HeaderObjectToken = {
|
|
|
73
73
|
}
|
|
74
74
|
};
|
|
75
75
|
export class State {
|
|
76
|
+
len;
|
|
76
77
|
constructor(header) {
|
|
77
78
|
this.len = header.objectSize - HeaderObjectToken.len;
|
|
78
79
|
}
|
|
@@ -94,6 +95,7 @@ export class State {
|
|
|
94
95
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_2
|
|
95
96
|
*/
|
|
96
97
|
export class FilePropertiesObject extends State {
|
|
98
|
+
static guid = AsfGuid.FilePropertiesObject;
|
|
97
99
|
get(buf, off) {
|
|
98
100
|
return {
|
|
99
101
|
fileId: AsfGuid.fromBin(buf, off),
|
|
@@ -114,12 +116,12 @@ export class FilePropertiesObject extends State {
|
|
|
114
116
|
};
|
|
115
117
|
}
|
|
116
118
|
}
|
|
117
|
-
FilePropertiesObject.guid = AsfGuid.FilePropertiesObject;
|
|
118
119
|
/**
|
|
119
120
|
* Token for: 3.3 Stream Properties Object (mandatory, one per stream)
|
|
120
121
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_3
|
|
121
122
|
*/
|
|
122
123
|
export class StreamPropertiesObject extends State {
|
|
124
|
+
static guid = AsfGuid.StreamPropertiesObject;
|
|
123
125
|
get(buf, off) {
|
|
124
126
|
return {
|
|
125
127
|
streamType: AsfGuid.decodeMediaType(AsfGuid.fromBin(buf, off)),
|
|
@@ -128,12 +130,13 @@ export class StreamPropertiesObject extends State {
|
|
|
128
130
|
};
|
|
129
131
|
}
|
|
130
132
|
}
|
|
131
|
-
StreamPropertiesObject.guid = AsfGuid.StreamPropertiesObject;
|
|
132
133
|
/**
|
|
133
134
|
* 3.4: Header Extension Object (mandatory, one only)
|
|
134
135
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_4
|
|
135
136
|
*/
|
|
136
137
|
export class HeaderExtensionObject {
|
|
138
|
+
static guid = AsfGuid.HeaderExtensionObject;
|
|
139
|
+
len;
|
|
137
140
|
constructor() {
|
|
138
141
|
this.len = 22;
|
|
139
142
|
}
|
|
@@ -146,7 +149,6 @@ export class HeaderExtensionObject {
|
|
|
146
149
|
};
|
|
147
150
|
}
|
|
148
151
|
}
|
|
149
|
-
HeaderExtensionObject.guid = AsfGuid.HeaderExtensionObject;
|
|
150
152
|
/**
|
|
151
153
|
* 3.5: The Codec List Object provides user-friendly information about the codecs and formats used to encode the content found in the ASF file.
|
|
152
154
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_5
|
|
@@ -161,6 +163,8 @@ const CodecListObjectHeader = {
|
|
|
161
163
|
}
|
|
162
164
|
};
|
|
163
165
|
class BoundedReader {
|
|
166
|
+
tokenizer;
|
|
167
|
+
remaining;
|
|
164
168
|
constructor(tokenizer, size) {
|
|
165
169
|
this.tokenizer = tokenizer;
|
|
166
170
|
this.remaining = size;
|
|
@@ -237,6 +241,8 @@ async function readCodecEntry(reader) {
|
|
|
237
241
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_10
|
|
238
242
|
*/
|
|
239
243
|
export class ContentDescriptionObjectState extends State {
|
|
244
|
+
static guid = AsfGuid.ContentDescriptionObject;
|
|
245
|
+
static contentDescTags = ['Title', 'Author', 'Copyright', 'Description', 'Rating'];
|
|
240
246
|
get(buf, off) {
|
|
241
247
|
const tags = [];
|
|
242
248
|
const view = new DataView(buf.buffer, off);
|
|
@@ -253,13 +259,12 @@ export class ContentDescriptionObjectState extends State {
|
|
|
253
259
|
return tags;
|
|
254
260
|
}
|
|
255
261
|
}
|
|
256
|
-
ContentDescriptionObjectState.guid = AsfGuid.ContentDescriptionObject;
|
|
257
|
-
ContentDescriptionObjectState.contentDescTags = ['Title', 'Author', 'Copyright', 'Description', 'Rating'];
|
|
258
262
|
/**
|
|
259
263
|
* 3.11 Extended Content Description Object (optional, one only)
|
|
260
264
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_11
|
|
261
265
|
*/
|
|
262
266
|
export class ExtendedContentDescriptionObjectState extends State {
|
|
267
|
+
static guid = AsfGuid.ExtendedContentDescriptionObject;
|
|
263
268
|
get(buf, off) {
|
|
264
269
|
const tags = [];
|
|
265
270
|
const view = new DataView(buf.buffer, off);
|
|
@@ -281,12 +286,12 @@ export class ExtendedContentDescriptionObjectState extends State {
|
|
|
281
286
|
return tags;
|
|
282
287
|
}
|
|
283
288
|
}
|
|
284
|
-
ExtendedContentDescriptionObjectState.guid = AsfGuid.ExtendedContentDescriptionObject;
|
|
285
289
|
/**
|
|
286
290
|
* 4.1 Extended Stream Properties Object (optional, 1 per media stream)
|
|
287
291
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/04_objects_in_the_asf_header_extension_object.html#4_1
|
|
288
292
|
*/
|
|
289
293
|
export class ExtendedStreamPropertiesObjectState extends State {
|
|
294
|
+
static guid = AsfGuid.ExtendedStreamPropertiesObject;
|
|
290
295
|
get(buf, off) {
|
|
291
296
|
const view = new DataView(buf.buffer, off);
|
|
292
297
|
return {
|
|
@@ -316,12 +321,12 @@ export class ExtendedStreamPropertiesObjectState extends State {
|
|
|
316
321
|
};
|
|
317
322
|
}
|
|
318
323
|
}
|
|
319
|
-
ExtendedStreamPropertiesObjectState.guid = AsfGuid.ExtendedStreamPropertiesObject;
|
|
320
324
|
/**
|
|
321
325
|
* 4.7 Metadata Object (optional, 0 or 1)
|
|
322
326
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/04_objects_in_the_asf_header_extension_object.html#4_7
|
|
323
327
|
*/
|
|
324
328
|
export class MetadataObjectState extends State {
|
|
329
|
+
static guid = AsfGuid.MetadataObject;
|
|
325
330
|
get(uint8Array, off) {
|
|
326
331
|
const tags = [];
|
|
327
332
|
const view = new DataView(uint8Array.buffer, off);
|
|
@@ -344,11 +349,10 @@ export class MetadataObjectState extends State {
|
|
|
344
349
|
return tags;
|
|
345
350
|
}
|
|
346
351
|
}
|
|
347
|
-
MetadataObjectState.guid = AsfGuid.MetadataObject;
|
|
348
352
|
// 4.8 Metadata Library Object (optional, 0 or 1)
|
|
349
353
|
export class MetadataLibraryObjectState extends MetadataObjectState {
|
|
354
|
+
static guid = AsfGuid.MetadataLibraryObject;
|
|
350
355
|
}
|
|
351
|
-
MetadataLibraryObjectState.guid = AsfGuid.MetadataLibraryObject;
|
|
352
356
|
/**
|
|
353
357
|
* Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/dd757977(v=vs.85).aspx
|
|
354
358
|
*/
|
|
@@ -357,6 +361,7 @@ export class WmPictureToken {
|
|
|
357
361
|
const pic = new WmPictureToken(buffer.length);
|
|
358
362
|
return pic.get(buffer, 0);
|
|
359
363
|
}
|
|
364
|
+
len;
|
|
360
365
|
constructor(len) {
|
|
361
366
|
this.len = len;
|
|
362
367
|
}
|
|
@@ -10,8 +10,8 @@ import { VorbisTagMapper } from '../ogg/vorbis/VorbisTagMapper.js';
|
|
|
10
10
|
import { InternalParserError } from '../ParseError.js';
|
|
11
11
|
import { RiffInfoTagMapper } from '../riff/RiffInfoTagMap.js';
|
|
12
12
|
export class CombinedTagMapper {
|
|
13
|
+
tagMappers = {};
|
|
13
14
|
constructor() {
|
|
14
|
-
this.tagMappers = {};
|
|
15
15
|
[
|
|
16
16
|
new ID3v1TagMapper(),
|
|
17
17
|
new ID3v22TagMapper(),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export class CommonTagMapper {
|
|
2
|
+
static maxRatingScore = 1;
|
|
2
3
|
static toIntOrNull(str) {
|
|
3
4
|
const cleaned = Number.parseInt(str, 10);
|
|
4
5
|
return Number.isNaN(cleaned) ? null : cleaned;
|
|
@@ -13,6 +14,8 @@ export class CommonTagMapper {
|
|
|
13
14
|
of: Number.parseInt(split[1], 10) || null
|
|
14
15
|
};
|
|
15
16
|
}
|
|
17
|
+
tagTypes;
|
|
18
|
+
tagMap;
|
|
16
19
|
constructor(tagTypes, tagMap) {
|
|
17
20
|
this.tagTypes = tagTypes;
|
|
18
21
|
this.tagMap = tagMap;
|
|
@@ -48,4 +51,3 @@ export class CommonTagMapper {
|
|
|
48
51
|
return;
|
|
49
52
|
}
|
|
50
53
|
}
|
|
51
|
-
CommonTagMapper.maxRatingScore = 1;
|
|
@@ -25,29 +25,30 @@ const TagPriority = [
|
|
|
25
25
|
* Responsible for triggering async updates
|
|
26
26
|
*/
|
|
27
27
|
export class MetadataCollector {
|
|
28
|
+
format = {
|
|
29
|
+
tagTypes: [],
|
|
30
|
+
trackInfo: []
|
|
31
|
+
};
|
|
32
|
+
native = {};
|
|
33
|
+
common = {
|
|
34
|
+
track: { no: null, of: null },
|
|
35
|
+
disk: { no: null, of: null },
|
|
36
|
+
movementIndex: { no: null, of: null }
|
|
37
|
+
};
|
|
38
|
+
quality = {
|
|
39
|
+
warnings: []
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Keeps track of origin priority for each mapped id
|
|
43
|
+
*/
|
|
44
|
+
commonOrigin = {};
|
|
45
|
+
/**
|
|
46
|
+
* Maps a tag type to a priority
|
|
47
|
+
*/
|
|
48
|
+
originPriority = {};
|
|
49
|
+
tagMapper = new CombinedTagMapper();
|
|
50
|
+
opts;
|
|
28
51
|
constructor(opts) {
|
|
29
|
-
this.format = {
|
|
30
|
-
tagTypes: [],
|
|
31
|
-
trackInfo: []
|
|
32
|
-
};
|
|
33
|
-
this.native = {};
|
|
34
|
-
this.common = {
|
|
35
|
-
track: { no: null, of: null },
|
|
36
|
-
disk: { no: null, of: null },
|
|
37
|
-
movementIndex: { no: null, of: null }
|
|
38
|
-
};
|
|
39
|
-
this.quality = {
|
|
40
|
-
warnings: []
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* Keeps track of origin priority for each mapped id
|
|
44
|
-
*/
|
|
45
|
-
this.commonOrigin = {};
|
|
46
|
-
/**
|
|
47
|
-
* Maps a tag type to a priority
|
|
48
|
-
*/
|
|
49
|
-
this.originPriority = {};
|
|
50
|
-
this.tagMapper = new CombinedTagMapper();
|
|
51
52
|
this.opts = opts;
|
|
52
53
|
let priority = 1;
|
|
53
54
|
for (const tagType of TagPriority) {
|
|
@@ -117,12 +118,15 @@ export class MetadataCollector {
|
|
|
117
118
|
});
|
|
118
119
|
case 'totaltracks':
|
|
119
120
|
this.common.track.of = CommonTagMapper.toIntOrNull(tag.value);
|
|
121
|
+
this.notifyCommonTag({ id: 'track', value: { ...this.common.track } });
|
|
120
122
|
return;
|
|
121
123
|
case 'totaldiscs':
|
|
122
124
|
this.common.disk.of = CommonTagMapper.toIntOrNull(tag.value);
|
|
125
|
+
this.notifyCommonTag({ id: 'disk', value: { ...this.common.disk } });
|
|
123
126
|
return;
|
|
124
127
|
case 'movementTotal':
|
|
125
128
|
this.common.movementIndex.of = CommonTagMapper.toIntOrNull(tag.value);
|
|
129
|
+
this.notifyCommonTag({ id: 'movementIndex', value: { ...this.common.movementIndex } });
|
|
126
130
|
return;
|
|
127
131
|
case 'track':
|
|
128
132
|
case 'disk':
|
|
@@ -130,6 +134,7 @@ export class MetadataCollector {
|
|
|
130
134
|
const of = this.common[tag.id].of; // store of value, maybe maybe overwritten
|
|
131
135
|
this.common[tag.id] = CommonTagMapper.normalizeTrack(tag.value);
|
|
132
136
|
this.common[tag.id].of = of != null ? of : this.common[tag.id].of;
|
|
137
|
+
this.notifyCommonTag({ id: tag.id, value: { ...this.common[tag.id] } });
|
|
133
138
|
return;
|
|
134
139
|
}
|
|
135
140
|
case 'bpm':
|
|
@@ -300,7 +305,7 @@ export class MetadataCollector {
|
|
|
300
305
|
this.common[tag.id].push(tag.value);
|
|
301
306
|
}
|
|
302
307
|
else {
|
|
303
|
-
debug(`Ignore duplicate value: ${tagType}.${tag.id} = ${tag.value}`);
|
|
308
|
+
return debug(`Ignore duplicate value: ${tagType}.${tag.id} = ${tag.value}`);
|
|
304
309
|
}
|
|
305
310
|
// no effect? this.commonOrigin[tag.id] = prio1;
|
|
306
311
|
}
|
|
@@ -312,10 +317,12 @@ export class MetadataCollector {
|
|
|
312
317
|
return debug(`Ignore native tag (list): ${tagType}.${tag.id} = ${tag.value}`);
|
|
313
318
|
}
|
|
314
319
|
}
|
|
320
|
+
this.notifyCommonTag(tag);
|
|
321
|
+
}
|
|
322
|
+
notifyCommonTag(tag) {
|
|
315
323
|
if (this.opts?.observer) {
|
|
316
324
|
this.opts.observer({ metadata: this, tag: { type: 'common', id: tag.id, value: tag.value } });
|
|
317
325
|
}
|
|
318
|
-
// ToDo: trigger metadata event
|
|
319
326
|
}
|
|
320
327
|
}
|
|
321
328
|
export function joinArtists(artists) {
|