tiny-essentials 1.28.12 → 1.28.14

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.
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ var objChecker = require('./objChecker.cjs');
4
+
3
5
  /**
4
6
  * A type representing either a static string or a function that returns a string, used to resolve an unknown artist's name.
5
7
  * @typedef {string|(() => string)} UnknownArtistGetter
@@ -28,7 +30,52 @@
28
30
 
29
31
  /**
30
32
  * A numeric structure representing track or disk indexing.
31
- * @typedef {{no: number|null, of: number|null}} MediaNumber
33
+ * @typedef {{no?: number, of?: number}} MediaNumber
34
+ */
35
+
36
+ /**
37
+ * @typedef {Object} IComment
38
+ * @property {string} [descriptor] - A descriptor for the comment.
39
+ * @property {string} [language] - The language of the comment.
40
+ * @property {string} [text] - The text content of the comment.
41
+ */
42
+
43
+ /**
44
+ * @typedef {Object} ILyricsText
45
+ * @property {string} text - The lyric text content.
46
+ * @property {number} [timestamp] - The timestamp associated with this lyric line.
47
+ */
48
+
49
+ /**
50
+ * @typedef {Object} TimestampFormat
51
+ * @property {number} notSynchronized - Indicates if the timestamp is not synchronized.
52
+ * @property {number} mpegFrameNumber - The MPEG frame number.
53
+ * @property {number} milliseconds - The time in milliseconds.
54
+ */
55
+
56
+ /**
57
+ * @typedef {Object} LyricsContentType
58
+ * @property {number} other - Content type for other.
59
+ * @property {number} lyrics - Content type for lyrics.
60
+ * @property {number} text - Content type for text.
61
+ * @property {number} movement_part - Content type for movement parts.
62
+ * @property {number} events - Content type for events.
63
+ * @property {number} chord - Content type for chords.
64
+ * @property {number} trivia_pop - Content type for trivia/pop.
65
+ */
66
+
67
+ /**
68
+ * @typedef {Object} ILyricsTag
69
+ * @property {string} [text] - The text content of the lyrics.
70
+ * @property {ILyricsText[]} syncText - An array of synchronized lyric text objects.
71
+ * @property {TimestampFormat} timeStampFormat - The format of the timestamp.
72
+ * @property {LyricsContentType} contentType - The type of lyrical content.
73
+ */
74
+
75
+ /**
76
+ * @typedef {Object} IRating
77
+ * @property {string} [source] - The source of the rating.
78
+ * @property {number} [rating] - The numeric rating value.
32
79
  */
33
80
 
