music-metadata 10.0.1 → 10.2.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/LICENSE.txt +9 -9
- package/README.md +535 -520
- package/lib/ParserFactory.d.ts +20 -28
- package/lib/ParserFactory.js +204 -207
- package/lib/aiff/AiffParser.js +20 -19
- package/lib/aiff/AiffToken.d.ts +14 -2
- package/lib/aiff/AiffToken.js +12 -0
- package/lib/apev2/APEv2Parser.d.ts +4 -4
- package/lib/apev2/APEv2Parser.js +12 -10
- package/lib/apev2/APEv2Token.d.ts +2 -4
- package/lib/apev2/APEv2Token.js +0 -3
- package/lib/asf/AsfObject.d.ts +7 -16
- package/lib/asf/AsfObject.js +8 -34
- package/lib/asf/AsfParser.js +17 -10
- package/lib/asf/AsfTagMapper.d.ts +1 -1
- package/lib/asf/AsfTagMapper.js +3 -2
- package/lib/asf/AsfUtil.d.ts +3 -11
- package/lib/asf/AsfUtil.js +29 -30
- package/lib/asf/GUID.js +6 -9
- package/lib/common/BasicParser.d.ts +4 -4
- package/lib/common/BasicParser.js +6 -0
- package/lib/common/CaseInsensitiveTagMap.d.ts +1 -1
- package/lib/common/CombinedTagMapper.d.ts +5 -5
- package/lib/common/CombinedTagMapper.js +1 -1
- package/lib/common/FourCC.d.ts +1 -1
- package/lib/common/GenericTagMapper.d.ts +8 -8
- package/lib/common/GenericTagMapper.js +4 -4
- package/lib/common/GenericTagTypes.d.ts +5 -4
- package/lib/common/GenericTagTypes.js +2 -2
- package/lib/common/MetadataCollector.d.ts +9 -9
- package/lib/common/MetadataCollector.js +22 -17
- package/lib/common/RandomFileReader.d.ts +1 -1
- package/lib/common/RandomFileReader.js +1 -1
- package/lib/common/RandomUint8ArrayReader.d.ts +1 -1
- package/lib/common/Util.d.ts +2 -6
- package/lib/common/Util.js +9 -11
- package/lib/core.d.ts +7 -9
- package/lib/core.js +10 -7
- package/lib/dsdiff/DsdiffParser.js +26 -14
- package/lib/dsdiff/DsdiffToken.d.ts +2 -2
- package/lib/dsdiff/DsdiffToken.js +1 -0
- package/lib/dsf/DsfChunk.js +1 -0
- package/lib/dsf/DsfParser.js +4 -2
- package/lib/ebml/EbmlIterator.d.ts +52 -0
- package/lib/ebml/EbmlIterator.js +220 -0
- package/lib/ebml/types.d.ts +36 -0
- package/lib/ebml/types.js +10 -0
- package/lib/flac/FlacParser.d.ts +3 -3
- package/lib/flac/FlacParser.js +9 -12
- package/lib/id3v1/ID3v1Parser.d.ts +1 -1
- package/lib/id3v1/ID3v1Parser.js +7 -4
- package/lib/id3v2/AbstractID3Parser.d.ts +1 -1
- package/lib/id3v2/AbstractID3Parser.js +2 -1
- package/lib/id3v2/FrameParser.d.ts +28 -3
- package/lib/id3v2/FrameParser.js +50 -28
- package/lib/id3v2/ID3v22TagMapper.d.ts +1 -1
- package/lib/id3v2/ID3v24TagMapper.d.ts +2 -1
- package/lib/id3v2/ID3v24TagMapper.js +23 -16
- package/lib/id3v2/ID3v2Parser.d.ts +2 -2
- package/lib/id3v2/ID3v2Parser.js +18 -8
- package/lib/iff/index.js +1 -0
- package/lib/index.d.ts +5 -6
- package/lib/index.js +7 -8
- package/lib/lyrics3/Lyrics3.d.ts +1 -1
- package/lib/lyrics3/Lyrics3.js +1 -1
- package/lib/matroska/MatroskaDtd.d.ts +2 -2
- package/lib/matroska/MatroskaDtd.js +247 -239
- package/lib/matroska/MatroskaParser.d.ts +10 -24
- package/lib/matroska/MatroskaParser.js +120 -205
- package/lib/matroska/types.d.ts +12 -46
- package/lib/matroska/types.js +0 -9
- package/lib/mp4/Atom.d.ts +3 -3
- package/lib/mp4/Atom.js +4 -7
- package/lib/mp4/AtomToken.js +2 -1
- package/lib/mp4/MP4Parser.js +29 -20
- package/lib/mp4/MP4TagMapper.d.ts +2 -2
- package/lib/mp4/MP4TagMapper.js +1 -1
- package/lib/mpeg/ExtendedLameHeader.d.ts +4 -4
- package/lib/mpeg/ExtendedLameHeader.js +2 -1
- package/lib/mpeg/MpegParser.d.ts +1 -1
- package/lib/mpeg/MpegParser.js +145 -96
- package/lib/mpeg/ReplayGainDataFormat.d.ts +1 -1
- package/lib/mpeg/ReplayGainDataFormat.js +1 -0
- package/lib/mpeg/XingTag.d.ts +4 -4
- package/lib/mpeg/XingTag.js +5 -4
- package/lib/musepack/index.js +1 -0
- package/lib/musepack/sv7/BitReader.js +14 -17
- package/lib/musepack/sv7/MpcSv7Parser.js +6 -1
- package/lib/musepack/sv7/StreamVersion7.js +1 -0
- package/lib/musepack/sv8/MpcSv8Parser.js +6 -2
- package/lib/musepack/sv8/StreamVersion8.d.ts +1 -1
- package/lib/musepack/sv8/StreamVersion8.js +1 -0
- package/lib/ogg/Ogg.d.ts +1 -1
- package/lib/ogg/Ogg.js +1 -0
- package/lib/ogg/OggParser.d.ts +3 -3
- package/lib/ogg/OggParser.js +25 -16
- package/lib/ogg/opus/Opus.d.ts +1 -1
- package/lib/ogg/opus/Opus.js +1 -0
- package/lib/ogg/opus/OpusParser.d.ts +4 -4
- package/lib/ogg/opus/OpusParser.js +2 -0
- package/lib/ogg/speex/Speex.js +1 -0
- package/lib/ogg/speex/SpeexParser.d.ts +4 -4
- package/lib/ogg/speex/SpeexParser.js +1 -0
- package/lib/ogg/theora/Theora.js +1 -0
- package/lib/ogg/theora/TheoraParser.d.ts +4 -4
- package/lib/ogg/theora/TheoraParser.js +1 -0
- package/lib/ogg/vorbis/Vorbis.d.ts +3 -3
- package/lib/ogg/vorbis/Vorbis.js +22 -11
- package/lib/ogg/vorbis/VorbisDecoder.d.ts +1 -1
- package/lib/ogg/vorbis/VorbisDecoder.js +1 -0
- package/lib/ogg/vorbis/VorbisParser.d.ts +4 -4
- package/lib/ogg/vorbis/VorbisParser.js +3 -2
- package/lib/ogg/vorbis/VorbisTagMapper.d.ts +2 -2
- package/lib/ogg/vorbis/VorbisTagMapper.js +2 -2
- package/lib/riff/RiffChunk.d.ts +3 -3
- package/lib/riff/RiffChunk.js +1 -0
- package/lib/riff/RiffInfoTagMap.d.ts +1 -1
- package/lib/type.d.ts +44 -25
- package/lib/wav/BwfChunk.js +1 -0
- package/lib/wav/WaveChunk.d.ts +1 -1
- package/lib/wav/WaveChunk.js +1 -0
- package/lib/wav/WaveParser.js +27 -17
- package/lib/wavpack/WavPackParser.d.ts +1 -1
- package/lib/wavpack/WavPackParser.js +22 -13
- package/lib/wavpack/WavPackToken.d.ts +13 -17
- package/lib/wavpack/WavPackToken.js +22 -23
- package/package.json +139 -150
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { Float32_BE, Float64_BE, StringType, UINT8 } from 'token-types';
|
|
2
1
|
import initDebug from 'debug';
|
|
3
2
|
import { BasicParser } from '../common/BasicParser.js';
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
3
|
+
import { matroskaDtd } from './MatroskaDtd.js';
|
|
4
|
+
import { TargetType, TrackType } from './types.js';
|
|
5
|
+
import { EbmlIterator, ParseAction } from '../ebml/EbmlIterator.js';
|
|
7
6
|
const debug = initDebug('music-metadata:parser:matroska');
|
|
8
7
|
/**
|
|
9
8
|
* Extensible Binary Meta Language (EBML) parser
|
|
@@ -14,17 +13,13 @@ const debug = initDebug('music-metadata:parser:matroska');
|
|
|
14
13
|
*/
|
|
15
14
|
export class MatroskaParser extends BasicParser {
|
|
16
15
|
constructor() {
|
|
17
|
-
super();
|
|
18
|
-
this.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
this.
|
|
24
|
-
this.parserMap.set(DataType.binary, e => this.readBuffer(e));
|
|
25
|
-
this.parserMap.set(DataType.uid, async (e) => this.readBuffer(e));
|
|
26
|
-
this.parserMap.set(DataType.bool, e => this.readFlag(e));
|
|
27
|
-
this.parserMap.set(DataType.float, e => this.readFloat(e));
|
|
16
|
+
super(...arguments);
|
|
17
|
+
this.seekHeadOffset = 0;
|
|
18
|
+
/**
|
|
19
|
+
* Use index to skip multiple segment/cluster elements at once.
|
|
20
|
+
* Significant performance impact
|
|
21
|
+
*/
|
|
22
|
+
this.flagUseIndexToSkipClusters = false;
|
|
28
23
|
}
|
|
29
24
|
/**
|
|
30
25
|
* Initialize parser with output (metadata), input (tokenizer) & parsing options (options).
|
|
@@ -34,207 +29,127 @@ export class MatroskaParser extends BasicParser {
|
|
|
34
29
|
*/
|
|
35
30
|
init(metadata, tokenizer, options) {
|
|
36
31
|
super.init(metadata, tokenizer, options);
|
|
32
|
+
this.flagUseIndexToSkipClusters = options.mkvUseIndex ?? false;
|
|
37
33
|
return this;
|
|
38
34
|
}
|
|
39
35
|
async parse() {
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
flagDefault: entry.flagDefault,
|
|
61
|
-
flagLacing: entry.flagLacing,
|
|
62
|
-
flagEnabled: entry.flagEnabled,
|
|
63
|
-
language: entry.language,
|
|
64
|
-
name: entry.name,
|
|
65
|
-
type: entry.trackType,
|
|
66
|
-
audio: entry.audio,
|
|
67
|
-
video: entry.video
|
|
68
|
-
};
|
|
69
|
-
this.metadata.addStreamInfo(stream);
|
|
70
|
-
});
|
|
71
|
-
const audioTrack = audioTracks.entries
|
|
72
|
-
.filter(entry => {
|
|
73
|
-
return entry.trackType === TrackType.audio.valueOf();
|
|
74
|
-
})
|
|
75
|
-
.reduce((acc, cur) => {
|
|
76
|
-
if (!acc) {
|
|
77
|
-
return cur;
|
|
78
|
-
}
|
|
79
|
-
if (!acc.flagDefault && cur.flagDefault) {
|
|
80
|
-
return cur;
|
|
81
|
-
}
|
|
82
|
-
if (cur.trackNumber && cur.trackNumber < acc.trackNumber) {
|
|
83
|
-
return cur;
|
|
84
|
-
}
|
|
85
|
-
return acc;
|
|
86
|
-
}, null);
|
|
87
|
-
if (audioTrack) {
|
|
88
|
-
this.metadata.setFormat('codec', audioTrack.codecID.replace('A_', ''));
|
|
89
|
-
this.metadata.setFormat('sampleRate', audioTrack.audio.samplingFrequency);
|
|
90
|
-
this.metadata.setFormat('numberOfChannels', audioTrack.audio.channels);
|
|
91
|
-
}
|
|
92
|
-
if (matroska.segment.tags) {
|
|
93
|
-
await Promise.all(matroska.segment.tags.tag.map(async (tag) => {
|
|
94
|
-
const target = tag.target;
|
|
95
|
-
const targetType = (target === null || target === void 0 ? void 0 : target.targetTypeValue) ? TargetType[target.targetTypeValue] : ((target === null || target === void 0 ? void 0 : target.targetType) ? target.targetType : 'track');
|
|
96
|
-
await Promise.all(tag.simpleTags.map(async (simpleTag) => {
|
|
97
|
-
const value = simpleTag.string ? simpleTag.string : simpleTag.binary;
|
|
98
|
-
await this.addTag(`${targetType}:${simpleTag.name}`, value);
|
|
99
|
-
}));
|
|
100
|
-
}));
|
|
101
|
-
}
|
|
102
|
-
if (matroska.segment.attachments) {
|
|
103
|
-
await Promise.all(matroska.segment.attachments.attachedFiles
|
|
104
|
-
.filter(file => file.mimeType.startsWith('image/'))
|
|
105
|
-
.map(file => this.addTag('picture', {
|
|
106
|
-
data: file.data,
|
|
107
|
-
format: file.mimeType,
|
|
108
|
-
description: file.description,
|
|
109
|
-
name: file.name
|
|
110
|
-
})));
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
async parseContainer(container, posDone, path) {
|
|
116
|
-
const tree = {};
|
|
117
|
-
while (this.tokenizer.position < posDone) {
|
|
118
|
-
let element;
|
|
119
|
-
try {
|
|
120
|
-
element = await this.readElement();
|
|
121
|
-
}
|
|
122
|
-
catch (error) {
|
|
123
|
-
if (error.message === 'End-Of-Stream') {
|
|
124
|
-
break;
|
|
125
|
-
}
|
|
126
|
-
throw error;
|
|
127
|
-
}
|
|
128
|
-
const type = container[element.id];
|
|
129
|
-
if (type) {
|
|
130
|
-
debug(`Element: name=${type.name}, container=${!!type.container}`);
|
|
131
|
-
if (type.container) {
|
|
132
|
-
const res = await this.parseContainer(type.container, element.len >= 0 ? this.tokenizer.position + element.len : -1, path.concat([type.name]));
|
|
133
|
-
if (type.multiple) {
|
|
134
|
-
if (!tree[type.name]) {
|
|
135
|
-
tree[type.name] = [];
|
|
36
|
+
const containerSize = this.tokenizer.fileInfo.size ?? Number.MAX_SAFE_INTEGER;
|
|
37
|
+
const matroskaIterator = new EbmlIterator(this.tokenizer);
|
|
38
|
+
debug('Initializing DTD end MatroskaIterator');
|
|
39
|
+
await matroskaIterator.iterate(matroskaDtd, containerSize, {
|
|
40
|
+
startNext: (element) => {
|
|
41
|
+
switch (element.id) {
|
|
42
|
+
// case 0x1f43b675: // cluster
|
|
43
|
+
case 0x1c53bb6b: // Cueing Data
|
|
44
|
+
debug(`Skip element: name=${element.name}, id=0x${element.id.toString(16)}`);
|
|
45
|
+
return ParseAction.IgnoreElement;
|
|
46
|
+
case 0x1f43b675: // cluster
|
|
47
|
+
if (this.flagUseIndexToSkipClusters && this.seekHead) {
|
|
48
|
+
const index = this.seekHead.seek.find(index => index.position + this.seekHeadOffset > this.tokenizer.position);
|
|
49
|
+
if (index) {
|
|
50
|
+
// Go to next index position
|
|
51
|
+
const ignoreSize = index.position + this.seekHeadOffset - this.tokenizer.position;
|
|
52
|
+
debug(`Use index to go to next position, ignoring ${ignoreSize} bytes`);
|
|
53
|
+
this.tokenizer.ignore(ignoreSize);
|
|
54
|
+
return ParseAction.SkipElement;
|
|
55
|
+
}
|
|
136
56
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
tree[type.name] = res;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
tree[type.name] = await this.parserMap.get(type.value)(element);
|
|
57
|
+
return ParseAction.IgnoreElement;
|
|
58
|
+
default:
|
|
59
|
+
return ParseAction.ReadNext;
|
|
145
60
|
}
|
|
146
|
-
}
|
|
147
|
-
|
|
61
|
+
},
|
|
62
|
+
elementValue: async (element, value, offset) => {
|
|
63
|
+
debug(`Received: name=${element.name}, value=${value}`);
|
|
148
64
|
switch (element.id) {
|
|
149
|
-
case
|
|
150
|
-
this.
|
|
151
|
-
|
|
65
|
+
case 0x4282: // docType
|
|
66
|
+
this.metadata.setFormat('container', `EBML/${value}`);
|
|
67
|
+
break;
|
|
68
|
+
case 0x114d9b74:
|
|
69
|
+
this.seekHead = value;
|
|
70
|
+
this.seekHeadOffset = offset;
|
|
71
|
+
break;
|
|
72
|
+
case 0x1549a966:
|
|
73
|
+
{ // Info (Segment Information)
|
|
74
|
+
const info = value;
|
|
75
|
+
const timecodeScale = info.timecodeScale ? info.timecodeScale : 1000000;
|
|
76
|
+
if (typeof info.duration === 'number') {
|
|
77
|
+
const duration = info.duration * timecodeScale / 1000000000;
|
|
78
|
+
await this.addTag('segment:title', info.title);
|
|
79
|
+
this.metadata.setFormat('duration', Number(duration));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
case 0x1654ae6b:
|
|
84
|
+
{ // tracks
|
|
85
|
+
const audioTracks = value;
|
|
86
|
+
if (audioTracks?.entries) {
|
|
87
|
+
audioTracks.entries.forEach(entry => {
|
|
88
|
+
const stream = {
|
|
89
|
+
codecName: entry.codecID.replace('A_', '').replace('V_', ''),
|
|
90
|
+
codecSettings: entry.codecSettings,
|
|
91
|
+
flagDefault: entry.flagDefault,
|
|
92
|
+
flagLacing: entry.flagLacing,
|
|
93
|
+
flagEnabled: entry.flagEnabled,
|
|
94
|
+
language: entry.language,
|
|
95
|
+
name: entry.name,
|
|
96
|
+
type: entry.trackType,
|
|
97
|
+
audio: entry.audio,
|
|
98
|
+
video: entry.video
|
|
99
|
+
};
|
|
100
|
+
this.metadata.addStreamInfo(stream);
|
|
101
|
+
});
|
|
102
|
+
const audioTrack = audioTracks.entries
|
|
103
|
+
.filter(entry => entry.trackType === TrackType.audio)
|
|
104
|
+
.reduce((acc, cur) => {
|
|
105
|
+
if (!acc)
|
|
106
|
+
return cur;
|
|
107
|
+
if (cur.flagDefault && !acc.flagDefault)
|
|
108
|
+
return cur;
|
|
109
|
+
if (cur.trackNumber < acc.trackNumber)
|
|
110
|
+
return cur;
|
|
111
|
+
return acc;
|
|
112
|
+
}, null);
|
|
113
|
+
if (audioTrack) {
|
|
114
|
+
this.metadata.setFormat('codec', audioTrack.codecID.replace('A_', ''));
|
|
115
|
+
this.metadata.setFormat('sampleRate', audioTrack.audio.samplingFrequency);
|
|
116
|
+
this.metadata.setFormat('numberOfChannels', audioTrack.audio.channels);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
case 0x1254c367:
|
|
122
|
+
{ // tags
|
|
123
|
+
const tags = value;
|
|
124
|
+
await Promise.all(tags.tag.map(async (tag) => {
|
|
125
|
+
const target = tag.target;
|
|
126
|
+
const targetType = target?.targetTypeValue ? TargetType[target.targetTypeValue] : (target?.targetType ? target.targetType : 'track');
|
|
127
|
+
await Promise.all(tag.simpleTags.map(async (simpleTag) => {
|
|
128
|
+
const value = simpleTag.string ? simpleTag.string : simpleTag.binary;
|
|
129
|
+
await this.addTag(`${targetType}:${simpleTag.name}`, value);
|
|
130
|
+
}));
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
case 0x1941a469:
|
|
135
|
+
{ // attachments
|
|
136
|
+
const attachments = value;
|
|
137
|
+
await Promise.all(attachments.attachedFiles
|
|
138
|
+
.filter(file => file.mimeType.startsWith('image/'))
|
|
139
|
+
.map(file => this.addTag('picture', {
|
|
140
|
+
data: file.data,
|
|
141
|
+
format: file.mimeType,
|
|
142
|
+
description: file.description,
|
|
143
|
+
name: file.name
|
|
144
|
+
})));
|
|
145
|
+
}
|
|
152
146
|
break;
|
|
153
|
-
default:
|
|
154
|
-
debug(`parseEbml: path=${path.join('/')}, unknown element: id=${element.id.toString(16)}`);
|
|
155
|
-
this.padding += element.len;
|
|
156
|
-
await this.tokenizer.ignore(element.len);
|
|
157
147
|
}
|
|
158
148
|
}
|
|
159
|
-
}
|
|
160
|
-
return tree;
|
|
161
|
-
}
|
|
162
|
-
async readVintData(maxLength) {
|
|
163
|
-
const msb = await this.tokenizer.peekNumber(UINT8);
|
|
164
|
-
let mask = 0x80;
|
|
165
|
-
let oc = 1;
|
|
166
|
-
// Calculate VINT_WIDTH
|
|
167
|
-
while ((msb & mask) === 0) {
|
|
168
|
-
if (oc > maxLength) {
|
|
169
|
-
throw new Error('VINT value exceeding maximum size');
|
|
170
|
-
}
|
|
171
|
-
++oc;
|
|
172
|
-
mask >>= 1;
|
|
173
|
-
}
|
|
174
|
-
const id = new Uint8Array(oc);
|
|
175
|
-
await this.tokenizer.readBuffer(id);
|
|
176
|
-
return id;
|
|
177
|
-
}
|
|
178
|
-
async readElement() {
|
|
179
|
-
const id = await this.readVintData(this.ebmlMaxIDLength);
|
|
180
|
-
const lenField = await this.readVintData(this.ebmlMaxSizeLength);
|
|
181
|
-
lenField[0] ^= 0x80 >> (lenField.length - 1);
|
|
182
|
-
return {
|
|
183
|
-
id: MatroskaParser.readUIntBE(id, id.length),
|
|
184
|
-
len: MatroskaParser.readUIntBE(lenField, lenField.length)
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
async readFloat(e) {
|
|
188
|
-
switch (e.len) {
|
|
189
|
-
case 0:
|
|
190
|
-
return 0.0;
|
|
191
|
-
case 4:
|
|
192
|
-
return this.tokenizer.readNumber(Float32_BE);
|
|
193
|
-
case 8:
|
|
194
|
-
return this.tokenizer.readNumber(Float64_BE);
|
|
195
|
-
case 10:
|
|
196
|
-
return this.tokenizer.readNumber(Float64_BE);
|
|
197
|
-
default:
|
|
198
|
-
throw new Error(`Invalid IEEE-754 float length: ${e.len}`);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
async readFlag(e) {
|
|
202
|
-
return (await this.readUint(e)) === 1;
|
|
203
|
-
}
|
|
204
|
-
async readUint(e) {
|
|
205
|
-
const buf = await this.readBuffer(e);
|
|
206
|
-
return MatroskaParser.readUIntBE(buf, e.len);
|
|
207
|
-
}
|
|
208
|
-
async readString(e) {
|
|
209
|
-
const rawString = await this.tokenizer.readToken(new StringType(e.len, 'utf-8'));
|
|
210
|
-
return rawString.replace(/\x00.*$/g, '');
|
|
211
|
-
}
|
|
212
|
-
async readBuffer(e) {
|
|
213
|
-
const buf = new Uint8Array(e.len);
|
|
214
|
-
await this.tokenizer.readBuffer(buf);
|
|
215
|
-
return buf;
|
|
149
|
+
});
|
|
216
150
|
}
|
|
217
151
|
async addTag(tagId, value) {
|
|
218
152
|
await this.metadata.addTag('matroska', tagId, value);
|
|
219
153
|
}
|
|
220
|
-
static readUIntBE(buf, len) {
|
|
221
|
-
return Number(MatroskaParser.readUIntBeAsBigInt(buf, len));
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Reeds an unsigned integer from a big endian buffer of length `len`
|
|
225
|
-
* @param buf Buffer to decode from
|
|
226
|
-
* @param len Number of bytes
|
|
227
|
-
* @private
|
|
228
|
-
*/
|
|
229
|
-
static readUIntBeAsBigInt(buf, len) {
|
|
230
|
-
const normalizedNumber = new Uint8Array(8);
|
|
231
|
-
const cleanNumber = buf.subarray(0, len);
|
|
232
|
-
try {
|
|
233
|
-
normalizedNumber.set(cleanNumber, 8 - len);
|
|
234
|
-
return Token.UINT64_BE.get(normalizedNumber, 0);
|
|
235
|
-
}
|
|
236
|
-
catch (error) {
|
|
237
|
-
return BigInt(-1);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
154
|
}
|
|
155
|
+
//# sourceMappingURL=MatroskaParser.js.map
|
package/lib/matroska/types.d.ts
CHANGED
|
@@ -1,33 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export declare enum DataType {
|
|
6
|
-
'string' = 0,
|
|
7
|
-
uint = 1,
|
|
8
|
-
uid = 2,
|
|
9
|
-
bool = 3,
|
|
10
|
-
binary = 4,
|
|
11
|
-
float = 5
|
|
12
|
-
}
|
|
13
|
-
export interface IElementType<T> {
|
|
14
|
-
readonly name: string;
|
|
15
|
-
readonly value?: DataType;
|
|
16
|
-
readonly container?: IContainerType;
|
|
17
|
-
readonly multiple?: boolean;
|
|
18
|
-
}
|
|
19
|
-
export interface IContainerType {
|
|
20
|
-
[id: number]: IElementType<string | number | boolean | Uint8Array>;
|
|
21
|
-
}
|
|
22
|
-
export interface ITree {
|
|
23
|
-
[name: string]: string | number | boolean | Uint8Array | ITree | ITree[];
|
|
1
|
+
import type { IEbmlDoc } from '../ebml/types.js';
|
|
2
|
+
export interface ISeek {
|
|
3
|
+
id: Uint8Array;
|
|
4
|
+
position: number;
|
|
24
5
|
}
|
|
25
6
|
export interface ISeekHead {
|
|
26
|
-
|
|
27
|
-
position?: number;
|
|
28
|
-
}
|
|
29
|
-
export interface IMetaSeekInformation {
|
|
30
|
-
seekHeads: ISeekHead[];
|
|
7
|
+
seek: ISeek[];
|
|
31
8
|
}
|
|
32
9
|
export interface ISegmentInformation {
|
|
33
10
|
uid?: Uint8Array;
|
|
@@ -40,9 +17,9 @@ export interface ISegmentInformation {
|
|
|
40
17
|
}
|
|
41
18
|
export interface ITrackEntry {
|
|
42
19
|
uid?: Uint8Array;
|
|
43
|
-
trackNumber
|
|
20
|
+
trackNumber: number;
|
|
44
21
|
trackType?: TrackType;
|
|
45
|
-
audio
|
|
22
|
+
audio: ITrackAudio;
|
|
46
23
|
video?: ITrackVideo;
|
|
47
24
|
flagEnabled?: boolean;
|
|
48
25
|
flagDefault?: boolean;
|
|
@@ -51,7 +28,7 @@ export interface ITrackEntry {
|
|
|
51
28
|
trackTimecodeScale?: number;
|
|
52
29
|
name?: string;
|
|
53
30
|
language?: string;
|
|
54
|
-
codecID
|
|
31
|
+
codecID: string;
|
|
55
32
|
codecPrivate?: Uint8Array;
|
|
56
33
|
codecName?: string;
|
|
57
34
|
codecSettings?: string;
|
|
@@ -121,6 +98,7 @@ export declare enum TrackType {
|
|
|
121
98
|
button = 18,
|
|
122
99
|
control = 32
|
|
123
100
|
}
|
|
101
|
+
export type TrackTypeKey = keyof TrackType;
|
|
124
102
|
export interface ITarget {
|
|
125
103
|
trackUID?: Uint8Array;
|
|
126
104
|
chapterUID?: Uint8Array;
|
|
@@ -146,29 +124,17 @@ export interface IAttachmedFile {
|
|
|
146
124
|
uid: string;
|
|
147
125
|
}
|
|
148
126
|
export interface IAttachments {
|
|
149
|
-
attachedFiles
|
|
127
|
+
attachedFiles: IAttachmedFile[];
|
|
150
128
|
}
|
|
151
129
|
export interface IMatroskaSegment {
|
|
152
|
-
metaSeekInfo?:
|
|
153
|
-
seekHeads?:
|
|
130
|
+
metaSeekInfo?: ISeekHead;
|
|
131
|
+
seekHeads?: ISeek[];
|
|
154
132
|
info?: ISegmentInformation;
|
|
155
133
|
tracks?: ITrackElement;
|
|
156
134
|
tags?: ITags;
|
|
157
135
|
cues?: ICuePoint[];
|
|
158
136
|
attachments?: IAttachments;
|
|
159
137
|
}
|
|
160
|
-
export interface IEbmlElements {
|
|
161
|
-
version?: number;
|
|
162
|
-
readVersion?: number;
|
|
163
|
-
maxIDWidth?: number;
|
|
164
|
-
maxSizeWidth?: number;
|
|
165
|
-
docType?: string;
|
|
166
|
-
docTypeVersion?: number;
|
|
167
|
-
docTypeReadVersion?: number;
|
|
168
|
-
}
|
|
169
|
-
export interface IEbmlDoc {
|
|
170
|
-
ebml: IEbmlElements;
|
|
171
|
-
}
|
|
172
138
|
export interface IMatroskaDoc extends IEbmlDoc {
|
|
173
139
|
segment: IMatroskaSegment;
|
|
174
140
|
}
|
package/lib/matroska/types.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
export var DataType;
|
|
2
|
-
(function (DataType) {
|
|
3
|
-
DataType[DataType["string"] = 0] = "string";
|
|
4
|
-
DataType[DataType["uint"] = 1] = "uint";
|
|
5
|
-
DataType[DataType["uid"] = 2] = "uid";
|
|
6
|
-
DataType[DataType["bool"] = 3] = "bool";
|
|
7
|
-
DataType[DataType["binary"] = 4] = "binary";
|
|
8
|
-
DataType[DataType["float"] = 5] = "float";
|
|
9
|
-
})(DataType || (DataType = {}));
|
|
10
1
|
export var TargetType;
|
|
11
2
|
(function (TargetType) {
|
|
12
3
|
TargetType[TargetType["shot"] = 10] = "shot";
|
package/lib/mp4/Atom.d.ts
CHANGED
|
@@ -4,11 +4,11 @@ export type AtomDataHandler = (atom: Atom, remaining: number) => Promise<void>;
|
|
|
4
4
|
export declare class Atom {
|
|
5
5
|
readonly header: AtomToken.IAtomHeader;
|
|
6
6
|
extended: boolean;
|
|
7
|
-
readonly parent: Atom;
|
|
8
|
-
static readAtom(tokenizer: ITokenizer, dataHandler: AtomDataHandler, parent: Atom, remaining: number): Promise<Atom>;
|
|
7
|
+
readonly parent: Atom | null;
|
|
8
|
+
static readAtom(tokenizer: ITokenizer, dataHandler: AtomDataHandler, parent: Atom | null, remaining: number): Promise<Atom>;
|
|
9
9
|
readonly children: Atom[];
|
|
10
10
|
readonly atomPath: string;
|
|
11
|
-
constructor(header: AtomToken.IAtomHeader, extended: boolean, parent: Atom);
|
|
11
|
+
constructor(header: AtomToken.IAtomHeader, extended: boolean, parent: Atom | null);
|
|
12
12
|
getHeaderLength(): number;
|
|
13
13
|
getPayloadLength(remaining: number): number;
|
|
14
14
|
readAtoms(tokenizer: ITokenizer, dataHandler: AtomDataHandler, size: number): Promise<void>;
|
package/lib/mp4/Atom.js
CHANGED
|
@@ -23,7 +23,7 @@ export class Atom {
|
|
|
23
23
|
this.extended = extended;
|
|
24
24
|
this.parent = parent;
|
|
25
25
|
this.children = [];
|
|
26
|
-
this.atomPath = (this.parent ? this.parent.atomPath
|
|
26
|
+
this.atomPath = (this.parent ? `${this.parent.atomPath}.` : '') + this.header.name;
|
|
27
27
|
}
|
|
28
28
|
getHeaderLength() {
|
|
29
29
|
return this.extended ? 16 : 8;
|
|
@@ -51,19 +51,16 @@ export class Atom {
|
|
|
51
51
|
case 'ilst':
|
|
52
52
|
case 'tref':
|
|
53
53
|
return this.readAtoms(tokenizer, dataHandler, this.getPayloadLength(remaining));
|
|
54
|
-
case 'meta': // Metadata Atom, ref: https://developer.apple.com/library/content/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW8
|
|
54
|
+
case 'meta': { // Metadata Atom, ref: https://developer.apple.com/library/content/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW8
|
|
55
55
|
// meta has 4 bytes of padding, ignore
|
|
56
56
|
const peekHeader = await tokenizer.peekToken(Header);
|
|
57
57
|
const paddingLength = peekHeader.name === 'hdlr' ? 0 : 4;
|
|
58
58
|
await tokenizer.ignore(paddingLength);
|
|
59
59
|
return this.readAtoms(tokenizer, dataHandler, this.getPayloadLength(remaining) - paddingLength);
|
|
60
|
-
|
|
61
|
-
case 'mvhd': // 'movie' => 'mvhd': movie header atom; child of Movie Atom
|
|
62
|
-
case 'tkhd':
|
|
63
|
-
case 'stsz':
|
|
64
|
-
case 'mdat':
|
|
60
|
+
}
|
|
65
61
|
default:
|
|
66
62
|
return dataHandler(this, remaining);
|
|
67
63
|
}
|
|
68
64
|
}
|
|
69
65
|
}
|
|
66
|
+
//# sourceMappingURL=Atom.js.map
|
package/lib/mp4/AtomToken.js
CHANGED
|
@@ -68,7 +68,7 @@ export class FixedLengthAtom {
|
|
|
68
68
|
if (len < expLen) {
|
|
69
69
|
throw new Error(`Atom ${atomId} expected to be ${expLen}, but specifies ${len} bytes long.`);
|
|
70
70
|
}
|
|
71
|
-
|
|
71
|
+
if (len > expLen) {
|
|
72
72
|
debug(`Warning: atom ${atomId} expected to be ${expLen}, but was actually ${len} bytes long.`);
|
|
73
73
|
}
|
|
74
74
|
}
|
|
@@ -390,3 +390,4 @@ function readTokenTable(buf, token, off, remainingLen, numberOfEntries) {
|
|
|
390
390
|
}
|
|
391
391
|
return entries;
|
|
392
392
|
}
|
|
393
|
+
//# sourceMappingURL=AtomToken.js.map
|
package/lib/mp4/MP4Parser.js
CHANGED
|
@@ -91,6 +91,7 @@ function distinct(value, index, self) {
|
|
|
91
91
|
export class MP4Parser extends BasicParser {
|
|
92
92
|
constructor() {
|
|
93
93
|
super(...arguments);
|
|
94
|
+
this.tracks = [];
|
|
94
95
|
this.atomParsers = {
|
|
95
96
|
/**
|
|
96
97
|
* Parse movie header (mvhd) atom
|
|
@@ -208,13 +209,13 @@ export class MP4Parser extends BasicParser {
|
|
|
208
209
|
const integerType = (signed ? 'INT' : 'UINT') + array.length * 8 + (array.length > 1 ? '_BE' : '');
|
|
209
210
|
const token = Token[integerType];
|
|
210
211
|
if (!token) {
|
|
211
|
-
throw new Error(
|
|
212
|
+
throw new Error(`Token for integer type not found: "${integerType}"`);
|
|
212
213
|
}
|
|
213
214
|
return Number(token.get(array, 0));
|
|
214
215
|
}
|
|
215
216
|
async parse() {
|
|
216
217
|
this.tracks = [];
|
|
217
|
-
let remainingFileSize = this.tokenizer.fileInfo.size;
|
|
218
|
+
let remainingFileSize = this.tokenizer.fileInfo.size || 0;
|
|
218
219
|
while (!this.tokenizer.fileInfo.size || remainingFileSize > 0) {
|
|
219
220
|
try {
|
|
220
221
|
const token = await this.tokenizer.peekToken(AtomToken.Header);
|
|
@@ -226,9 +227,13 @@ export class MP4Parser extends BasicParser {
|
|
|
226
227
|
}
|
|
227
228
|
}
|
|
228
229
|
catch (error) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
if (error instanceof Error) {
|
|
231
|
+
const errMsg = `Error at offset=${this.tokenizer.position}: ${error.message}`;
|
|
232
|
+
debug(errMsg);
|
|
233
|
+
this.addWarning(errMsg);
|
|
234
|
+
}
|
|
235
|
+
else
|
|
236
|
+
throw error;
|
|
232
237
|
break;
|
|
233
238
|
}
|
|
234
239
|
const rootAtom = await Atom.readAtom(this.tokenizer, (atom, remaining) => this.handleAtom(atom, remaining), null, remainingFileSize);
|
|
@@ -309,10 +314,8 @@ export class MP4Parser extends BasicParser {
|
|
|
309
314
|
if (this.atomParsers[atom.header.name]) {
|
|
310
315
|
return this.atomParsers[atom.header.name](remaining);
|
|
311
316
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
await this.tokenizer.ignore(remaining);
|
|
315
|
-
}
|
|
317
|
+
debug(`No parser for atom path=${atom.atomPath}, payload-len=${remaining}, ignoring atom`);
|
|
318
|
+
await this.tokenizer.ignore(remaining);
|
|
316
319
|
}
|
|
317
320
|
getTrackDescription() {
|
|
318
321
|
return this.tracks[this.tracks.length - 1];
|
|
@@ -326,7 +329,7 @@ export class MP4Parser extends BasicParser {
|
|
|
326
329
|
await this.metadata.addTag(tagFormat, id, value);
|
|
327
330
|
}
|
|
328
331
|
addWarning(message) {
|
|
329
|
-
debug(
|
|
332
|
+
debug(`Warning: ${message}`);
|
|
330
333
|
this.metadata.addWarning(message);
|
|
331
334
|
}
|
|
332
335
|
/**
|
|
@@ -343,20 +346,22 @@ export class MP4Parser extends BasicParser {
|
|
|
343
346
|
return this.parseValueAtom(tagKey, child);
|
|
344
347
|
case 'name': // name atom (optional)
|
|
345
348
|
case 'mean':
|
|
346
|
-
case 'rate':
|
|
349
|
+
case 'rate': {
|
|
347
350
|
const name = await this.tokenizer.readToken(new AtomToken.NameAtom(payLoadLength));
|
|
348
|
-
tagKey +=
|
|
351
|
+
tagKey += `:${name.name}`;
|
|
349
352
|
break;
|
|
350
|
-
|
|
353
|
+
}
|
|
354
|
+
default: {
|
|
351
355
|
const uint8Array = await this.tokenizer.readToken(new Token.Uint8ArrayType(payLoadLength));
|
|
352
|
-
this.addWarning(
|
|
356
|
+
this.addWarning(`Unsupported meta-item: ${tagKey}[${child.header.name}] => value=${uint8ArrayToHex(uint8Array)} ascii=${uint8ArrayToString(uint8Array, 'ascii')}`);
|
|
357
|
+
}
|
|
353
358
|
}
|
|
354
359
|
}, metaAtom.getPayloadLength(0));
|
|
355
360
|
}
|
|
356
361
|
async parseValueAtom(tagKey, metaAtom) {
|
|
357
362
|
const dataAtom = await this.tokenizer.readToken(new AtomToken.DataAtom(Number(metaAtom.header.length) - AtomToken.Header.len));
|
|
358
363
|
if (dataAtom.type.set !== 0) {
|
|
359
|
-
throw new Error(
|
|
364
|
+
throw new Error(`Unsupported type-set != 0: ${dataAtom.type.set}`);
|
|
360
365
|
}
|
|
361
366
|
// Use well-known-type table
|
|
362
367
|
// Ref: https://developer.apple.com/library/content/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW35
|
|
@@ -364,24 +369,27 @@ export class MP4Parser extends BasicParser {
|
|
|
364
369
|
case 0: // reserved: Reserved for use where no type needs to be indicated
|
|
365
370
|
switch (tagKey) {
|
|
366
371
|
case 'trkn':
|
|
367
|
-
case 'disk':
|
|
372
|
+
case 'disk': {
|
|
368
373
|
const num = Token.UINT8.get(dataAtom.value, 3);
|
|
369
374
|
const of = Token.UINT8.get(dataAtom.value, 5);
|
|
370
375
|
// console.log(" %s[data] = %s/%s", tagKey, num, of);
|
|
371
|
-
await this.addTag(tagKey, num
|
|
376
|
+
await this.addTag(tagKey, `${num}/${of}`);
|
|
372
377
|
break;
|
|
373
|
-
|
|
378
|
+
}
|
|
379
|
+
case 'gnre': {
|
|
374
380
|
const genreInt = Token.UINT8.get(dataAtom.value, 1);
|
|
375
381
|
const genreStr = Genres[genreInt - 1];
|
|
376
382
|
// console.log(" %s[data] = %s", tagKey, genreStr);
|
|
377
383
|
await this.addTag(tagKey, genreStr);
|
|
378
384
|
break;
|
|
379
|
-
|
|
385
|
+
}
|
|
386
|
+
case 'rate': {
|
|
380
387
|
const rate = new TextDecoder('ascii').decode(dataAtom.value);
|
|
381
388
|
await this.addTag(tagKey, rate);
|
|
382
389
|
break;
|
|
390
|
+
}
|
|
383
391
|
default:
|
|
384
|
-
debug(
|
|
392
|
+
debug(`unknown proprietary value type for: ${metaAtom.atomPath}`);
|
|
385
393
|
}
|
|
386
394
|
break;
|
|
387
395
|
case 1: // UTF-8: Without any count or NULL terminator
|
|
@@ -515,3 +523,4 @@ export class MP4Parser extends BasicParser {
|
|
|
515
523
|
return stcTable[stcTable.length - 1].samplesPerChunk;
|
|
516
524
|
}
|
|
517
525
|
}
|
|
526
|
+
//# sourceMappingURL=MP4Parser.js.map
|