music-metadata 8.1.4 → 8.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/README.md +38 -0
- package/lib/apev2/APEv2TagMapper.js +1 -0
- package/lib/apev2/APEv2Token.js +1 -1
- package/lib/asf/AsfObject.js +9 -17
- package/lib/asf/AsfUtil.js +1 -2
- package/lib/common/GenericTagMapper.js +1 -2
- package/lib/common/GenericTagTypes.d.ts +1 -1
- package/lib/common/GenericTagTypes.js +1 -0
- package/lib/dsf/DsfChunk.js +1 -1
- package/lib/id3v2/ID3v24TagMapper.js +1 -1
- package/lib/id3v2/ID3v2Token.js +1 -1
- package/lib/matroska/MatroskaTagMapper.js +1 -0
- package/lib/matroska/types.js +3 -3
- package/lib/mp4/MP4Parser.js +19 -10
- package/lib/mp4/MP4TagMapper.d.ts +3 -0
- package/lib/mp4/MP4TagMapper.js +13 -1
- package/lib/ogg/OggParser.js +1 -2
- package/lib/ogg/vorbis/VorbisTagMapper.d.ts +1 -1
- package/lib/ogg/vorbis/VorbisTagMapper.js +9 -4
- package/lib/type.d.ts +6 -2
- package/lib/wav/WaveChunk.js +1 -1
- package/lib/wavpack/WavPackToken.js +1 -2
- package/package.json +23 -23
package/README.md
CHANGED
|
@@ -100,6 +100,44 @@ import * as mm from 'music-metadata/lib/core';
|
|
|
100
100
|
### Sponsor
|
|
101
101
|
[Become a sponsor to Borewit](https://github.com/sponsors/Borewit)
|
|
102
102
|
|
|
103
|
+
## Dependencies
|
|
104
|
+
|
|
105
|
+
Dependency diagram:
|
|
106
|
+
```mermaid
|
|
107
|
+
graph TD;
|
|
108
|
+
MM(music-metadata)-->S(strtok3)
|
|
109
|
+
MM-->TY(token-types)
|
|
110
|
+
MM-->FT(file-type)
|
|
111
|
+
S(strtok3)-->P(peek-readable)
|
|
112
|
+
S-->TO("@tokenizer/token")
|
|
113
|
+
TY-->TO
|
|
114
|
+
TY-->IE("ieee754")
|
|
115
|
+
FT-->RWNS(readable-web-to-node-stream)
|
|
116
|
+
FT-->S
|
|
117
|
+
FT-->TY
|
|
118
|
+
TY-->NB(node:buffer)
|
|
119
|
+
RWNS-->RS(readable-stream)
|
|
120
|
+
RS-->SD(string_decoder)
|
|
121
|
+
SD-->SB(safe-buffer)
|
|
122
|
+
RS-->UD(util-deprecate)
|
|
123
|
+
RS-->I(inherits)
|
|
124
|
+
style NB fill:#F88,stroke:#A44
|
|
125
|
+
style SB fill:#F88,stroke:#A44
|
|
126
|
+
style SD fill:#CCC,stroke:#888
|
|
127
|
+
style IE fill:#CCC,stroke:#888
|
|
128
|
+
style UD fill:#CCC,stroke:#888
|
|
129
|
+
style I fill:#CCC,stroke:#888
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Dependency list:
|
|
133
|
+
* [tokenizer-token](https://github.com/Borewit/tokenizer-token)
|
|
134
|
+
* [strtok3](https://github.com/Borewit/strtok3)
|
|
135
|
+
* [token-types](https://github.com/Borewit/token-types)
|
|
136
|
+
* [file-type](https://github.com/sindresorhus/file-type)
|
|
137
|
+
* [@tokenizer-token](https://github.com/Borewit/tokenizer-token)
|
|
138
|
+
* [peek-readable](https://github.com/Borewit/peek-readable)
|
|
139
|
+
* [readable-web-to-node-stream](https://github.com/Borewit/readable-web-to-node-stream)
|
|
140
|
+
|
|
103
141
|
## Usage
|
|
104
142
|
|
|
105
143
|
### Installation
|
package/lib/apev2/APEv2Token.js
CHANGED
|
@@ -6,7 +6,7 @@ export var DataType;
|
|
|
6
6
|
DataType[DataType["binary"] = 1] = "binary";
|
|
7
7
|
DataType[DataType["external_info"] = 2] = "external_info";
|
|
8
8
|
DataType[DataType["reserved"] = 3] = "reserved";
|
|
9
|
-
})(DataType
|
|
9
|
+
})(DataType || (DataType = {}));
|
|
10
10
|
/**
|
|
11
11
|
* APE_DESCRIPTOR: defines the sizes (and offsets) of all the pieces, as well as the MD5 checksum
|
|
12
12
|
*/
|
package/lib/asf/AsfObject.js
CHANGED
|
@@ -34,7 +34,7 @@ export var DataType;
|
|
|
34
34
|
* WORD. The data is 2 bytes long and should be interpreted as a 16-bit unsigned integer.
|
|
35
35
|
*/
|
|
36
36
|
DataType[DataType["Word"] = 5] = "Word";
|
|
37
|
-
})(DataType
|
|
37
|
+
})(DataType || (DataType = {}));
|
|
38
38
|
/**
|
|
39
39
|
* Token for: 3. ASF top-level Header Object
|
|
40
40
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3
|
|
@@ -93,7 +93,7 @@ export class IgnoreObjectState extends State {
|
|
|
93
93
|
* Token for: 3.2: File Properties Object (mandatory, one only)
|
|
94
94
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_2
|
|
95
95
|
*/
|
|
96
|
-
class FilePropertiesObject extends State {
|
|
96
|
+
export class FilePropertiesObject extends State {
|
|
97
97
|
constructor(header) {
|
|
98
98
|
super(header);
|
|
99
99
|
}
|
|
@@ -118,12 +118,11 @@ class FilePropertiesObject extends State {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
FilePropertiesObject.guid = GUID.FilePropertiesObject;
|
|
121
|
-
export { FilePropertiesObject };
|
|
122
121
|
/**
|
|
123
122
|
* Token for: 3.3 Stream Properties Object (mandatory, one per stream)
|
|
124
123
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_3
|
|
125
124
|
*/
|
|
126
|
-
class StreamPropertiesObject extends State {
|
|
125
|
+
export class StreamPropertiesObject extends State {
|
|
127
126
|
constructor(header) {
|
|
128
127
|
super(header);
|
|
129
128
|
}
|
|
@@ -136,12 +135,11 @@ class StreamPropertiesObject extends State {
|
|
|
136
135
|
}
|
|
137
136
|
}
|
|
138
137
|
StreamPropertiesObject.guid = GUID.StreamPropertiesObject;
|
|
139
|
-
export { StreamPropertiesObject };
|
|
140
138
|
/**
|
|
141
139
|
* 3.4: Header Extension Object (mandatory, one only)
|
|
142
140
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_4
|
|
143
141
|
*/
|
|
144
|
-
class HeaderExtensionObject {
|
|
142
|
+
export class HeaderExtensionObject {
|
|
145
143
|
constructor() {
|
|
146
144
|
this.len = 22;
|
|
147
145
|
}
|
|
@@ -154,7 +152,6 @@ class HeaderExtensionObject {
|
|
|
154
152
|
}
|
|
155
153
|
}
|
|
156
154
|
HeaderExtensionObject.guid = GUID.HeaderExtensionObject;
|
|
157
|
-
export { HeaderExtensionObject };
|
|
158
155
|
/**
|
|
159
156
|
* 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.
|
|
160
157
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_5
|
|
@@ -209,7 +206,7 @@ async function readCodecEntry(tokenizer) {
|
|
|
209
206
|
* 3.10 Content Description Object (optional, one only)
|
|
210
207
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_10
|
|
211
208
|
*/
|
|
212
|
-
class ContentDescriptionObjectState extends State {
|
|
209
|
+
export class ContentDescriptionObjectState extends State {
|
|
213
210
|
constructor(header) {
|
|
214
211
|
super(header);
|
|
215
212
|
}
|
|
@@ -230,12 +227,11 @@ class ContentDescriptionObjectState extends State {
|
|
|
230
227
|
}
|
|
231
228
|
ContentDescriptionObjectState.guid = GUID.ContentDescriptionObject;
|
|
232
229
|
ContentDescriptionObjectState.contentDescTags = ['Title', 'Author', 'Copyright', 'Description', 'Rating'];
|
|
233
|
-
export { ContentDescriptionObjectState };
|
|
234
230
|
/**
|
|
235
231
|
* 3.11 Extended Content Description Object (optional, one only)
|
|
236
232
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_11
|
|
237
233
|
*/
|
|
238
|
-
class ExtendedContentDescriptionObjectState extends State {
|
|
234
|
+
export class ExtendedContentDescriptionObjectState extends State {
|
|
239
235
|
constructor(header) {
|
|
240
236
|
super(header);
|
|
241
237
|
}
|
|
@@ -260,12 +256,11 @@ class ExtendedContentDescriptionObjectState extends State {
|
|
|
260
256
|
}
|
|
261
257
|
}
|
|
262
258
|
ExtendedContentDescriptionObjectState.guid = GUID.ExtendedContentDescriptionObject;
|
|
263
|
-
export { ExtendedContentDescriptionObjectState };
|
|
264
259
|
/**
|
|
265
260
|
* 4.1 Extended Stream Properties Object (optional, 1 per media stream)
|
|
266
261
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/04_objects_in_the_asf_header_extension_object.html#4_1
|
|
267
262
|
*/
|
|
268
|
-
class ExtendedStreamPropertiesObjectState extends State {
|
|
263
|
+
export class ExtendedStreamPropertiesObjectState extends State {
|
|
269
264
|
constructor(header) {
|
|
270
265
|
super(header);
|
|
271
266
|
}
|
|
@@ -297,12 +292,11 @@ class ExtendedStreamPropertiesObjectState extends State {
|
|
|
297
292
|
}
|
|
298
293
|
}
|
|
299
294
|
ExtendedStreamPropertiesObjectState.guid = GUID.ExtendedStreamPropertiesObject;
|
|
300
|
-
export { ExtendedStreamPropertiesObjectState };
|
|
301
295
|
/**
|
|
302
296
|
* 4.7 Metadata Object (optional, 0 or 1)
|
|
303
297
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/04_objects_in_the_asf_header_extension_object.html#4_7
|
|
304
298
|
*/
|
|
305
|
-
class MetadataObjectState extends State {
|
|
299
|
+
export class MetadataObjectState extends State {
|
|
306
300
|
constructor(header) {
|
|
307
301
|
super(header);
|
|
308
302
|
}
|
|
@@ -329,15 +323,13 @@ class MetadataObjectState extends State {
|
|
|
329
323
|
}
|
|
330
324
|
}
|
|
331
325
|
MetadataObjectState.guid = GUID.MetadataObject;
|
|
332
|
-
export { MetadataObjectState };
|
|
333
326
|
// 4.8 Metadata Library Object (optional, 0 or 1)
|
|
334
|
-
class MetadataLibraryObjectState extends MetadataObjectState {
|
|
327
|
+
export class MetadataLibraryObjectState extends MetadataObjectState {
|
|
335
328
|
constructor(header) {
|
|
336
329
|
super(header);
|
|
337
330
|
}
|
|
338
331
|
}
|
|
339
332
|
MetadataLibraryObjectState.guid = GUID.MetadataLibraryObject;
|
|
340
|
-
export { MetadataLibraryObjectState };
|
|
341
333
|
/**
|
|
342
334
|
* Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/dd757977(v=vs.85).aspx
|
|
343
335
|
*/
|
package/lib/asf/AsfUtil.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as Token from 'token-types';
|
|
2
2
|
import { Buffer } from 'node:buffer';
|
|
3
3
|
import * as util from '../common/Util.js';
|
|
4
|
-
class AsfUtil {
|
|
4
|
+
export class AsfUtil {
|
|
5
5
|
static getParserForAttr(i) {
|
|
6
6
|
return AsfUtil.attributeParsers[i];
|
|
7
7
|
}
|
|
@@ -33,4 +33,3 @@ AsfUtil.attributeParsers = [
|
|
|
33
33
|
AsfUtil.parseWordAttr,
|
|
34
34
|
AsfUtil.parseByteArrayAttr
|
|
35
35
|
];
|
|
36
|
-
export { AsfUtil };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class CommonTagMapper {
|
|
1
|
+
export class CommonTagMapper {
|
|
2
2
|
static toIntOrNull(str) {
|
|
3
3
|
const cleaned = parseInt(str, 10);
|
|
4
4
|
return isNaN(cleaned) ? null : cleaned;
|
|
@@ -49,5 +49,4 @@ class CommonTagMapper {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
CommonTagMapper.maxRatingScore = 1;
|
|
52
|
-
export { CommonTagMapper };
|
|
53
52
|
//# sourceMappingURL=GenericTagMapper.js.map
|
|
@@ -3,7 +3,7 @@ export interface IGenericTag {
|
|
|
3
3
|
id: GenericTagId;
|
|
4
4
|
value: any;
|
|
5
5
|
}
|
|
6
|
-
export type GenericTagId = 'track' | 'disk' | 'year' | 'title' | 'artist' | 'artists' | 'albumartist' | 'album' | 'date' | 'originaldate' | 'originalyear' | 'comment' | 'genre' | 'picture' | 'composer' | 'lyrics' | 'albumsort' | 'titlesort' | 'work' | 'artistsort' | 'albumartistsort' | 'composersort' | 'lyricist' | 'writer' | 'conductor' | 'remixer' | 'arranger' | 'engineer' | 'technician' | 'producer' | 'djmixer' | 'mixer' | 'publisher' | 'label' | 'grouping' | 'subtitle' | 'discsubtitle' | 'totaltracks' | 'totaldiscs' | 'compilation' | 'rating' | 'bpm' | 'mood' | 'media' | 'catalognumber' | 'tvShow' | 'tvShowSort' | 'tvEpisode' | 'tvEpisodeId' | 'tvNetwork' | 'tvSeason' | 'podcast' | 'podcasturl' | 'releasestatus' | 'releasetype' | 'releasecountry' | 'script' | 'language' | 'copyright' | 'license' | 'encodedby' | 'encodersettings' | 'gapless' | 'barcode' | 'isrc' | 'asin' | 'musicbrainz_recordingid' | 'musicbrainz_trackid' | 'musicbrainz_albumid' | 'musicbrainz_artistid' | 'musicbrainz_albumartistid' | 'musicbrainz_releasegroupid' | 'musicbrainz_workid' | 'musicbrainz_trmid' | 'musicbrainz_discid' | 'acoustid_id' | 'acoustid_fingerprint' | 'musicip_puid' | 'musicip_fingerprint' | 'website' | 'performer:instrument' | 'peakLevel' | 'averageLevel' | 'notes' | 'key' | 'originalalbum' | 'originalartist' | 'discogs_artist_id' | 'discogs_label_id' | 'discogs_master_release_id' | 'discogs_rating' | 'discogs_release_id' | 'discogs_votes' | 'replaygain_track_gain' | 'replaygain_track_peak' | 'replaygain_album_gain' | 'replaygain_album_peak' | 'replaygain_track_minmax' | 'replaygain_album_minmax' | 'replaygain_undo' | 'description' | 'longDescription' | 'category' | 'hdVideo' | 'keywords' | 'movement' | 'movementIndex' | 'movementTotal' | 'podcastId' | 'showMovement' | 'stik';
|
|
6
|
+
export type GenericTagId = 'track' | 'disk' | 'year' | 'title' | 'artist' | 'artists' | 'albumartist' | 'album' | 'date' | 'originaldate' | 'originalyear' | 'releasedate' | 'comment' | 'genre' | 'picture' | 'composer' | 'lyrics' | 'albumsort' | 'titlesort' | 'work' | 'artistsort' | 'albumartistsort' | 'composersort' | 'lyricist' | 'writer' | 'conductor' | 'remixer' | 'arranger' | 'engineer' | 'technician' | 'producer' | 'djmixer' | 'mixer' | 'publisher' | 'label' | 'grouping' | 'subtitle' | 'discsubtitle' | 'totaltracks' | 'totaldiscs' | 'compilation' | 'rating' | 'bpm' | 'mood' | 'media' | 'catalognumber' | 'tvShow' | 'tvShowSort' | 'tvEpisode' | 'tvEpisodeId' | 'tvNetwork' | 'tvSeason' | 'podcast' | 'podcasturl' | 'releasestatus' | 'releasetype' | 'releasecountry' | 'script' | 'language' | 'copyright' | 'license' | 'encodedby' | 'encodersettings' | 'gapless' | 'barcode' | 'isrc' | 'asin' | 'musicbrainz_recordingid' | 'musicbrainz_trackid' | 'musicbrainz_albumid' | 'musicbrainz_artistid' | 'musicbrainz_albumartistid' | 'musicbrainz_releasegroupid' | 'musicbrainz_workid' | 'musicbrainz_trmid' | 'musicbrainz_discid' | 'acoustid_id' | 'acoustid_fingerprint' | 'musicip_puid' | 'musicip_fingerprint' | 'website' | 'performer:instrument' | 'peakLevel' | 'averageLevel' | 'notes' | 'key' | 'originalalbum' | 'originalartist' | 'discogs_artist_id' | 'discogs_label_id' | 'discogs_master_release_id' | 'discogs_rating' | 'discogs_release_id' | 'discogs_votes' | 'replaygain_track_gain' | 'replaygain_track_peak' | 'replaygain_album_gain' | 'replaygain_album_peak' | 'replaygain_track_minmax' | 'replaygain_album_minmax' | 'replaygain_undo' | 'description' | 'longDescription' | 'category' | 'hdVideo' | 'keywords' | 'movement' | 'movementIndex' | 'movementTotal' | 'podcastId' | 'showMovement' | 'stik';
|
|
7
7
|
export interface INativeTagMap {
|
|
8
8
|
[index: string]: GenericTagId;
|
|
9
9
|
}
|
|
@@ -10,6 +10,7 @@ export const commonTags = {
|
|
|
10
10
|
date: { multiple: false },
|
|
11
11
|
originaldate: { multiple: false },
|
|
12
12
|
originalyear: { multiple: false },
|
|
13
|
+
releasedate: { multiple: false },
|
|
13
14
|
comment: { multiple: true, unique: false },
|
|
14
15
|
genre: { multiple: true, unique: true },
|
|
15
16
|
picture: { multiple: true, unique: true },
|
package/lib/dsf/DsfChunk.js
CHANGED
|
@@ -30,7 +30,7 @@ export var ChannelType;
|
|
|
30
30
|
ChannelType[ChannelType["4 channels"] = 5] = "4 channels";
|
|
31
31
|
ChannelType[ChannelType["5 channels"] = 6] = "5 channels";
|
|
32
32
|
ChannelType[ChannelType["5.1 channels"] = 7] = "5.1 channels";
|
|
33
|
-
})(ChannelType
|
|
33
|
+
})(ChannelType || (ChannelType = {}));
|
|
34
34
|
/**
|
|
35
35
|
* Common chunk DSD header: the 'chunk name (Four-CC)' & chunk size
|
|
36
36
|
*/
|
package/lib/id3v2/ID3v2Token.js
CHANGED
|
@@ -27,7 +27,7 @@ export var AttachedPictureType;
|
|
|
27
27
|
AttachedPictureType[AttachedPictureType["Illustration"] = 18] = "Illustration";
|
|
28
28
|
AttachedPictureType[AttachedPictureType["Band/artist logotype"] = 19] = "Band/artist logotype";
|
|
29
29
|
AttachedPictureType[AttachedPictureType["Publisher/Studio logotype"] = 20] = "Publisher/Studio logotype";
|
|
30
|
-
})(AttachedPictureType
|
|
30
|
+
})(AttachedPictureType || (AttachedPictureType = {}));
|
|
31
31
|
/**
|
|
32
32
|
* 28 bits (representing up to 256MB) integer, the msb is 0 to avoid 'false syncsignals'.
|
|
33
33
|
* 4 * %0xxxxxxx
|
|
@@ -8,6 +8,7 @@ const ebmlTagMap = {
|
|
|
8
8
|
'album:ARTISTSORT': 'albumartistsort',
|
|
9
9
|
'album:TITLE': 'album',
|
|
10
10
|
'album:DATE_RECORDED': 'originaldate',
|
|
11
|
+
'album:DATE_RELEASED': 'releasedate',
|
|
11
12
|
'album:PART_NUMBER': 'disk',
|
|
12
13
|
'album:TOTAL_PARTS': 'totaltracks',
|
|
13
14
|
'track:ARTIST': 'artist',
|
package/lib/matroska/types.js
CHANGED
|
@@ -6,7 +6,7 @@ export var DataType;
|
|
|
6
6
|
DataType[DataType["bool"] = 3] = "bool";
|
|
7
7
|
DataType[DataType["binary"] = 4] = "binary";
|
|
8
8
|
DataType[DataType["float"] = 5] = "float";
|
|
9
|
-
})(DataType
|
|
9
|
+
})(DataType || (DataType = {}));
|
|
10
10
|
export var TargetType;
|
|
11
11
|
(function (TargetType) {
|
|
12
12
|
TargetType[TargetType["shot"] = 10] = "shot";
|
|
@@ -16,7 +16,7 @@ export var TargetType;
|
|
|
16
16
|
TargetType[TargetType["album"] = 50] = "album";
|
|
17
17
|
TargetType[TargetType["edition"] = 60] = "edition";
|
|
18
18
|
TargetType[TargetType["collection"] = 70] = "collection";
|
|
19
|
-
})(TargetType
|
|
19
|
+
})(TargetType || (TargetType = {}));
|
|
20
20
|
export var TrackType;
|
|
21
21
|
(function (TrackType) {
|
|
22
22
|
TrackType[TrackType["video"] = 1] = "video";
|
|
@@ -26,5 +26,5 @@ export var TrackType;
|
|
|
26
26
|
TrackType[TrackType["subtitle"] = 17] = "subtitle";
|
|
27
27
|
TrackType[TrackType["button"] = 18] = "button";
|
|
28
28
|
TrackType[TrackType["control"] = 32] = "control";
|
|
29
|
-
})(TrackType
|
|
29
|
+
})(TrackType || (TrackType = {}));
|
|
30
30
|
//# sourceMappingURL=types.js.map
|
package/lib/mp4/MP4Parser.js
CHANGED
|
@@ -175,7 +175,7 @@ export class MP4Parser extends BasicParser {
|
|
|
175
175
|
this.getTrackDescription().sampleToChunkTable = stsc.entries;
|
|
176
176
|
},
|
|
177
177
|
/**
|
|
178
|
-
* time
|
|
178
|
+
* time-to-sample table
|
|
179
179
|
*/
|
|
180
180
|
stts: async (len) => {
|
|
181
181
|
const stts = await this.tokenizer.readToken(new AtomToken.SttsAtom(len));
|
|
@@ -272,13 +272,22 @@ export class MP4Parser extends BasicParser {
|
|
|
272
272
|
});
|
|
273
273
|
if (audioTracks.length >= 1) {
|
|
274
274
|
const audioTrack = audioTracks[0];
|
|
275
|
-
|
|
276
|
-
|
|
275
|
+
if (audioTrack.timeScale > 0) {
|
|
276
|
+
const duration = audioTrack.duration / audioTrack.timeScale; // calculate duration in seconds
|
|
277
|
+
this.metadata.setFormat('duration', duration);
|
|
278
|
+
}
|
|
277
279
|
const ssd = audioTrack.soundSampleDescription[0];
|
|
278
280
|
if (ssd.description) {
|
|
279
281
|
this.metadata.setFormat('sampleRate', ssd.description.sampleRate);
|
|
280
282
|
this.metadata.setFormat('bitsPerSample', ssd.description.sampleSize);
|
|
281
283
|
this.metadata.setFormat('numberOfChannels', ssd.description.numAudioChannels);
|
|
284
|
+
if (audioTrack.timeScale === 0 && audioTrack.timeToSampleTable.length > 0) {
|
|
285
|
+
const totalSampleSize = audioTrack.timeToSampleTable
|
|
286
|
+
.map(ttstEntry => ttstEntry.count * ttstEntry.duration)
|
|
287
|
+
.reduce((total, sampleSize) => total + sampleSize);
|
|
288
|
+
const duration = totalSampleSize / ssd.description.sampleRate;
|
|
289
|
+
this.metadata.setFormat('duration', duration);
|
|
290
|
+
}
|
|
282
291
|
}
|
|
283
292
|
const encoderInfo = encoderDict[ssd.dataFormat];
|
|
284
293
|
if (encoderInfo) {
|
|
@@ -332,14 +341,11 @@ export class MP4Parser extends BasicParser {
|
|
|
332
341
|
case 'data': // value atom
|
|
333
342
|
return this.parseValueAtom(tagKey, child);
|
|
334
343
|
case 'name': // name atom (optional)
|
|
344
|
+
case 'mean':
|
|
345
|
+
case 'rate':
|
|
335
346
|
const name = await this.tokenizer.readToken(new AtomToken.NameAtom(payLoadLength));
|
|
336
347
|
tagKey += ':' + name.name;
|
|
337
348
|
break;
|
|
338
|
-
case 'mean': // name atom (optional)
|
|
339
|
-
const mean = await this.tokenizer.readToken(new AtomToken.NameAtom(payLoadLength));
|
|
340
|
-
// console.log(" %s[%s] = %s", tagKey, header.name, mean.name);
|
|
341
|
-
tagKey += ':' + mean.name;
|
|
342
|
-
break;
|
|
343
349
|
default:
|
|
344
350
|
const dataAtom = await this.tokenizer.readToken(new Token.BufferType(payLoadLength));
|
|
345
351
|
this.addWarning('Unsupported meta-item: ' + tagKey + '[' + child.header.name + '] => value=' + dataAtom.toString('hex') + ' ascii=' + dataAtom.toString('ascii'));
|
|
@@ -369,9 +375,12 @@ export class MP4Parser extends BasicParser {
|
|
|
369
375
|
// console.log(" %s[data] = %s", tagKey, genreStr);
|
|
370
376
|
this.addTag(tagKey, genreStr);
|
|
371
377
|
break;
|
|
378
|
+
case 'rate':
|
|
379
|
+
const rate = dataAtom.value.toString('ascii');
|
|
380
|
+
this.addTag(tagKey, rate);
|
|
381
|
+
break;
|
|
372
382
|
default:
|
|
373
|
-
|
|
374
|
-
// header.name, header.length, dataAtom.type.set, dataAtom.type.type, dataAtom.locale, dataAtom.value.toString('hex'), dataAtom.value.toString('ascii'));
|
|
383
|
+
debug('unknown proprietary value type for: ' + metaAtom.atomPath);
|
|
375
384
|
}
|
|
376
385
|
break;
|
|
377
386
|
case 1: // UTF-8: Without any count or NULL terminator
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { CaseInsensitiveTagMap } from '../common/CaseInsensitiveTagMap.js';
|
|
2
|
+
import { ITag } from "../type.js";
|
|
3
|
+
import { INativeMetadataCollector } from "../common/MetadataCollector.js";
|
|
2
4
|
export declare const tagType = "iTunes";
|
|
3
5
|
export declare class MP4TagMapper extends CaseInsensitiveTagMap {
|
|
4
6
|
constructor();
|
|
7
|
+
protected postMap(tag: ITag, warnings: INativeMetadataCollector): void;
|
|
5
8
|
}
|
package/lib/mp4/MP4TagMapper.js
CHANGED
|
@@ -89,6 +89,7 @@ const mp4TagMap = {
|
|
|
89
89
|
'----:com.apple.iTunes:ARTISTS': 'artists',
|
|
90
90
|
'----:com.apple.iTunes:ORIGINALDATE': 'originaldate',
|
|
91
91
|
'----:com.apple.iTunes:ORIGINALYEAR': 'originalyear',
|
|
92
|
+
'----:com.apple.iTunes:RELEASEDATE': 'releasedate',
|
|
92
93
|
// '----:com.apple.iTunes:PERFORMER': 'performer'
|
|
93
94
|
desc: 'description',
|
|
94
95
|
ldes: 'longDescription',
|
|
@@ -101,12 +102,23 @@ const mp4TagMap = {
|
|
|
101
102
|
hdvd: 'hdVideo',
|
|
102
103
|
keyw: 'keywords',
|
|
103
104
|
shwm: 'showMovement',
|
|
104
|
-
stik: 'stik'
|
|
105
|
+
stik: 'stik',
|
|
106
|
+
rate: 'rating'
|
|
105
107
|
};
|
|
106
108
|
export const tagType = 'iTunes';
|
|
107
109
|
export class MP4TagMapper extends CaseInsensitiveTagMap {
|
|
108
110
|
constructor() {
|
|
109
111
|
super([tagType], mp4TagMap);
|
|
110
112
|
}
|
|
113
|
+
postMap(tag, warnings) {
|
|
114
|
+
switch (tag.id) {
|
|
115
|
+
case 'rate':
|
|
116
|
+
tag.value = {
|
|
117
|
+
source: undefined,
|
|
118
|
+
rating: parseFloat(tag.value) / 100
|
|
119
|
+
};
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
111
123
|
}
|
|
112
124
|
//# sourceMappingURL=MP4TagMapper.js.map
|
package/lib/ogg/OggParser.js
CHANGED
|
@@ -29,7 +29,7 @@ export class SegmentTable {
|
|
|
29
29
|
/**
|
|
30
30
|
* Parser for Ogg logical bitstream framing
|
|
31
31
|
*/
|
|
32
|
-
class OggParser extends BasicParser {
|
|
32
|
+
export class OggParser extends BasicParser {
|
|
33
33
|
/**
|
|
34
34
|
* Parse page
|
|
35
35
|
* @returns {Promise<void>}
|
|
@@ -119,4 +119,3 @@ OggParser.Header = {
|
|
|
119
119
|
};
|
|
120
120
|
}
|
|
121
121
|
};
|
|
122
|
-
export { OggParser };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CommonTagMapper } from '../../common/GenericTagMapper.js';
|
|
2
2
|
import { IRating, ITag } from '../../type.js';
|
|
3
3
|
export declare class VorbisTagMapper extends CommonTagMapper {
|
|
4
|
-
static toRating(email: string, rating: string): IRating;
|
|
4
|
+
static toRating(email: string, rating: string, maxScore: number): IRating;
|
|
5
5
|
constructor();
|
|
6
6
|
protected postMap(tag: ITag): void;
|
|
7
7
|
}
|
|
@@ -16,6 +16,7 @@ const vorbisTagMap = {
|
|
|
16
16
|
DATE: 'date',
|
|
17
17
|
ORIGINALDATE: 'originaldate',
|
|
18
18
|
ORIGINALYEAR: 'originalyear',
|
|
19
|
+
RELEASEDATE: 'releasedate',
|
|
19
20
|
COMMENT: 'comment',
|
|
20
21
|
TRACKNUMBER: 'track',
|
|
21
22
|
DISCNUMBER: 'disk',
|
|
@@ -109,19 +110,23 @@ const vorbisTagMap = {
|
|
|
109
110
|
REPLAYGAIN_UNDO: 'replaygain_undo'
|
|
110
111
|
};
|
|
111
112
|
export class VorbisTagMapper extends CommonTagMapper {
|
|
112
|
-
static toRating(email, rating) {
|
|
113
|
+
static toRating(email, rating, maxScore) {
|
|
113
114
|
return {
|
|
114
115
|
source: email ? email.toLowerCase() : email,
|
|
115
|
-
rating: parseFloat(rating) * CommonTagMapper.maxRatingScore
|
|
116
|
+
rating: (parseFloat(rating) / maxScore) * CommonTagMapper.maxRatingScore
|
|
116
117
|
};
|
|
117
118
|
}
|
|
118
119
|
constructor() {
|
|
119
120
|
super(['vorbis'], vorbisTagMap);
|
|
120
121
|
}
|
|
121
122
|
postMap(tag) {
|
|
122
|
-
if (tag.id
|
|
123
|
+
if (tag.id === 'RATING') {
|
|
124
|
+
// The way Winamp 5.666 assigns rating
|
|
125
|
+
tag.value = VorbisTagMapper.toRating(undefined, tag.value, 100);
|
|
126
|
+
}
|
|
127
|
+
else if (tag.id.indexOf('RATING:') === 0) {
|
|
123
128
|
const keys = tag.id.split(':');
|
|
124
|
-
tag.value = VorbisTagMapper.toRating(keys[1], tag.value);
|
|
129
|
+
tag.value = VorbisTagMapper.toRating(keys[1], tag.value, 1);
|
|
125
130
|
tag.id = keys[0];
|
|
126
131
|
}
|
|
127
132
|
}
|
package/lib/type.d.ts
CHANGED
|
@@ -76,7 +76,7 @@ export interface ICommonTagsResult {
|
|
|
76
76
|
*/
|
|
77
77
|
album?: string;
|
|
78
78
|
/**
|
|
79
|
-
*
|
|
79
|
+
* Date
|
|
80
80
|
*/
|
|
81
81
|
date?: string;
|
|
82
82
|
/**
|
|
@@ -84,9 +84,13 @@ export interface ICommonTagsResult {
|
|
|
84
84
|
*/
|
|
85
85
|
originaldate?: string;
|
|
86
86
|
/**
|
|
87
|
-
* Original release
|
|
87
|
+
* Original release year
|
|
88
88
|
*/
|
|
89
89
|
originalyear?: number;
|
|
90
|
+
/**
|
|
91
|
+
* Release date
|
|
92
|
+
*/
|
|
93
|
+
releasedate?: string;
|
|
90
94
|
/**
|
|
91
95
|
* List of comments
|
|
92
96
|
*/
|
package/lib/wav/WaveChunk.js
CHANGED
|
@@ -18,7 +18,7 @@ export var WaveFormat;
|
|
|
18
18
|
WaveFormat[WaveFormat["DRM"] = 9] = "DRM";
|
|
19
19
|
WaveFormat[WaveFormat["DTS2"] = 8193] = "DTS2";
|
|
20
20
|
WaveFormat[WaveFormat["MPEG"] = 80] = "MPEG";
|
|
21
|
-
})(WaveFormat
|
|
21
|
+
})(WaveFormat || (WaveFormat = {}));
|
|
22
22
|
/**
|
|
23
23
|
* format chunk; chunk-id is "fmt "
|
|
24
24
|
* http://soundfile.sapp.org/doc/WaveFormat/
|
|
@@ -2,7 +2,7 @@ import * as Token from 'token-types';
|
|
|
2
2
|
import { FourCcToken } from '../common/FourCC.js';
|
|
3
3
|
const SampleRates = [6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, 32000, 44100,
|
|
4
4
|
48000, 64000, 88200, 96000, 192000, -1];
|
|
5
|
-
class WavPack {
|
|
5
|
+
export class WavPack {
|
|
6
6
|
static isBitSet(flags, bitOffset) {
|
|
7
7
|
return WavPack.getBitAllignedNumber(flags, bitOffset, 1) === 1;
|
|
8
8
|
}
|
|
@@ -70,4 +70,3 @@ WavPack.MetadataIdToken = {
|
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
};
|
|
73
|
-
export { WavPack };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "music-metadata",
|
|
3
3
|
"description": "Music metadata parser for Node.js, supporting virtual any audio and tag format.",
|
|
4
|
-
"version": "8.
|
|
4
|
+
"version": "8.2.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Borewit",
|
|
7
7
|
"url": "https://github.com/Borewit"
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"compile-doc": "tsc -p doc-gen",
|
|
80
80
|
"compile": "npm run compile-src && npm run compile-test && npm run compile-doc",
|
|
81
81
|
"eslint": "eslint lib/**/*.ts --ignore-pattern lib/**/*.d.ts example/typescript/**/*.ts test/**/*.ts doc-gen/**/*.ts",
|
|
82
|
-
"lint-md": "remark -u preset-lint-
|
|
82
|
+
"lint-md": "remark -u preset-lint-markdown-style-guide .",
|
|
83
83
|
"lint": "npm run lint-md && npm run eslint",
|
|
84
84
|
"test": "mocha",
|
|
85
85
|
"build": "npm run clean && npm run compile && npm run doc-gen",
|
|
@@ -92,39 +92,39 @@
|
|
|
92
92
|
"@tokenizer/token": "^0.3.0",
|
|
93
93
|
"content-type": "^1.0.5",
|
|
94
94
|
"debug": "^4.3.4",
|
|
95
|
-
"file-type": "^18.
|
|
95
|
+
"file-type": "^18.6.0",
|
|
96
96
|
"media-typer": "^1.1.0",
|
|
97
97
|
"strtok3": "^7.0.0",
|
|
98
98
|
"token-types": "^5.0.1"
|
|
99
99
|
},
|
|
100
100
|
"devDependencies": {
|
|
101
|
-
"@types/chai": "^4.3.
|
|
102
|
-
"@types/chai-as-promised": "^7.1.
|
|
103
|
-
"@types/debug": "^4.1.
|
|
101
|
+
"@types/chai": "^4.3.9",
|
|
102
|
+
"@types/chai-as-promised": "^7.1.7",
|
|
103
|
+
"@types/debug": "^4.1.11",
|
|
104
104
|
"@types/file-type": "^10.9.1",
|
|
105
|
-
"@types/mocha": "^10.0.
|
|
106
|
-
"@types/node": "^
|
|
107
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
108
|
-
"@typescript-eslint/parser": "^5.
|
|
109
|
-
"c8": "^
|
|
110
|
-
"chai": "^4.3.
|
|
105
|
+
"@types/mocha": "^10.0.3",
|
|
106
|
+
"@types/node": "^20.8.10",
|
|
107
|
+
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
|
108
|
+
"@typescript-eslint/parser": "^5.62.0",
|
|
109
|
+
"c8": "^8.0.1",
|
|
110
|
+
"chai": "^4.3.10",
|
|
111
111
|
"chai-as-promised": "^7.1.1",
|
|
112
|
-
"del-cli": "5.
|
|
113
|
-
"eslint": "^8.
|
|
114
|
-
"eslint-config-prettier": "^
|
|
115
|
-
"eslint-import-resolver-typescript": "^3.
|
|
116
|
-
"eslint-plugin-import": "^2.
|
|
117
|
-
"eslint-plugin-jsdoc": "^
|
|
112
|
+
"del-cli": "5.1.0",
|
|
113
|
+
"eslint": "^8.53.0",
|
|
114
|
+
"eslint-config-prettier": "^9.0.0",
|
|
115
|
+
"eslint-import-resolver-typescript": "^3.6.1",
|
|
116
|
+
"eslint-plugin-import": "^2.29.0",
|
|
117
|
+
"eslint-plugin-jsdoc": "^46.8.2",
|
|
118
118
|
"eslint-plugin-node": "^11.1.0",
|
|
119
|
-
"eslint-plugin-unicorn": "^
|
|
119
|
+
"eslint-plugin-unicorn": "^49.0.0",
|
|
120
120
|
"mime": "^3.0.0",
|
|
121
121
|
"mocha": "^10.2.0",
|
|
122
122
|
"npm-run-all": "^4.1.5",
|
|
123
|
-
"prettier": "^
|
|
124
|
-
"remark-cli": "^
|
|
125
|
-
"remark-preset-lint-
|
|
123
|
+
"prettier": "^3.0.3",
|
|
124
|
+
"remark-cli": "^12.0.0",
|
|
125
|
+
"remark-preset-lint-markdown-style-guide": "^5.1.3",
|
|
126
126
|
"ts-node": "^10.9.1",
|
|
127
|
-
"typescript": "^5.
|
|
127
|
+
"typescript": "^5.2.2"
|
|
128
128
|
},
|
|
129
129
|
"engines": {
|
|
130
130
|
"node": "^14.13.1 || >=16.0.0"
|