34
81
  /**
@@ -36,19 +83,121 @@
36
83
  *
37
84
  * @template {IPictureTemplate<PictureDataType>} IPictureContent
38
85
  * @typedef {Object} ContentMetadataTemplate
39
- * @property {string|null} title - The title of the track.
40
- * @property {string|null} album - The name of the album.
41
- * @property {string|null} albumartist - The primary artist of the album.
42
- * @property {string[]} albumartists - An array of artists associated with the album.
43
- * @property {string[]} genre - An array of genres associated with the track.
44
- * @property {string[]} label - The record label.
45
- * @property {string[]} composer - The composer of the track.
46
- * @property {number|null} year - The release year.
47
- * @property {string|null} artist - The primary artist of the track.
48
- * @property {string[]} artists - An array of artists associated with the track.
49
- * @property {MediaNumber} disk - Disk information containing the current disk number and total disks.
50
- * @property {MediaNumber} track - Track information containing the current track number and total tracks.
86
+ * @property {MediaContentFetchData<PictureDataType>|null} [_fetchData]
87
+ * @property {string} [title] - The title of the track.
88
+ * @property {string} [album] - The name of the album.
89
+ * @property {string} [albumartist] - The primary artist of the album.
90
+ * @property {string[]} [albumartists] - An array of artists associated with the album.
91
+ * @property {string[]} [genre] - An array of genres associated with the track.
92
+ * @property {string[]} [label] - The record label.
93
+ * @property {string[]} [composer] - The composer of the track.
94
+ * @property {number} [year] - The release year.
95
+ * @property {string} [artist] - The primary artist of the track.
96
+ * @property {string[]} [artists] - An array of artists associated with the track.
97
+ * @property {MediaNumber} [disk] - Disk information containing the current disk number and total disks.
98
+ * @property {MediaNumber} [track] - Track information containing the current track number and total tracks.
51
99
  * @property {IPictureContent[]} [picture] - An array of picture objects containing album art.
100
+ * @property {string} [date] - The date.
101
+ * @property {string} [originaldate] - The original date.
102
+ * @property {number} [originalyear] - The original release year.
103
+ * @property {string} [releasedate] - The release date.
104
+ * @property {IComment[]} [comment] - An array of comments.
105
+ * @property {ILyricsTag[]} [lyrics] - An array of lyric tags.
106
+ * @property {string} [albumsort] - The sortable album name.
107
+ * @property {string} [titlesort] - The sortable title.
108
+ * @property {string} [work] - The work/composition name.
109
+ * @property {string} [artistsort] - The sortable artist name.
110
+ * @property {string} [albumartistsort] - The sortable album artist name.
111
+ * @property {string} [composersort] - The sortable composer name.
112
+ * @property {string[]} [lyricist] - An array of lyricists.
113
+ * @property {string[]} [writer] - An array of writers.
114
+ * @property {string[]} [conductor] - An array of conductors.
115
+ * @property {string[]} [remixer] - An array of remixers.
116
+ * @property {string[]} [arranger] - An array of arrangers.
117
+ * @property {string[]} [engineer] - An array of engineers.
118
+ * @property {string[]} [publisher] - An array of publishers.
119
+ * @property {string[]} [producer] - An array of producers.
120
+ * @property {string[]} [djmixer] - An array of DJ mixers.
121
+ * @property {string[]} [mixer] - An array of mixers.
122
+ * @property {string[]} [technician] - An array of technicians.
123
+ * @property {string} [grouping] - The grouping name.
124
+ * @property {string[]} [subtitle] - An array of subtitles.
125
+ * @property {string[]} [description] - An array of descriptions.
126
+ * @property {string} [longDescription] - A long description.
127
+ * @property {string[]} [discsubtitle] - An array of disc subtitles.
128
+ * @property {string} [totaltracks] - Total number of tracks.
129
+ * @property {string} [totaldiscs] - Total number of discs.
130
+ * @property {number} [movementTotal] - Total number of movements.
131
+ * @property {boolean} [compilation] - Whether it is a compilation.
132
+ * @property {IRating[]} [rating] - An array of ratings.
133
+ * @property {number} [bpm] - Beats per minute.
134
+ * @property {string} [mood] - The mood of the track.
135
+ * @property {string} [media] - The media type.
136
+ * @property {string[]} [catalognumber] - An array of catalog numbers.
137
+ * @property {string} [tvShow] - The TV show name.
138
+ * @property {string} [tvShowSort] - The sortable TV show name.
139
+ * @property {number} [tvSeason] - The TV season number.
140
+ * @property {number} [tvEpisode] - The TV episode number.
141
+ * @property {string} [tvEpisodeId] - The TV episode ID.
142
+ * @property {string} [tvNetwork] - The TV network.
143
+ * @property {boolean} [podcast] - Whether it is a podcast.
144
+ * @property {string} [podcasturl] - The podcast URL.
145
+ * @property {string} [releasestatus] - The release status.
146
+ * @property {string[]} [releasetype] - An array of release types.
147
+ * @property {string} [releasecountry] - The release country.
148
+ * @property {string} [script] - The script.
149
+ * @property {string} [language] - The language.
150
+ * @property {string} [copyright] - The copyright information.
151
+ * @property {string} [license] - The license information.
152
+ * @property {string} [encodedby] - The encoder.
153
+ * @property {string} [encodersettings] - The encoder settings.
154
+ * @property {boolean} [gapless] - Whether it is gapless.
155
+ * @property {string} [barcode] - The barcode.
156
+ * @property {string[]} [isrc] - An array of ISRC codes.
157
+ * @property {string} [asin] - The ASIN.
158
+ * @property {string} [website] - The website.
159
+ * @property {string[]} [performer_instrument] - An array of performers and their instruments.
160
+ * @property {number} [averageLevel] - The average loudness level.
161
+ * @property {number} [peakLevel] - The peak loudness level.
162
+ * @property {string[]} [notes] - An array of notes.
163
+ * @property {string} [originalalbum] - The original album.
164
+ * @property {string} [originalartist] - The original artist.
165
+ * @property {string} [key] - The initial key of the music in the file, e.g. "A Minor".
166
+ * @property {string[]} [category] - An array of categories.
167
+ * @property {number} [hdVideo] - HD video flag.
168
+ * @property {string[]} [keywords] - An array of keywords.
169
+ * @property {string} [movement] - The movement.
170
+ * @property {MediaNumber} [movementIndex] - The movement index.
171
+ * @property {string} [podcastId] - The podcast ID.
172
+ * @property {boolean} [showMovement] - Whether to show movement.
173
+ * @property {number} [stik] - The stik value.
174
+ * @property {number} [playCounter] - The play counter.
175
+ */
176
+
177
+ /**
178
+ * @typedef {Object} MediaContentFetchNativeData
179
+ * @property {string} id - The unique identifier for the native data.
180
+ * @property {any|Record<string|number|symbol, unknown>} [value] - The actual data value.
181
+ */
182
+
183
+ /**
184
+ * A record containing common metadata tags.
185
+ * @template {PictureDataType} PictureData
186
+ * @typedef {Record<string|number|symbol, unknown> & ContentMetadataTemplate<IPictureTemplate<PictureData>>} ICommonTagsResult
187
+ */
188
+
189
+ /**
190
+ * A mapping of keys to arrays of native metadata fetch data.
191
+ * @typedef {Record<string|number|symbol, MediaContentFetchNativeData[]>} INativeTags
192
+ */
193
+
194
+ /**
195
+ * @template {PictureDataType} PictureData
196
+ * @typedef {Object} MediaContentFetchData
197
+ * @property {ICommonTagsResult<PictureData>} common - The common metadata properties.
198
+ * @property {INativeTags} native - The native metadata properties.
199
+ * @property {unknown} format - The format of the media.
200
+ * @property {unknown} quality - The quality of the media.
52
201
  */
53
202
 
54
203
  /**
@@ -85,7 +234,7 @@
85
234
  * A promise that resolves to an object containing the extracted metadata.
86
235
  * @callback ParseMediaContentMetadata
87
236
  * @param {Blob} data - The raw file blob.
88
- * @returns {Promise<{ common: Partial<ContentMetadataTemplate<IPictureTemplate<PictureDataType>>> }>} A promise resolving to the common metadata properties.
237
+ * @returns {Promise<MediaContentFetchData<PictureDataType>>} A promise resolving to the common metadata properties.
89
238
  */
90
239
 
91
240
  /**
@@ -137,6 +286,35 @@ const checkObject = (/** @type {Object<string, any> | null} */ val, /** @type {s
137
286
  }
138
287
  };
139
288
 
289
+ const isUndefinedAllowed = (/** @type {any} */ v, forceNoUndefined = false) =>
290
+ !forceNoUndefined && typeof v === 'undefined';
291
+
292
+ const isString = (/** @type {string | null | undefined} */ v, forceNoUndefined = false) =>
293
+ isUndefinedAllowed(v, forceNoUndefined) ||
294
+ typeof v === 'string' ||
295
+ (!forceNoUndefined && v === null);
296
+
297
+ const isNumber = (/** @type {number | null | undefined} */ v, forceNoUndefined = false) =>
298
+ isUndefinedAllowed(v, forceNoUndefined) ||
299
+ typeof v === 'number' ||
300
+ (!forceNoUndefined && v === null);
301
+
302
+ const isBoolean = (/** @type {boolean | null | undefined} */ v, forceNoUndefined = false) =>
303
+ isUndefinedAllowed(v, forceNoUndefined) ||
304
+ typeof v === 'boolean' ||
305
+ (!forceNoUndefined && v === null);
306
+
307
+ /**
308
+ * @template {any} T
309
+ * @param {T[]|undefined} v
310
+ * @param {(value: T) => boolean} valueValidator
311
+ */
312
+ const isArray = (v, valueValidator) => {
313
+ if (isUndefinedAllowed(v)) return true;
314
+ checkFunction(valueValidator, 'valueValidator');
315
+ return Array.isArray(v) && v.every(valueValidator);
316
+ };
317
+
140
318
  /** @type {Map<string, number>} */
