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 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
@@ -11,6 +11,7 @@ const apev2TagMap = {
11
11
  Year: 'date',
12
12
  Originalyear: 'originalyear',
13
13
  Originaldate: 'originaldate',
14
+ Releasedate: 'releasedate',
14
15
  Comment: 'comment',
15
16
  Track: 'track',
16
17
  Disc: 'disk',
@@ -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 = DataType || (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
  */
@@ -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 = DataType || (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
  */
@@ -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 },
@@ -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 = ChannelType || (ChannelType = {}));
33
+ })(ChannelType || (ChannelType = {}));
34
34
  /**
35
35
  * Common chunk DSD header: the 'chunk name (Four-CC)' & chunk size
36
36
  */
@@ -132,7 +132,7 @@ const id3v24TagMap = {
132
132
  PCST: 'podcast',
133
133
  TCAT: 'category',
134
134
  TDES: 'description',
135
- TDRL: 'date',
135
+ TDRL: 'releasedate',
136
136
  TGID: 'podcastId',
137
137
  TKWD: 'keywords',
138
138
  WFED: 'podcasturl'
@@ -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 = AttachedPictureType || (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',
@@ -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 = DataType || (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 = TargetType || (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 = TrackType || (TrackType = {}));
29
+ })(TrackType || (TrackType = {}));
30
30
  //# sourceMappingURL=types.js.map
@@ -175,7 +175,7 @@ export class MP4Parser extends BasicParser {
175
175
  this.getTrackDescription().sampleToChunkTable = stsc.entries;
176
176
  },
177
177
  /**
178
- * time to sample
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
- const duration = audioTrack.duration / audioTrack.timeScale;
276
- this.metadata.setFormat('duration', duration); // calculate duration in seconds
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
- // console.log(" reserved-data: name=%s, len=%s, set=%s, type=%s, locale=%s, value{ hex=%s, ascii=%s }",
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
  }
@@ -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
@@ -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.indexOf('RATING:') === 0) {
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
- * Release data
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 yeat
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
  */
@@ -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 = WaveFormat || (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.1.4",
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-recommended .",
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.2.1",
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.4",
102
- "@types/chai-as-promised": "^7.1.5",
103
- "@types/debug": "^4.1.7",
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.1",
106
- "@types/node": "^18.15.11",
107
- "@typescript-eslint/eslint-plugin": "^5.57.0",
108
- "@typescript-eslint/parser": "^5.57.0",
109
- "c8": "^7.13.0",
110
- "chai": "^4.3.7",
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.0.0",
113
- "eslint": "^8.37.0",
114
- "eslint-config-prettier": "^8.8.0",
115
- "eslint-import-resolver-typescript": "^3.5.3",
116
- "eslint-plugin-import": "^2.27.5",
117
- "eslint-plugin-jsdoc": "^40.1.0",
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": "^46.0.0",
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": "^2.8.7",
124
- "remark-cli": "^11.0.0",
125
- "remark-preset-lint-recommended": "^6.1.2",
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.0.2"
127
+ "typescript": "^5.2.2"
128
128
  },
129
129
  "engines": {
130
130
  "node": "^14.13.1 || >=16.0.0"