141
319
  const _blobCounter = new Map();
142
320
 
@@ -153,26 +331,6 @@ const getMediaContentBase = () => ({
153
331
  weight: 1,
154
332
  });
155
333
 
156
- /**
157
- * Returns a new object initialized with the default values for the media content metadata properties.
158
- * @returns {MediaContentMetadata<PictureDataType>}
159
- */
160
- const getMediaContentMetadata = () => ({
161
- title: null,
162
- album: null,
163
- albumartist: null,
164
- albumartists: [],
165
- genre: [],
166
- label: [],
167
- composer: [],
168
- year: null,
169
- artist: null,
170
- artists: [],
171
- disk: { no: null, of: null },
172
- track: { no: null, of: null },
173
- picture: [],
174
- });
175
-
176
334
  /**
177
335
  * Custom error class to provide detailed context during the content preparation process.
178
336
  * @extends Error
@@ -298,55 +456,213 @@ const revokeContentUrls = (content) => {
298
456
  };
299
457
 
300
458
  /**
301
- * Central logic of metadata validation.
302
- * @param {Partial<ContentMetadataTemplate<IPictureTemplate<string | Uint8Array<ArrayBufferLike>>>>} common - The object to be validated.
303
- * @param {boolean} isPartial - If true, properties can be undefined.
459
+ * @param {MediaContentFetchData<PictureDataType>|null} [fetchData] - The fetch object to be validated.
460
+ * @param {boolean} [returnsBoolean=false] - Ignores the throw error to return a boolean.
461
+ * @returns {boolean}
304
462
  */
305
- const validateMediaContent = (common, isPartial) => {
306
- checkObject(common, 'common');
307
- if (typeof isPartial !== 'boolean') throw new TypeError('Expected "isPartial" to be a boolean.');
463
+ const valFetchMediaContent = (fetchData, returnsBoolean = false) => {
464
+ if (objChecker.isJsonObject(fetchData)) {
465
+ if (objChecker.countObj(fetchData) !== 4) {
466
+ if (returnsBoolean) return false;
467
+ throw new TypeError(`Invalid fetch data: Object must be an MediaContentFetchData.`);
468
+ }
469
+ valMediaContentMetadata(fetchData.common);
470
+ checkObject(fetchData.native, '_fetchData.native');
308
471
 
309
- const isUndefinedAllowed = (/** @type {any} */ v) => isPartial && typeof v === 'undefined';
472
+ // @ts-ignore
473
+ checkObject(fetchData.format, '_fetchData.format');
474
+ // @ts-ignore
475
+ checkObject(fetchData.quality, '_fetchData.quality');
310
476
 
311
- const isString = (/** @type {string | null | undefined} */ v) =>
312
- isUndefinedAllowed(v) || typeof v === 'string' || v === null;
477
+ for (const id in fetchData.native) {
478
+ const item = fetchData.native[id];
313
479
 
314
- const isNumber = (/** @type {number | null | undefined}} */ v) =>
315
- isUndefinedAllowed(v) || typeof v === 'number' || v === null;
480
+ if (!isArray(item, (data) => typeof data.id === 'string')) {
481
+ if (returnsBoolean) return false;
482
+ throw new TypeError(`Invalid native data: "${id}" must be an array of native fetch data.`);
483
+ }
484
+ }
485
+ }
486
+ return true;
487
+ };
316
488
 
317
- const isArray = (
318
- /** @type {string[] | IPictureTemplate<string|Uint8Array>[] | undefined} */ v,
319
- /** @type {(value: any, index: number, array: any[]) => any} */ valueValidator,
320
- ) => {
321
- checkFunction(valueValidator, 'valueValidator');
322
- if (isUndefinedAllowed(v)) return true;
323
- if (Array.isArray(v) && v.every(valueValidator)) return true;
324
- return false;
325
- };
489
+ /**
490
+ * Central logic of metadata validation.
491
+ * @param {ContentMetadataTemplate<IPictureTemplate<string | Uint8Array>>} common - The object to be validated.
492
+ */
493
+ const valMediaContentMetadata = (common) => {
494
+ checkObject(common, 'common');
495
+ valFetchMediaContent(common._fetchData);
496
+
497
+ // --- Primitive Validation ---
326
498
 
327
- // Validate Primitives
328
499
  if (!isString(common.title))
329
500
  throw new TypeError('Invalid metadata: "title" must be a string or null.');
330
501
  if (!isString(common.album))
331
502
  throw new TypeError('Invalid metadata: "album" must be a string or null.');
332
503
  if (!isString(common.albumartist))
333
- throw new TypeError('Invalid metadata: "albumartist" must be a string or null.');
504
+ throw new TypeError('Invalid metadata: "albumartist" must be a a string or null.');
334
505
  if (!isString(common.artist))
335
506
  throw new TypeError('Invalid metadata: "artist" must be a string or null.');
507
+ if (!isBoolean(common.compilation))
508
+ throw new TypeError('Invalid metadata: "compilation" must be a boolean or null.');
509
+ if (!isBoolean(common.podcast))
510
+ throw new TypeError('Invalid metadata: "podcast" must be a boolean or null.');
511
+ if (!isBoolean(common.gapless))
512
+ throw new TypeError('Invalid metadata: "gapless" must be a boolean or null.');
513
+ if (!isBoolean(common.showMovement))
514
+ throw new TypeError('Invalid metadata: "showMovement" must be a boolean or null.');
515
+ if (!isNumber(common.averageLevel))
516
+ throw new TypeError('Invalid metadata: "averageLevel" must be a number or null.');
517
+ if (!isNumber(common.peakLevel))
518
+ throw new TypeError('Invalid metadata: "peakLevel" must be a number or null.');
519
+ if (!isNumber(common.hdVideo))
520
+ throw new TypeError('Invalid metadata: "hdVideo" must be a number or null.');
521
+ if (!isNumber(common.stik))
522
+ throw new TypeError('Invalid metadata: "stik" must be a number or null.');
523
+ if (!isNumber(common.playCounter))
524
+ throw new TypeError('Invalid metadata: "playCounter" must be a number or null.');
525
+ if (!isNumber(common.tvSeason))
526
+ throw new TypeError('Invalid metadata: "tvSeason" must be a number or null.');
527
+ if (!isNumber(common.tvEpisode))
528
+ throw new TypeError('Invalid metadata: "tvEpisode" must be a number or null.');
336
529
  if (!isNumber(common.year))
337
530
  throw new TypeError('Invalid metadata: "year" must be a number or null.');
531
+ if (!isNumber(common.bpm))
532
+ throw new TypeError('Invalid metadata: "bpm" must be a number or null.');
533
+ if (!isNumber(common.originalyear))
534
+ throw new TypeError('Invalid metadata: "originalyear" must be a number or null.');
535
+ if (!isNumber(common.movementTotal))
536
+ throw new TypeError('Invalid metadata: "movementTotal" must be a number or null.');
537
+ if (!isString(common.date))
538
+ throw new TypeError('Invalid metadata: "date" must be a string or null.');
539
+ if (!isString(common.originaldate))
540
+ throw new TypeError('Invalid metadata: "originaldate" must be a string or null.');
541
+ if (!isString(common.releasedate))
542
+ throw new TypeError('Invalid metadata: "releasedate" must be a string or null.');
543
+ if (!isString(common.albumsort))
544
+ throw new TypeError('Invalid metadata: "albumsort" must be a string or null.');
545
+ if (!isString(common.titlesort))
546
+ throw new TypeError('Invalid metadata: "titlesort" must be a string or null.');
547
+ if (!isString(common.work))
548
+ throw new TypeError('Invalid metadata: "work" must be a string or null.');
549
+ if (!isString(common.artistsort))
550
+ throw new TypeError('Invalid metadata: "artistsort" must be a string or null.');
551
+ if (!isString(common.albumartistsort))
552
+ throw new TypeError('Invalid metadata: "albumartistsort" must be a string or null.');
553
+ if (!isString(common.composersort))
554
+ throw new TypeError('Invalid metadata: "composersort" must be a string or null.');
555
+ if (!isString(common.grouping))
556
+ throw new TypeError('Invalid metadata: "grouping" must be a string or null.');
557
+ if (!isString(common.longDescription))
558
+ throw new TypeError('Invalid metadata: "longDescription" must be a string or null.');
559
+ if (!isString(common.totaltracks))
560
+ throw new TypeError('Invalid metadata: "totaltracks" must be a string or null.');
561
+ if (!isString(common.totaldiscs))
562
+ throw new TypeError('Invalid metadata: "totaldiscs" must be a string or null.');
563
+ if (!isString(common.mood))
564
+ throw new TypeError('Invalid metadata: "mood" must be a string or null.');
565
+ if (!isString(common.media))
566
+ throw new TypeError('Invalid metadata: "media" must be a string or null.');
567
+ if (!isString(common.tvShow))
568
+ throw new TypeError('Invalid metadata: "tvShow" must be a string or null.');
569
+ if (!isString(common.tvShowSort))
570
+ throw new TypeError('Invalid metadata: "tvShowSort" must be a string or null.');
571
+ if (!isString(common.tvEpisodeId))
572
+ throw new TypeError('Invalid metadata: "tvEpisodeId" must be a string or null.');
573
+ if (!isString(common.tvNetwork))
574
+ throw new TypeError('Invalid metadata: "tvNetwork" must be a string or null.');
575
+ if (!isString(common.podcasturl))
576
+ throw new TypeError('Invalid metadata: "podcasturl" must be a string or null.');
577
+ if (!isString(common.releasestatus))
578
+ throw new TypeError('Invalid metadata: "releasestatus" must be a string or null.');
579
+ if (!isString(common.releasecountry))
580
+ throw new TypeError('Invalid metadata: "releasecountry" must be a string or null.');
581
+ if (!isString(common.script))
582
+ throw new TypeError('Invalid metadata: "script" must be a string or null.');
583
+ if (!isString(common.language))
584
+ throw new TypeError('Invalid metadata: "language" must be a string or null.');
585
+ if (!isString(common.copyright))
586
+ throw new TypeError('Invalid metadata: "copyright" must be a string or null.');
587
+ if (!isString(common.license))
588
+ throw new TypeError('Invalid metadata: "license" must be a string or null.');
589
+ if (!isString(common.encodedby))
590
+ throw new TypeError('Invalid metadata: "encodedby" must be a string or null.');
591
+ if (!isString(common.encodersettings))
592
+ throw new TypeError('Invalid metadata: "encodersettings" must be a string or null.');
593
+ if (!isString(common.barcode))
594
+ throw new TypeError('Invalid metadata: "barcode" must be a string or null.');
595
+ if (!isString(common.asin))
596
+ throw new TypeError('Invalid metadata: "asin" must be a string or null.');
597
+ if (!isString(common.website))
598
+ throw new TypeError('Invalid metadata: "website" must be a string or null.');
599
+ if (!isString(common.originalalbum))
600
+ throw new TypeError('Invalid metadata: "originalalbum" must be a string or null.');
601
+ if (!isString(common.originalartist))
602
+ throw new TypeError('Invalid metadata: "originalartist" must be a string or null.');
603
+ if (!isString(common.key))
604
+ throw new TypeError('Invalid metadata: "key" must be a string or null.');
605
+ if (!isString(common.movement))
606
+ throw new TypeError('Invalid metadata: "movement" must be a string or null.');
607
+ if (!isString(common.podcastId))
608
+ throw new TypeError('Invalid metadata: "podcastId" must be a string or null.');
338
609
 
339
610
  // Validate Arrays
340
- if (!isArray(common.albumartists, (value) => typeof value === 'string'))
341
- throw new TypeError('Invalid metadata: "albumartists" must be an array of string.');
342
- if (!isArray(common.genre, (value) => typeof value === 'string'))
343
- throw new TypeError('Invalid metadata: "genre" must be an array of string.');
344
- if (!isArray(common.label, (value) => typeof value === 'string'))
345
- throw new TypeError('Invalid metadata: "label" must be an array of string.');
346
- if (!isArray(common.composer, (value) => typeof value === 'string'))
347
- throw new TypeError('Invalid metadata: "composer" must be an array of string.');
348
- if (!isArray(common.artists, (value) => typeof value === 'string'))
349
- throw new TypeError('Invalid metadata: "artists" must be an array of string.');
611
+ if (!isArray(common.albumartists, (v) => typeof v === 'string'))
612
+ throw new TypeError('Invalid metadata: "albumartists" must be an array of strings.');
613
+ if (!isArray(common.genre, (v) => typeof v === 'string'))
614
+ throw new TypeError('Invalid metadata: "genre" must be an array of strings.');
615
+ if (!isArray(common.label, (v) => typeof v === 'string'))
616
+ throw new TypeError('Invalid metadata: "label" must be an array of strings.');
617
+ if (!isArray(common.composer, (v) => typeof v === 'string'))
618
+ throw new TypeError('Invalid metadata: "composer" must be an array of strings.');
619
+ if (!isArray(common.artists, (v) => typeof v === 'string'))
620
+ throw new TypeError('Invalid metadata: "artists" must be an array of strings.');
621
+ if (!isArray(common.lyricist, (v) => typeof v === 'string'))
622
+ throw new TypeError('Invalid metadata: "lyricist" must be an array of strings.');
623
+ if (!isArray(common.writer, (v) => typeof v === 'string'))
624
+ throw new TypeError('Invalid metadata: "writer" must be an array of strings.');
625
+ if (!isArray(common.conductor, (v) => typeof v === 'string'))
626
+ throw new TypeError('Invalid metadata: "conductor" must be an array of strings.');
627
+ if (!isArray(common.remixer, (v) => typeof v === 'string'))
628
+ throw new TypeError('Invalid metadata: "remixer" must be an array of strings.');
629
+ if (!isArray(common.arranger, (v) => typeof v === 'string'))
630
+ throw new TypeError('Invalid metadata: "arranger" must be an array of strings.');
631
+ if (!isArray(common.engineer, (v) => typeof v === 'string'))
632
+ throw new TypeError('Invalid metadata: "engineer" must be an array of strings.');
633
+ if (!isArray(common.publisher, (v) => typeof v === 'string'))
634
+ throw new TypeError('Invalid metadata: "publisher" must be an array of strings.');
635
+ if (!isArray(common.producer, (v) => typeof v === 'string'))
636
+ throw new TypeError('Invalid metadata: "producer" must be an array of strings.');
637
+ if (!isArray(common.djmixer, (v) => typeof v === 'string'))
638
+ throw new TypeError('Invalid metadata: "djmixer" must be an array of strings.');
639
+ if (!isArray(common.mixer, (v) => typeof v === 'string'))
640
+ throw new TypeError('Invalid metadata: "mixer" must be an array of strings.');
641
+ if (!isArray(common.technician, (v) => typeof v === 'string'))
642
+ throw new TypeError('Invalid metadata: "technician" must be an array of strings.');
643
+ if (!isArray(common.subtitle, (v) => typeof v === 'string'))
644
+ throw new TypeError('Invalid metadata: "subtitle" must be an array of strings.');
645
+ if (!isArray(common.description, (v) => typeof v === 'string'))
646
+ throw new TypeError('Invalid metadata: "description" must be an array of strings.');
647
+ if (!isArray(common.discsubtitle, (v) => typeof v === 'string'))
648
+ throw new TypeError('Invalid metadata: "discsubtitle" must be an array of strings.');
649
+ if (!isArray(common.releasetype, (v) => typeof v === 'string'))
650
+ throw new TypeError('Invalid metadata: "releasetype" must be an array of strings.');
651
+ if (!isArray(common.isrc, (v) => typeof v === 'string'))
652
+ throw new TypeError('Invalid metadata: "isrc" must be an array of strings.');
653
+ if (!isArray(common.performer_instrument, (v) => typeof v === 'string'))
654
+ throw new TypeError('Invalid metadata: "performer_instrument" must be an array of strings.');
655
+ if (!isArray(common.notes, (v) => typeof v === 'string'))
656
+ throw new TypeError('Invalid metadata: "notes" must be an array of strings.');
657
+ if (!isArray(common.category, (v) => typeof v === 'string'))
658
+ throw new TypeError('Invalid metadata: "category" must be an array of strings.');
659
+ if (!isArray(common.keywords, (v) => typeof v === 'string'))
660
+ throw new TypeError('Invalid metadata: "keywords" must be an array of strings.');
661
+ if (!isArray(common.catalognumber, (v) => typeof v === 'string'))
662
+ throw new TypeError('Invalid metadata: "catalognumber" must be an array of strings.');
663
+
664
+ // Validate Complex Arrays
665
+
350
666
  if (
351
667
  !isArray(
352
668
  common.picture,
@@ -362,19 +678,73 @@ const validateMediaContent = (common, isPartial) => {
362
678
  )
363
679
  throw new TypeError('Invalid metadata: "picture" must be an array of pictures.');
364
680
 
681
+ if (
682
+ !isArray(
683
+ common.comment,
684
+ (v) =>
685
+ v !== null &&
686
+ typeof v === 'object' &&
687
+ isString(v.descriptor) &&
688
+ isString(v.language) &&
689
+ isString(v.text),
690
+ )
691
+ )
692
+ throw new TypeError('Invalid metadata: "comment" must be an array of valid IComment objects.');
693
+
694
+ if (
695
+ !isArray(common.lyrics, (v) => {
696
+ const isLyricsContentType = (/** @type {LyricsContentType} */ v) =>
697
+ v !== null &&
698
+ typeof v === 'object' &&
699
+ isNumber(v.other, true) &&
700
+ isNumber(v.lyrics, true) &&
701
+ isNumber(v.text, true) &&
702
+ isNumber(v.movement_part, true) &&
703
+ isNumber(v.events, true) &&
704
+ isNumber(v.chord, true) &&
705
+ isNumber(v.trivia_pop, true);
706
+
707
+ const isTimestampFormat = (/** @type {TimestampFormat} */ v) =>
708
+ v !== null &&
709
+ typeof v === 'object' &&
710
+ isNumber(v.notSynchronized, true) &&
711
+ isNumber(v.mpegFrameNumber, true) &&
712
+ isNumber(v.milliseconds, true);
713
+
714
+ return (
715
+ v !== null &&
716
+ typeof v === 'object' &&
717
+ isString(v.text) &&
718
+ isArray(
719
+ v.syncText,
720
+ (v) =>
721
+ v !== null && typeof v === 'object' && isString(v.text, true) && isNumber(v.timestamp),
722
+ ) &&
723
+ isTimestampFormat(v.timeStampFormat) &&
724
+ isLyricsContentType(v.contentType)
725
+ );
726
+ })
727
+ )
728
+ throw new TypeError('Invalid metadata: "lyrics" must be an array of valid ILyricsTag objects.');
729
+
730
+ if (
731
+ !isArray(
732
+ common.rating,
733
+ (v) => v !== null && typeof v === 'object' && isString(v.source) && isNumber(v.rating),
734
+ )
735
+ )
736
+ throw new TypeError('Invalid metadata: "rating" must be an array of valid IRating objects.');
737
+
738
+ // Validate Nested Objects (Disk, Track, Movement, etc.)
739
+
365
740
  /**
366
741
  * Validate Nested Objects (Disk and Track)
367
742
  * @param {string} name
368
743
  * @param {MediaNumber|null} [info]
369
744
  */
370
745
  const validateTrackInfo = (name, info) => {
371
- checkString(name, 'name');
372
- if (isUndefinedAllowed(info)) return; // Ignores if partial
373
-
374
- if (info === undefined || info === null) {
375
- throw new TypeError(`Invalid metadata: "${name}" is required.`);
376
- }
377
-
746
+ if (isUndefinedAllowed(info)) return;
747
+ if (info === null) return;
378
748
  if (typeof info !== 'object')
379
749
  throw new TypeError(`Invalid metadata: "${name}" must be an object or null.`);
380
750
  if (typeof info.no !== 'number' && info.no !== null)
@@ -385,26 +755,7 @@ const validateMediaContent = (common, isPartial) => {
385
755
 
386
756
  validateTrackInfo('disk', common.disk);
387
757
  validateTrackInfo('track', common.track);
388
- };
389
-
390
- /**
391
- * Helper to validate types within the media content metadata object.
392
- * This ensures that if a property is present, it matches the expected type.
393
- * @param {ContentMetadataTemplate<IPictureTemplate<string | Uint8Array<ArrayBufferLike>>>} common
394
- */
395
- const valMediaContentMetadata = (common) => {
396
- checkObject(common, 'common');
397
- return validateMediaContent(common, false);
398
- };
399
-
400
- /**
401
- * Helper to validate types within the media content metadata object.
402
- * Allows the absence of properties (useful for updates/patch).
403
- * @param {Partial<ContentMetadataTemplate<IPictureTemplate<string | Uint8Array<ArrayBufferLike>>>>} common
404
- */
405
- const valMediaContentMetadataPartial = (common) => {
406
- checkObject(common, 'common');
407
- return validateMediaContent(common, true);
758
+ validateTrackInfo('movementIndex', common.movementIndex);
408
759
  };
409
760
 
410
761
  /**
@@ -437,32 +788,173 @@ const extractMediaId3Tags = async (url, parseFile, convertBase64toBlob = true) =
437
788
 
438
789
  // 4. Complete Validation of the parsed metadata structure
439
790
  if (!metadata || typeof metadata.common !== 'object')
440
- throw new TypeError('Invalid metadata: "common" property is missing or not an object.');
791
+ throw new TypeError('Invalid metadata: "common" property is missing.');
792
+ if (typeof metadata.common['performer:instrument'] !== undefined)
793
+ // @ts-ignore
794
+ metadata.common.performer_instrument = metadata.common['performer:instrument'];
795
+ valFetchMediaContent(metadata);
441
796
 
442
797
  const common = metadata.common;
443
- valMediaContentMetadataPartial(common);
798
+ valMediaContentMetadata(common);
799
+
800
+ /**
801
+ * @type {(value: [string, any]) => boolean}
802
+ */
803
+ const filterContent = ([_, value]) =>
804
+ Array.isArray(value)
805
+ ? value.length > 0
806
+ : objChecker.isJsonObject(value)
807
+ ? (() => {
808
+ value;
809
+ const d = Object.fromEntries(Object.entries(value).filter(filterContent));
810
+ const amount = objChecker.countObj(d);
811
+ return amount > 0;
812
+ })()
813
+ : value !== null;
814
+
815
+ // Fix blob values.
816
+ const picture =
817
+ common?.picture?.map((value) => ({
818
+ ...value,
819
+ data: convertBase64toBlob ? convertToBlobUrl(value.data, value.format) : value.data,
820
+ })) ?? [];
821
+
822
+ metadata.common = {
823
+ ...metadata.common,
824
+ picture: picture.map((value) => ({
825
+ ...value,
826
+ data: '',
827
+ })),
828
+ };
829
+
830
+ // Sanitize metadata.native to prevent Uint8Array leakage
831
+ if (convertBase64toBlob) {
832
+ Object.keys(metadata.native).forEach((key) => {
833
+ metadata.native[key] = metadata.native[key].map((item) => {
834
+ // Check if item.value is an object and contains a 'data' property that is a Uint8Array
835
+ if (
836
+ item.value &&
837
+ typeof item.value === 'object' &&
838
+ item.value.data instanceof Uint8Array
839
+ ) {
840
+ const index =
841
+ common?.picture?.findIndex(
842
+ (item2) =>
843
+ item2.data === item.value.data &&
844
+ item2.format === item.value.format &&
845
+ item2.description === item.value.description &&
846
+ item2.name === item.value.name,
847
+ ) ?? -1;
848
+ const data = picture[index];
849
+ if (data) {
850
+ return { ...item, value: { ...item.value, data: '' } };
851
+ }
852
+ }
853
+ return item;
854
+ });
855
+ });
856
+ }
444
857
 
445
858
  // 5. Return the specific metadata fields requested
446
859
  // We structure the return to match the MediaContentMetadata typedef
447
- return {
448
- title: common?.title ?? null,
449
- album: common?.album ?? null,
450
- albumartist: common?.albumartist ?? null,
451
- albumartists: common?.albumartists ?? [],
452
- genre: common?.genre ?? [],
453
- label: common?.label ?? [],
454
- composer: common?.composer ?? [],
455
- year: common?.year ?? null,
456
- artist: common?.artist ?? null,
457
- artists: common?.artists ?? [],
458
- disk: common?.disk ? { no: common.disk.no, of: common.disk.of } : { no: null, of: null },
459
- track: common?.track ? { no: common.track.no, of: common.track.of } : { no: null, of: null },
460
- picture:
461
- common?.picture?.map((value) => ({
462
- ...value,
463
- data: convertBase64toBlob ? convertToBlobUrl(value.data, value.format) : value.data,
464
- })) ?? [],
465
- };
860
+ /** @type {MediaContentMetadata<PictureDataType>} */
861
+ const result = Object.fromEntries(
862
+ Object.entries({
863
+ title: common?.title ?? null,
864
+ album: common?.album ?? null,
865
+ albumartist: common?.albumartist ?? null,
866
+ albumartists: common?.albumartists ?? [],
867
+ genre: common?.genre ?? [],
868
+ label: common?.label ?? [],
869
+ composer: common?.composer ?? [],
870
+ year: common?.year ?? null,
871
+ artist: common?.artist ?? null,
872
+ artists: common?.artists ?? [],
873
+ disk: common?.disk ? { no: common.disk.no, of: common.disk.of } : null,
874
+ track: common?.track ? { no: common.track.no, of: common.track.of } : null,
875
+ movementIndex: common?.movementIndex
876
+ ? { no: common.movementIndex.no, of: common.movementIndex.of }
877
+ : null,
878
+ picture,
879
+ date: common?.date ?? null,
880
+ originaldate: common?.originaldate ?? null,
881
+ originalyear: common?.originalyear ?? null,
882
+ releasedate: common?.releasedate ?? null,
883
+ comment: common?.comment ?? [],
884
+ lyrics: common?.lyrics ?? [],
885
+ albumsort: common?.albumsort ?? null,
886
+ titlesort: common?.titlesort ?? null,
887
+ work: common?.work ?? null,
888
+ artistsort: common?.artistsort ?? null,
889
+ albumartistsort: common?.albumartistsort ?? null,
890
+ composersort: common?.composersort ?? null,
891
+ lyricist: common?.lyricist ?? [],
892
+ writer: common?.writer ?? [],
893
+ conductor: common?.conductor ?? [],
894
+ remixer: common?.remixer ?? [],
895
+ arranger: common?.arranger ?? [],
896
+ engineer: common?.engineer ?? [],
897
+ publisher: common?.publisher ?? [],
898
+ producer: common?.producer ?? [],
899
+ djmixer: common?.djmixer ?? [],
900
+ mixer: common?.mixer ?? [],
901
+ technician: common?.technician ?? [],
902
+ grouping: common?.grouping ?? null,
903
+ subtitle: common?.subtitle ?? [],
904
+ description: common?.description ?? [],
905
+ longDescription: common?.longDescription ?? null,
906
+ discsubtitle: common?.discsubtitle ?? [],
907
+ totaltracks: common?.totaltracks ?? null,
908
+ totaldiscs: common?.totaldiscs ?? null,
909
+ movementTotal: common?.movementTotal ?? null,
910
+ compilation: common?.compilation ?? null,
911
+ rating: common?.rating ?? [],
912
+ bpm: common?.bpm ?? null,
913
+ mood: common?.mood ?? null,
914
+ media: common?.media ?? null,
915
+ catalognumber: common?.catalognumber ?? [],
916
+ tvShow: common?.tvShow ?? null,
917
+ tvShowSort: common?.tvShowSort ?? null,
918
+ tvSeason: common?.tvSeason ?? null,
919
+ tvEpisode: common?.tvEpisode ?? null,
920
+ tvEpisodeId: common?.tvEpisodeId ?? null,
921
+ tvNetwork: common?.tvNetwork ?? null,
922
+ podcast: common?.podcast ?? null,
923
+ podcasturl: common?.podcasturl ?? null,
924
+ releasestatus: common?.releasestatus ?? null,
925
+ releasetype: common?.releasetype ?? [],
926
+ releasecountry: common?.releasecountry ?? null,
927
+ script: common?.script ?? null,
928
+ language: common?.language ?? null,
929
+ copyright: common?.copyright ?? null,
930
+ license: common?.license ?? null,
931
+ encodedby: common?.encodedby ?? null,
932
+ encodersettings: common?.encodersettings ?? null,
933
+ gapless: common?.gapless ?? null,
934
+ barcode: common?.barcode ?? null,
935
+ isrc: common?.isrc ?? [],
936
+ asin: common?.asin ?? null,
937
+ website: common?.website ?? null,
938
+ performer_instrument: common?.performer_instrument ?? [],
939
+ averageLevel: common?.averageLevel ?? null,
940
+ peakLevel: common?.peakLevel ?? null,
941
+ notes: common?.notes ?? [],
942
+ originalalbum: common?.originalalbum ?? null,
943
+ originalartist: common?.originalartist ?? null,
944
+ key: common?.key ?? null,
945
+ category: common?.category ?? [],
946
+ hdVideo: common?.hdVideo ?? null,
947
+ keywords: common?.keywords ?? [],
948
+ movement: common?.movement ?? null,
949
+ podcastId: common?.podcastId ?? null,
950
+ showMovement: common?.showMovement ?? null,
951
+ stik: common?.stik ?? null,
952
+ playCounter: common?.playCounter ?? null,
953
+ }).filter(filterContent),
954
+ );
955
+
956
+ result._fetchData = metadata;
957
+ return result;
466
958
  } catch (error) {
467
959
  // Re-throwing the error allows the caller to handle specific failure cases
468
960
  throw error;
@@ -519,7 +1011,7 @@ const parseMediaMetadata = async (
519
1011
  metadata = {},
520
1012
  parseFile = (data) => {
521
1013
  if (!(data instanceof Blob)) throw new TypeError('Expected "data" to be a Blob.');
522
- return new Promise((resolve, reject) => reject(new TypeError('parseFile library not found.')));
1014
+ return new Promise((_, reject) => reject(new TypeError('parseFile library not found.')));
523
1015
  },
524
1016
  callbacks = {},
525
1017
  convertBase64toBlob = true,
@@ -570,11 +1062,11 @@ const parseMediaMetadata = async (
570
1062
 
571
1063
  const notifyError = (/** @type {Error} */ error, /** @type {LoadingErrorStage} */ stage) => {
572
1064
  if (!(error instanceof Error)) throw new TypeError('Expected "error" to be an Error.');
573
- checkString(stage, 'stage'); // Adicionado
1065
+ checkString(stage, 'stage');
574
1066
 
575
1067
  if (callbacks.onError) {
576
1068
  callbacks.onError({
577
- error,
1069
+ error: error instanceof Error ? error : new Error(String(error)),
578
1070
  url: url || (source instanceof HTMLMediaElement ? source.src : 'unknown'),
579
1071
  stage,
580
1072
  });
@@ -678,11 +1170,38 @@ const parseMediaMetadata = async (
678
1170
 
679
1171
  // 5. Final Merge Logic
680
1172
  // Priority: Manual Metadata (highest) > Extracted ID3 > Default values
1173
+
1174
+ /**
1175
+ * @template {Partial<MediaContent<PictureData>>} Metadata
1176
+ * @param {Metadata} meta
1177
+ */
1178
+ const mergeMetadata = (meta) => {
1179
+ const metadataEdit = { ...meta };
1180
+ if (objChecker.isJsonObject(metadataEdit._fetchData)) {
1181
+ if (objChecker.isJsonObject(extractedMetadata._fetchData)) {
1182
+ metadataEdit._fetchData = {
1183
+ ...extractedMetadata._fetchData,
1184
+ };
1185
+ }
1186
+ if (objChecker.isJsonObject(meta._fetchData)) {
1187
+ if (typeof meta._fetchData.common !== 'undefined')
1188
+ metadataEdit._fetchData.common = meta._fetchData.common;
1189
+ if (typeof meta._fetchData.native !== 'undefined')
1190
+ metadataEdit._fetchData.native = meta._fetchData.native;
1191
+ if (typeof meta._fetchData.format !== 'undefined')
1192
+ metadataEdit._fetchData.format = meta._fetchData.format;
1193
+ if (typeof meta._fetchData.quality !== 'undefined')
1194
+ metadataEdit._fetchData.quality = meta._fetchData.quality;
1195
+ }
1196
+ }
1197
+ return metadataEdit;
1198
+ };
1199
+
681
1200
  const finalContent = {
682
1201
  ...baseData,
683
- ...defaultMetadata,
1202
+ ...mergeMetadata(defaultMetadata),
684
1203
  ...extractedMetadata,
685
- ...metadata,
1204
+ ...mergeMetadata(metadata),
686
1205
  // Explicitly ensure title and artist are resolved from the hierarchy
687
1206
  title: extractedMetadata.title || metadata.title || getFallbackTitleFromUrl(url),
688
1207
  artist:
@@ -693,28 +1212,21 @@ const parseMediaMetadata = async (
693
1212
 
694
1213
  // Notify Success
695
1214
  if (callbacks.onProgress) {
696
- callbacks.onProgress({
697
- status: 'success',
698
- stage: 'COMPLETE',
699
- url: url,
700
- });
1215
+ callbacks.onProgress({ status: 'success', stage: 'COMPLETE', url: url });
701
1216
  }
702
1217
 
703
1218
  return /** @type {MediaContent<PictureData>} */ (finalContent);
704
1219
  } catch (err) {
705
1220
  // If it's already a MediaLoadingError, re-throw it.
706
1221
  // Otherwise, wrap it.
707
- if (err instanceof MediaLoadingError) {
708
- throw err;
709
- } else {
710
- const wrappedError = new MediaLoadingError(
711
- err instanceof Error ? err.message : 'UNKNOWN ERROR',
712
- url,
713
- 'INITIALIZING',
714
- );
715
- notifyError(wrappedError, 'INITIALIZING');
716
- throw wrappedError;
717
- }
1222
+ if (err instanceof MediaLoadingError) throw err;
1223
+ const wrappedError = new MediaLoadingError(
1224
+ err instanceof Error ? err.message : 'UNKNOWN ERROR',
1225
+ url,
1226
+ 'INITIALIZING',
1227
+ );
1228
+ notifyError(wrappedError, 'INITIALIZING');
1229
+ throw wrappedError;
718
1230
  }
719
1231
  };
720
1232
 
@@ -725,8 +1237,7 @@ exports.convertToBlobUrl = convertToBlobUrl;
725
1237
  exports.extractMediaId3Tags = extractMediaId3Tags;
726
1238
  exports.generateSimpleHash = generateSimpleHash;
727
1239
  exports.getMediaContentBase = getMediaContentBase;
728
- exports.getMediaContentMetadata = getMediaContentMetadata;
729
1240
  exports.parseMediaMetadata = parseMediaMetadata;
730
1241
  exports.revokeContentUrls = revokeContentUrls;
1242
+ exports.valFetchMediaContent = valFetchMediaContent;
731
1243
  exports.valMediaContentMetadata = valMediaContentMetadata;
732
- exports.valMediaContentMetadataPartial = valMediaContentMetadataPartial;