tiny-essentials 1.28.11 → 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,3 +1,4 @@
1
+ import { countObj, isJsonObject } from './objChecker.mjs';
1
2
  /**
2
3
  * A type representing either a static string or a function that returns a string, used to resolve an unknown artist's name.
3
4
  * @typedef {string|(() => string)} UnknownArtistGetter
@@ -22,26 +23,163 @@
22
23
  */
23
24
  /**
24
25
  * A numeric structure representing track or disk indexing.
25
- * @typedef {{no: number|null, of: number|null}} MediaNumber
26
+ * @typedef {{no?: number, of?: number}} MediaNumber
27
+ */
28
+ /**
29
+ * @typedef {Object} IComment
30
+ * @property {string} [descriptor] - A descriptor for the comment.
31
+ * @property {string} [language] - The language of the comment.
32
+ * @property {string} [text] - The text content of the comment.
33
+ */
34
+ /**
35
+ * @typedef {Object} ILyricsText
36
+ * @property {string} text - The lyric text content.
37
+ * @property {number} [timestamp] - The timestamp associated with this lyric line.
38
+ */
39
+ /**
40
+ * @typedef {Object} TimestampFormat
41
+ * @property {number} notSynchronized - Indicates if the timestamp is not synchronized.
42
+ * @property {number} mpegFrameNumber - The MPEG frame number.
43
+ * @property {number} milliseconds - The time in milliseconds.
44
+ */
45
+ /**
46
+ * @typedef {Object} LyricsContentType
47
+ * @property {number} other - Content type for other.
48
+ * @property {number} lyrics - Content type for lyrics.
49
+ * @property {number} text - Content type for text.
50
+ * @property {number} movement_part - Content type for movement parts.
51
+ * @property {number} events - Content type for events.
52
+ * @property {number} chord - Content type for chords.
53
+ * @property {number} trivia_pop - Content type for trivia/pop.
54
+ */
55
+ /**
56
+ * @typedef {Object} ILyricsTag
57
+ * @property {string} [text] - The text content of the lyrics.
58
+ * @property {ILyricsText[]} syncText - An array of synchronized lyric text objects.
59
+ * @property {TimestampFormat} timeStampFormat - The format of the timestamp.
60
+ * @property {LyricsContentType} contentType - The type of lyrical content.
61
+ */
62
+ /**
63
+ * @typedef {Object} IRating
64
+ * @property {string} [source] - The source of the rating.
65
+ * @property {number} [rating] - The numeric rating value.
26
66
  */
27
67
  /**
28
68
  * This metadata structure is modeled template.
29
69
  *
30
70
  * @template {IPictureTemplate<PictureDataType>} IPictureContent
31
71
  * @typedef {Object} ContentMetadataTemplate
32
- * @property {string|null} title - The title of the track.
33
- * @property {string|null} album - The name of the album.
34
- * @property {string|null} albumartist - The primary artist of the album.
35
- * @property {string[]} albumartists - An array of artists associated with the album.
36
- * @property {string[]} genre - An array of genres associated with the track.
37
- * @property {string[]} label - The record label.
38
- * @property {string[]} composer - The composer of the track.
39
- * @property {number|null} year - The release year.
40
- * @property {string|null} artist - The primary artist of the track.
41
- * @property {string[]} artists - An array of artists associated with the track.
42
- * @property {MediaNumber} disk - Disk information containing the current disk number and total disks.
43
- * @property {MediaNumber} track - Track information containing the current track number and total tracks.
72
+ * @property {MediaContentFetchData<PictureDataType>|null} [_fetchData]
73
+ * @property {string} [title] - The title of the track.
74
+ * @property {string} [album] - The name of the album.
75
+ * @property {string} [albumartist] - The primary artist of the album.
76
+ * @property {string[]} [albumartists] - An array of artists associated with the album.
77
+ * @property {string[]} [genre] - An array of genres associated with the track.
78
+ * @property {string[]} [label] - The record label.
79
+ * @property {string[]} [composer] - The composer of the track.
80
+ * @property {number} [year] - The release year.
81
+ * @property {string} [artist] - The primary artist of the track.
82
+ * @property {string[]} [artists] - An array of artists associated with the track.
83
+ * @property {MediaNumber} [disk] - Disk information containing the current disk number and total disks.
84
+ * @property {MediaNumber} [track] - Track information containing the current track number and total tracks.
44
85
  * @property {IPictureContent[]} [picture] - An array of picture objects containing album art.
86
+ * @property {string} [date] - The date.
87
+ * @property {string} [originaldate] - The original date.
88
+ * @property {number} [originalyear] - The original release year.
89
+ * @property {string} [releasedate] - The release date.
90
+ * @property {IComment[]} [comment] - An array of comments.
91
+ * @property {ILyricsTag[]} [lyrics] - An array of lyric tags.
92
+ * @property {string} [albumsort] - The sortable album name.
93
+ * @property {string} [titlesort] - The sortable title.
94
+ * @property {string} [work] - The work/composition name.
95
+ * @property {string} [artistsort] - The sortable artist name.
96
+ * @property {string} [albumartistsort] - The sortable album artist name.
97
+ * @property {string} [composersort] - The sortable composer name.
98
+ * @property {string[]} [lyricist] - An array of lyricists.
99
+ * @property {string[]} [writer] - An array of writers.
100
+ * @property {string[]} [conductor] - An array of conductors.
101
+ * @property {string[]} [remixer] - An array of remixers.
102
+ * @property {string[]} [arranger] - An array of arrangers.
103
+ * @property {string[]} [engineer] - An array of engineers.
104
+ * @property {string[]} [publisher] - An array of publishers.
105
+ * @property {string[]} [producer] - An array of producers.
106
+ * @property {string[]} [djmixer] - An array of DJ mixers.
107
+ * @property {string[]} [mixer] - An array of mixers.
108
+ * @property {string[]} [technician] - An array of technicians.
109
+ * @property {string} [grouping] - The grouping name.
110
+ * @property {string[]} [subtitle] - An array of subtitles.
111
+ * @property {string[]} [description] - An array of descriptions.
112
+ * @property {string} [longDescription] - A long description.
113
+ * @property {string[]} [discsubtitle] - An array of disc subtitles.
114
+ * @property {string} [totaltracks] - Total number of tracks.
115
+ * @property {string} [totaldiscs] - Total number of discs.
116
+ * @property {number} [movementTotal] - Total number of movements.
117
+ * @property {boolean} [compilation] - Whether it is a compilation.
118
+ * @property {IRating[]} [rating] - An array of ratings.
119
+ * @property {number} [bpm] - Beats per minute.
120
+ * @property {string} [mood] - The mood of the track.
121
+ * @property {string} [media] - The media type.
122
+ * @property {string[]} [catalognumber] - An array of catalog numbers.
123
+ * @property {string} [tvShow] - The TV show name.
124
+ * @property {string} [tvShowSort] - The sortable TV show name.
125
+ * @property {number} [tvSeason] - The TV season number.
126
+ * @property {number} [tvEpisode] - The TV episode number.
127
+ * @property {string} [tvEpisodeId] - The TV episode ID.
128
+ * @property {string} [tvNetwork] - The TV network.
129
+ * @property {boolean} [podcast] - Whether it is a podcast.
130
+ * @property {string} [podcasturl] - The podcast URL.
131
+ * @property {string} [releasestatus] - The release status.
132
+ * @property {string[]} [releasetype] - An array of release types.
133
+ * @property {string} [releasecountry] - The release country.
134
+ * @property {string} [script] - The script.
135
+ * @property {string} [language] - The language.
136
+ * @property {string} [copyright] - The copyright information.
137
+ * @property {string} [license] - The license information.
138
+ * @property {string} [encodedby] - The encoder.
139
+ * @property {string} [encodersettings] - The encoder settings.
140
+ * @property {boolean} [gapless] - Whether it is gapless.
141
+ * @property {string} [barcode] - The barcode.
142
+ * @property {string[]} [isrc] - An array of ISRC codes.
143
+ * @property {string} [asin] - The ASIN.
144
+ * @property {string} [website] - The website.
145
+ * @property {string[]} [performer_instrument] - An array of performers and their instruments.
146
+ * @property {number} [averageLevel] - The average loudness level.
147
+ * @property {number} [peakLevel] - The peak loudness level.
148
+ * @property {string[]} [notes] - An array of notes.
149
+ * @property {string} [originalalbum] - The original album.
150
+ * @property {string} [originalartist] - The original artist.
151
+ * @property {string} [key] - The initial key of the music in the file, e.g. "A Minor".
152
+ * @property {string[]} [category] - An array of categories.
153
+ * @property {number} [hdVideo] - HD video flag.
154
+ * @property {string[]} [keywords] - An array of keywords.
155
+ * @property {string} [movement] - The movement.
156
+ * @property {MediaNumber} [movementIndex] - The movement index.
157
+ * @property {string} [podcastId] - The podcast ID.
158
+ * @property {boolean} [showMovement] - Whether to show movement.
159
+ * @property {number} [stik] - The stik value.
160
+ * @property {number} [playCounter] - The play counter.
161
+ */
162
+ /**
163
+ * @typedef {Object} MediaContentFetchNativeData
164
+ * @property {string} id - The unique identifier for the native data.
165
+ * @property {any|Record<string|number|symbol, unknown>} [value] - The actual data value.
166
+ */
167
+ /**
168
+ * A record containing common metadata tags.
169
+ * @template {PictureDataType} PictureData
170
+ * @typedef {Record<string|number|symbol, unknown> & ContentMetadataTemplate<IPictureTemplate<PictureData>>} ICommonTagsResult
171
+ */
172
+ /**
173
+ * A mapping of keys to arrays of native metadata fetch data.
174
+ * @typedef {Record<string|number|symbol, MediaContentFetchNativeData[]>} INativeTags
175
+ */
176
+ /**
177
+ * @template {PictureDataType} PictureData
178
+ * @typedef {Object} MediaContentFetchData
179
+ * @property {ICommonTagsResult<PictureData>} common - The common metadata properties.
180
+ * @property {INativeTags} native - The native metadata properties.
181
+ * @property {unknown} format - The format of the media.
182
+ * @property {unknown} quality - The quality of the media.
45
183
  */
46
184
  /**
47
185
  * This metadata structure is modeled after the standard output of the
@@ -73,7 +211,7 @@
73
211
  * A promise that resolves to an object containing the extracted metadata.
74
212
  * @callback ParseMediaContentMetadata
75
213
  * @param {Blob} data - The raw file blob.
76
- * @returns {Promise<{ common: Partial<ContentMetadataTemplate<IPictureTemplate<PictureDataType>>> }>} A promise resolving to the common metadata properties.
214
+ * @returns {Promise<MediaContentFetchData<PictureDataType>>} A promise resolving to the common metadata properties.
77
215
  */
78
216
  /**
79
217
  * @typedef {Object} LoadingMediaProgress Represents the current state of a media loading operation, including its status, execution stage, and the URL being processed.
@@ -117,6 +255,27 @@ const checkObject = (/** @type {Object<string, any> | null} */ val, /** @type {s
117
255
  throw new TypeError(`Expected "${name}" to be a plain object, but received ${val === null ? 'null' : Array.isArray(val) ? 'array' : typeof val}.`);
118
256
  }
119
257
  };
258
+ const isUndefinedAllowed = (/** @type {any} */ v, forceNoUndefined = false) => !forceNoUndefined && typeof v === 'undefined';
259
+ const isString = (/** @type {string | null | undefined} */ v, forceNoUndefined = false) => isUndefinedAllowed(v, forceNoUndefined) ||
260
+ typeof v === 'string' ||
261
+ (!forceNoUndefined && v === null);
262
+ const isNumber = (/** @type {number | null | undefined} */ v, forceNoUndefined = false) => isUndefinedAllowed(v, forceNoUndefined) ||
263
+ typeof v === 'number' ||
264
+ (!forceNoUndefined && v === null);
265
+ const isBoolean = (/** @type {boolean | null | undefined} */ v, forceNoUndefined = false) => isUndefinedAllowed(v, forceNoUndefined) ||
266
+ typeof v === 'boolean' ||
267
+ (!forceNoUndefined && v === null);
268
+ /**
269
+ * @template {any} T
270
+ * @param {T[]|undefined} v
271
+ * @param {(value: T) => boolean} valueValidator
272
+ */
273
+ const isArray = (v, valueValidator) => {
274
+ if (isUndefinedAllowed(v))
275
+ return true;
276
+ checkFunction(valueValidator, 'valueValidator');
277
+ return Array.isArray(v) && v.every(valueValidator);
278
+ };
120
279
  /** @type {Map<string, number>} */
121
280
  export const _blobCounter = new Map();
122
281
  /**
@@ -131,25 +290,6 @@ export const getMediaContentBase = () => ({
131
290
  duration: 0,
132
291
  weight: 1,
133
292
  });
134
- /**
135
- * Returns a new object initialized with the default values for the media content metadata properties.
136
- * @returns {MediaContentMetadata<PictureDataType>}
137
- */
138
- export const getMediaContentMetadata = () => ({
139
- title: null,
140
- album: null,
141
- albumartist: null,
142
- albumartists: [],
143
- genre: [],
144
- label: [],
145
- composer: [],
146
- year: null,
147
- artist: null,
148
- artists: [],
149
- disk: { no: null, of: null },
150
- track: { no: null, of: null },
151
- picture: [],
152
- });
153
293
  /**
154
294
  * Custom error class to provide detailed context during the content preparation process.
155
295
  * @extends Error
@@ -267,50 +407,207 @@ export const revokeContentUrls = (content) => {
267
407
  });
268
408
  }
269
409
  };
410
+ /**
411
+ * @param {MediaContentFetchData<PictureDataType>|null} [fetchData] - The fetch object to be validated.
412
+ * @param {boolean} [returnsBoolean=false] - Ignores the throw error to return a boolean.
413
+ * @returns {boolean}
414
+ */
415
+ export const valFetchMediaContent = (fetchData, returnsBoolean = false) => {
416
+ if (isJsonObject(fetchData)) {
417
+ if (countObj(fetchData) !== 4) {
418
+ if (returnsBoolean)
419
+ return false;
420
+ throw new TypeError(`Invalid fetch data: Object must be an MediaContentFetchData.`);
421
+ }
422
+ valMediaContentMetadata(fetchData.common);
423
+ checkObject(fetchData.native, '_fetchData.native');
424
+ // @ts-ignore
425
+ checkObject(fetchData.format, '_fetchData.format');
426
+ // @ts-ignore
427
+ checkObject(fetchData.quality, '_fetchData.quality');
428
+ for (const id in fetchData.native) {
429
+ const item = fetchData.native[id];
430
+ if (!isArray(item, (data) => typeof data.id === 'string')) {
431
+ if (returnsBoolean)
432
+ return false;
433
+ throw new TypeError(`Invalid native data: "${id}" must be an array of native fetch data.`);
434
+ }
435
+ }
436
+ }
437
+ return true;
438
+ };
270
439
  /**
271
440
  * Central logic of metadata validation.
272
- * @param {Partial<ContentMetadataTemplate<IPictureTemplate<string | Uint8Array<ArrayBufferLike>>>>} common - The object to be validated.
273
- * @param {boolean} isPartial - If true, properties can be undefined.
441
+ * @param {ContentMetadataTemplate<IPictureTemplate<string | Uint8Array>>} common - The object to be validated.
274
442
  */
275
- const validateMediaContent = (common, isPartial) => {
443
+ export const valMediaContentMetadata = (common) => {
276
444
  checkObject(common, 'common');
277
- if (typeof isPartial !== 'boolean')
278
- throw new TypeError('Expected "isPartial" to be a boolean.');
279
- const isUndefinedAllowed = (/** @type {any} */ v) => isPartial && typeof v === 'undefined';
280
- const isString = (/** @type {string | null | undefined} */ v) => isUndefinedAllowed(v) || typeof v === 'string' || v === null;
281
- const isNumber = (/** @type {number | null | undefined}} */ v) => isUndefinedAllowed(v) || typeof v === 'number' || v === null;
282
- const isArray = (
283
- /** @type {string[] | IPictureTemplate<string|Uint8Array>[] | undefined} */ v,
284
- /** @type {(value: any, index: number, array: any[]) => any} */ valueValidator) => {
285
- checkFunction(valueValidator, 'valueValidator');
286
- if (isUndefinedAllowed(v))
287
- return true;
288
- if (Array.isArray(v) && v.every(valueValidator))
289
- return true;
290
- return false;
291
- };
292
- // Validate Primitives
445
+ valFetchMediaContent(common._fetchData);
446
+ // --- Primitive Validation ---
293
447
  if (!isString(common.title))
294
448
  throw new TypeError('Invalid metadata: "title" must be a string or null.');
295
449
  if (!isString(common.album))
296
450
  throw new TypeError('Invalid metadata: "album" must be a string or null.');
297
451
  if (!isString(common.albumartist))
298
- throw new TypeError('Invalid metadata: "albumartist" must be a string or null.');
452
+ throw new TypeError('Invalid metadata: "albumartist" must be a a string or null.');
299
453
  if (!isString(common.artist))
300
454
  throw new TypeError('Invalid metadata: "artist" must be a string or null.');
455
+ if (!isBoolean(common.compilation))
456
+ throw new TypeError('Invalid metadata: "compilation" must be a boolean or null.');
457
+ if (!isBoolean(common.podcast))
458
+ throw new TypeError('Invalid metadata: "podcast" must be a boolean or null.');
459
+ if (!isBoolean(common.gapless))
460
+ throw new TypeError('Invalid metadata: "gapless" must be a boolean or null.');
461
+ if (!isBoolean(common.showMovement))
462
+ throw new TypeError('Invalid metadata: "showMovement" must be a boolean or null.');
463
+ if (!isNumber(common.averageLevel))
464
+ throw new TypeError('Invalid metadata: "averageLevel" must be a number or null.');
465
+ if (!isNumber(common.peakLevel))
466
+ throw new TypeError('Invalid metadata: "peakLevel" must be a number or null.');
467
+ if (!isNumber(common.hdVideo))
468
+ throw new TypeError('Invalid metadata: "hdVideo" must be a number or null.');
469
+ if (!isNumber(common.stik))
470
+ throw new TypeError('Invalid metadata: "stik" must be a number or null.');
471
+ if (!isNumber(common.playCounter))
472
+ throw new TypeError('Invalid metadata: "playCounter" must be a number or null.');
473
+ if (!isNumber(common.tvSeason))
474
+ throw new TypeError('Invalid metadata: "tvSeason" must be a number or null.');
475
+ if (!isNumber(common.tvEpisode))
476
+ throw new TypeError('Invalid metadata: "tvEpisode" must be a number or null.');
301
477
  if (!isNumber(common.year))
302
478
  throw new TypeError('Invalid metadata: "year" must be a number or null.');
479
+ if (!isNumber(common.bpm))
480
+ throw new TypeError('Invalid metadata: "bpm" must be a number or null.');
481
+ if (!isNumber(common.originalyear))
482
+ throw new TypeError('Invalid metadata: "originalyear" must be a number or null.');
483
+ if (!isNumber(common.movementTotal))
484
+ throw new TypeError('Invalid metadata: "movementTotal" must be a number or null.');
485
+ if (!isString(common.date))
486
+ throw new TypeError('Invalid metadata: "date" must be a string or null.');
487
+ if (!isString(common.originaldate))
488
+ throw new TypeError('Invalid metadata: "originaldate" must be a string or null.');
489
+ if (!isString(common.releasedate))
490
+ throw new TypeError('Invalid metadata: "releasedate" must be a string or null.');
491
+ if (!isString(common.albumsort))
492
+ throw new TypeError('Invalid metadata: "albumsort" must be a string or null.');
493
+ if (!isString(common.titlesort))
494
+ throw new TypeError('Invalid metadata: "titlesort" must be a string or null.');
495
+ if (!isString(common.work))
496
+ throw new TypeError('Invalid metadata: "work" must be a string or null.');
497
+ if (!isString(common.artistsort))
498
+ throw new TypeError('Invalid metadata: "artistsort" must be a string or null.');
499
+ if (!isString(common.albumartistsort))
500
+ throw new TypeError('Invalid metadata: "albumartistsort" must be a string or null.');
501
+ if (!isString(common.composersort))
502
+ throw new TypeError('Invalid metadata: "composersort" must be a string or null.');
503
+ if (!isString(common.grouping))
504
+ throw new TypeError('Invalid metadata: "grouping" must be a string or null.');
505
+ if (!isString(common.longDescription))
506
+ throw new TypeError('Invalid metadata: "longDescription" must be a string or null.');
507
+ if (!isString(common.totaltracks))
508
+ throw new TypeError('Invalid metadata: "totaltracks" must be a string or null.');
509
+ if (!isString(common.totaldiscs))
510
+ throw new TypeError('Invalid metadata: "totaldiscs" must be a string or null.');
511
+ if (!isString(common.mood))
512
+ throw new TypeError('Invalid metadata: "mood" must be a string or null.');
513
+ if (!isString(common.media))
514
+ throw new TypeError('Invalid metadata: "media" must be a string or null.');
515
+ if (!isString(common.tvShow))
516
+ throw new TypeError('Invalid metadata: "tvShow" must be a string or null.');
517
+ if (!isString(common.tvShowSort))
518
+ throw new TypeError('Invalid metadata: "tvShowSort" must be a string or null.');
519
+ if (!isString(common.tvEpisodeId))
520
+ throw new TypeError('Invalid metadata: "tvEpisodeId" must be a string or null.');
521
+ if (!isString(common.tvNetwork))
522
+ throw new TypeError('Invalid metadata: "tvNetwork" must be a string or null.');
523
+ if (!isString(common.podcasturl))
524
+ throw new TypeError('Invalid metadata: "podcasturl" must be a string or null.');
525
+ if (!isString(common.releasestatus))
526
+ throw new TypeError('Invalid metadata: "releasestatus" must be a string or null.');
527
+ if (!isString(common.releasecountry))
528
+ throw new TypeError('Invalid metadata: "releasecountry" must be a string or null.');
529
+ if (!isString(common.script))
530
+ throw new TypeError('Invalid metadata: "script" must be a string or null.');
531
+ if (!isString(common.language))
532
+ throw new TypeError('Invalid metadata: "language" must be a string or null.');
533
+ if (!isString(common.copyright))
534
+ throw new TypeError('Invalid metadata: "copyright" must be a string or null.');
535
+ if (!isString(common.license))
536
+ throw new TypeError('Invalid metadata: "license" must be a string or null.');
537
+ if (!isString(common.encodedby))
538
+ throw new TypeError('Invalid metadata: "encodedby" must be a string or null.');
539
+ if (!isString(common.encodersettings))
540
+ throw new TypeError('Invalid metadata: "encodersettings" must be a string or null.');
541
+ if (!isString(common.barcode))
542
+ throw new TypeError('Invalid metadata: "barcode" must be a string or null.');
543
+ if (!isString(common.asin))
544
+ throw new TypeError('Invalid metadata: "asin" must be a string or null.');
545
+ if (!isString(common.website))
546
+ throw new TypeError('Invalid metadata: "website" must be a string or null.');
547
+ if (!isString(common.originalalbum))
548
+ throw new TypeError('Invalid metadata: "originalalbum" must be a string or null.');
549
+ if (!isString(common.originalartist))
550
+ throw new TypeError('Invalid metadata: "originalartist" must be a string or null.');
551
+ if (!isString(common.key))
552
+ throw new TypeError('Invalid metadata: "key" must be a string or null.');
553
+ if (!isString(common.movement))
554
+ throw new TypeError('Invalid metadata: "movement" must be a string or null.');
555
+ if (!isString(common.podcastId))
556
+ throw new TypeError('Invalid metadata: "podcastId" must be a string or null.');
303
557
  // Validate Arrays
304
- if (!isArray(common.albumartists, (value) => typeof value === 'string'))
305
- throw new TypeError('Invalid metadata: "albumartists" must be an array of string.');
306
- if (!isArray(common.genre, (value) => typeof value === 'string'))
307
- throw new TypeError('Invalid metadata: "genre" must be an array of string.');
308
- if (!isArray(common.label, (value) => typeof value === 'string'))
309
- throw new TypeError('Invalid metadata: "label" must be an array of string.');
310
- if (!isArray(common.composer, (value) => typeof value === 'string'))
311
- throw new TypeError('Invalid metadata: "composer" must be an array of string.');
312
- if (!isArray(common.artists, (value) => typeof value === 'string'))
313
- throw new TypeError('Invalid metadata: "artists" must be an array of string.');
558
+ if (!isArray(common.albumartists, (v) => typeof v === 'string'))
559
+ throw new TypeError('Invalid metadata: "albumartists" must be an array of strings.');
560
+ if (!isArray(common.genre, (v) => typeof v === 'string'))
561
+ throw new TypeError('Invalid metadata: "genre" must be an array of strings.');
562
+ if (!isArray(common.label, (v) => typeof v === 'string'))
563
+ throw new TypeError('Invalid metadata: "label" must be an array of strings.');
564
+ if (!isArray(common.composer, (v) => typeof v === 'string'))
565
+ throw new TypeError('Invalid metadata: "composer" must be an array of strings.');
566
+ if (!isArray(common.artists, (v) => typeof v === 'string'))
567
+ throw new TypeError('Invalid metadata: "artists" must be an array of strings.');
568
+ if (!isArray(common.lyricist, (v) => typeof v === 'string'))
569
+ throw new TypeError('Invalid metadata: "lyricist" must be an array of strings.');
570
+ if (!isArray(common.writer, (v) => typeof v === 'string'))
571
+ throw new TypeError('Invalid metadata: "writer" must be an array of strings.');
572
+ if (!isArray(common.conductor, (v) => typeof v === 'string'))
573
+ throw new TypeError('Invalid metadata: "conductor" must be an array of strings.');
574
+ if (!isArray(common.remixer, (v) => typeof v === 'string'))
575
+ throw new TypeError('Invalid metadata: "remixer" must be an array of strings.');
576
+ if (!isArray(common.arranger, (v) => typeof v === 'string'))
577
+ throw new TypeError('Invalid metadata: "arranger" must be an array of strings.');
578
+ if (!isArray(common.engineer, (v) => typeof v === 'string'))
579
+ throw new TypeError('Invalid metadata: "engineer" must be an array of strings.');
580
+ if (!isArray(common.publisher, (v) => typeof v === 'string'))
581
+ throw new TypeError('Invalid metadata: "publisher" must be an array of strings.');
582
+ if (!isArray(common.producer, (v) => typeof v === 'string'))
583
+ throw new TypeError('Invalid metadata: "producer" must be an array of strings.');
584
+ if (!isArray(common.djmixer, (v) => typeof v === 'string'))
585
+ throw new TypeError('Invalid metadata: "djmixer" must be an array of strings.');
586
+ if (!isArray(common.mixer, (v) => typeof v === 'string'))
587
+ throw new TypeError('Invalid metadata: "mixer" must be an array of strings.');
588
+ if (!isArray(common.technician, (v) => typeof v === 'string'))
589
+ throw new TypeError('Invalid metadata: "technician" must be an array of strings.');
590
+ if (!isArray(common.subtitle, (v) => typeof v === 'string'))
591
+ throw new TypeError('Invalid metadata: "subtitle" must be an array of strings.');
592
+ if (!isArray(common.description, (v) => typeof v === 'string'))
593
+ throw new TypeError('Invalid metadata: "description" must be an array of strings.');
594
+ if (!isArray(common.discsubtitle, (v) => typeof v === 'string'))
595
+ throw new TypeError('Invalid metadata: "discsubtitle" must be an array of strings.');
596
+ if (!isArray(common.releasetype, (v) => typeof v === 'string'))
597
+ throw new TypeError('Invalid metadata: "releasetype" must be an array of strings.');
598
+ if (!isArray(common.isrc, (v) => typeof v === 'string'))
599
+ throw new TypeError('Invalid metadata: "isrc" must be an array of strings.');
600
+ if (!isArray(common.performer_instrument, (v) => typeof v === 'string'))
601
+ throw new TypeError('Invalid metadata: "performer_instrument" must be an array of strings.');
602
+ if (!isArray(common.notes, (v) => typeof v === 'string'))
603
+ throw new TypeError('Invalid metadata: "notes" must be an array of strings.');
604
+ if (!isArray(common.category, (v) => typeof v === 'string'))
605
+ throw new TypeError('Invalid metadata: "category" must be an array of strings.');
606
+ if (!isArray(common.keywords, (v) => typeof v === 'string'))
607
+ throw new TypeError('Invalid metadata: "keywords" must be an array of strings.');
608
+ if (!isArray(common.catalognumber, (v) => typeof v === 'string'))
609
+ throw new TypeError('Invalid metadata: "catalognumber" must be an array of strings.');
610
+ // Validate Complex Arrays
314
611
  if (!isArray(common.picture, (value) => value &&
315
612
  typeof value === 'object' &&
316
613
  (typeof value.description === 'undefined' || typeof value.description === 'string') &&
@@ -319,18 +616,48 @@ const validateMediaContent = (common, isPartial) => {
319
616
  typeof value.format === 'string' &&
320
617
  (value.data instanceof Uint8Array || typeof value.data === 'string')))
321
618
  throw new TypeError('Invalid metadata: "picture" must be an array of pictures.');
619
+ if (!isArray(common.comment, (v) => v !== null &&
620
+ typeof v === 'object' &&
621
+ isString(v.descriptor) &&
622
+ isString(v.language) &&
623
+ isString(v.text)))
624
+ throw new TypeError('Invalid metadata: "comment" must be an array of valid IComment objects.');
625
+ if (!isArray(common.lyrics, (v) => {
626
+ const isLyricsContentType = (/** @type {LyricsContentType} */ v) => v !== null &&
627
+ typeof v === 'object' &&
628
+ isNumber(v.other, true) &&
629
+ isNumber(v.lyrics, true) &&
630
+ isNumber(v.text, true) &&
631
+ isNumber(v.movement_part, true) &&
632
+ isNumber(v.events, true) &&
633
+ isNumber(v.chord, true) &&
634
+ isNumber(v.trivia_pop, true);
635
+ const isTimestampFormat = (/** @type {TimestampFormat} */ v) => v !== null &&
636
+ typeof v === 'object' &&
637
+ isNumber(v.notSynchronized, true) &&
638
+ isNumber(v.mpegFrameNumber, true) &&
639
+ isNumber(v.milliseconds, true);
640
+ return (v !== null &&
641
+ typeof v === 'object' &&
642
+ isString(v.text) &&
643
+ isArray(v.syncText, (v) => v !== null && typeof v === 'object' && isString(v.text, true) && isNumber(v.timestamp)) &&
644
+ isTimestampFormat(v.timeStampFormat) &&
645
+ isLyricsContentType(v.contentType));
646
+ }))
647
+ throw new TypeError('Invalid metadata: "lyrics" must be an array of valid ILyricsTag objects.');
648
+ if (!isArray(common.rating, (v) => v !== null && typeof v === 'object' && isString(v.source) && isNumber(v.rating)))
649
+ throw new TypeError('Invalid metadata: "rating" must be an array of valid IRating objects.');
650
+ // Validate Nested Objects (Disk, Track, Movement, etc.)
322
651
  /**
323
652
  * Validate Nested Objects (Disk and Track)
324
653
  * @param {string} name
325
654
  * @param {MediaNumber|null} [info]
326
655
  */
327
656
  const validateTrackInfo = (name, info) => {
328
- checkString(name, 'name');
329
657
  if (isUndefinedAllowed(info))
330
- return; // Ignores if partial
331
- if (info === undefined || info === null) {
332
- throw new TypeError(`Invalid metadata: "${name}" is required.`);
333
- }
658
+ return;
659
+ if (info === null)
660
+ return;
334
661
  if (typeof info !== 'object')
335
662
  throw new TypeError(`Invalid metadata: "${name}" must be an object or null.`);
336
663
  if (typeof info.no !== 'number' && info.no !== null)
@@ -340,24 +667,7 @@ const validateMediaContent = (common, isPartial) => {
340
667
  };
341
668
  validateTrackInfo('disk', common.disk);
342
669
  validateTrackInfo('track', common.track);
343
- };
344
- /**
345
- * Helper to validate types within the media content metadata object.
346
- * This ensures that if a property is present, it matches the expected type.
347
- * @param {ContentMetadataTemplate<IPictureTemplate<string | Uint8Array<ArrayBufferLike>>>} common
348
- */
349
- export const valMediaContentMetadata = (common) => {
350
- checkObject(common, 'common');
351
- return validateMediaContent(common, false);
352
- };
353
- /**
354
- * Helper to validate types within the media content metadata object.
355
- * Allows the absence of properties (useful for updates/patch).
356
- * @param {Partial<ContentMetadataTemplate<IPictureTemplate<string | Uint8Array<ArrayBufferLike>>>>} common
357
- */
358
- export const valMediaContentMetadataPartial = (common) => {
359
- checkObject(common, 'common');
360
- return validateMediaContent(common, true);
670
+ validateTrackInfo('movementIndex', common.movementIndex);
361
671
  };
362
672
  /**
363
673
  * Downloads an audio file from a URL and extracts its ID3/metadata tags.
@@ -384,12 +694,63 @@ export const extractMediaId3Tags = async (url, parseFile, convertBase64toBlob =
384
694
  const metadata = await parseFile(blob);
385
695
  // 4. Complete Validation of the parsed metadata structure
386
696
  if (!metadata || typeof metadata.common !== 'object')
387
- throw new TypeError('Invalid metadata: "common" property is missing or not an object.');
697
+ throw new TypeError('Invalid metadata: "common" property is missing.');
698
+ if (typeof metadata.common['performer:instrument'] !== undefined)
699
+ // @ts-ignore
700
+ metadata.common.performer_instrument = metadata.common['performer:instrument'];
701
+ valFetchMediaContent(metadata);
388
702
  const common = metadata.common;
389
- valMediaContentMetadataPartial(common);
703
+ valMediaContentMetadata(common);
704
+ /**
705
+ * @type {(value: [string, any]) => boolean}
706
+ */
707
+ const filterContent = ([_, value]) => Array.isArray(value)
708
+ ? value.length > 0
709
+ : isJsonObject(value)
710
+ ? (() => {
711
+ value;
712
+ const d = Object.fromEntries(Object.entries(value).filter(filterContent));
713
+ const amount = countObj(d);
714
+ return amount > 0;
715
+ })()
716
+ : value !== null;
717
+ // Fix blob values.
718
+ const picture = common?.picture?.map((value) => ({
719
+ ...value,
720
+ data: convertBase64toBlob ? convertToBlobUrl(value.data, value.format) : value.data,
721
+ })) ?? [];
722
+ metadata.common = {
723
+ ...metadata.common,
724
+ picture: picture.map((value) => ({
725
+ ...value,
726
+ data: '',
727
+ })),
728
+ };
729
+ // Sanitize metadata.native to prevent Uint8Array leakage
730
+ if (convertBase64toBlob) {
731
+ Object.keys(metadata.native).forEach((key) => {
732
+ metadata.native[key] = metadata.native[key].map((item) => {
733
+ // Check if item.value is an object and contains a 'data' property that is a Uint8Array
734
+ if (item.value &&
735
+ typeof item.value === 'object' &&
736
+ item.value.data instanceof Uint8Array) {
737
+ const index = common?.picture?.findIndex((item2) => item2.data === item.value.data &&
738
+ item2.format === item.value.format &&
739
+ item2.description === item.value.description &&
740
+ item2.name === item.value.name) ?? -1;
741
+ const data = picture[index];
742
+ if (data) {
743
+ return { ...item, value: { ...item.value, data: '' } };
744
+ }
745
+ }
746
+ return item;
747
+ });
748
+ });
749
+ }
390
750
  // 5. Return the specific metadata fields requested
391
751
  // We structure the return to match the MediaContentMetadata typedef
392
- return {
752
+ /** @type {MediaContentMetadata<PictureDataType>} */
753
+ const result = Object.fromEntries(Object.entries({
393
754
  title: common?.title ?? null,
394
755
  album: common?.album ?? null,
395
756
  albumartist: common?.albumartist ?? null,
@@ -400,13 +761,89 @@ export const extractMediaId3Tags = async (url, parseFile, convertBase64toBlob =
400
761
  year: common?.year ?? null,
401
762
  artist: common?.artist ?? null,
402
763
  artists: common?.artists ?? [],
403
- disk: common?.disk ? { no: common.disk.no, of: common.disk.of } : { no: null, of: null },
404
- track: common?.track ? { no: common.track.no, of: common.track.of } : { no: null, of: null },
405
- picture: common?.picture?.map((value) => ({
406
- ...value,
407
- data: convertBase64toBlob ? convertToBlobUrl(value.data, value.format) : value.data,
408
- })) ?? [],
409
- };
764
+ disk: common?.disk ? { no: common.disk.no, of: common.disk.of } : null,
765
+ track: common?.track ? { no: common.track.no, of: common.track.of } : null,
766
+ movementIndex: common?.movementIndex
767
+ ? { no: common.movementIndex.no, of: common.movementIndex.of }
768
+ : null,
769
+ picture,
770
+ date: common?.date ?? null,
771
+ originaldate: common?.originaldate ?? null,
772
+ originalyear: common?.originalyear ?? null,
773
+ releasedate: common?.releasedate ?? null,
774
+ comment: common?.comment ?? [],
775
+ lyrics: common?.lyrics ?? [],
776
+ albumsort: common?.albumsort ?? null,
777
+ titlesort: common?.titlesort ?? null,
778
+ work: common?.work ?? null,
779
+ artistsort: common?.artistsort ?? null,
780
+ albumartistsort: common?.albumartistsort ?? null,
781
+ composersort: common?.composersort ?? null,
782
+ lyricist: common?.lyricist ?? [],
783
+ writer: common?.writer ?? [],
784
+ conductor: common?.conductor ?? [],
785
+ remixer: common?.remixer ?? [],
786
+ arranger: common?.arranger ?? [],
787
+ engineer: common?.engineer ?? [],
788
+ publisher: common?.publisher ?? [],
789
+ producer: common?.producer ?? [],
790
+ djmixer: common?.djmixer ?? [],
791
+ mixer: common?.mixer ?? [],
792
+ technician: common?.technician ?? [],
793
+ grouping: common?.grouping ?? null,
794
+ subtitle: common?.subtitle ?? [],
795
+ description: common?.description ?? [],
796
+ longDescription: common?.longDescription ?? null,
797
+ discsubtitle: common?.discsubtitle ?? [],
798
+ totaltracks: common?.totaltracks ?? null,
799
+ totaldiscs: common?.totaldiscs ?? null,
800
+ movementTotal: common?.movementTotal ?? null,
801
+ compilation: common?.compilation ?? null,
802
+ rating: common?.rating ?? [],
803
+ bpm: common?.bpm ?? null,
804
+ mood: common?.mood ?? null,
805
+ media: common?.media ?? null,
806
+ catalognumber: common?.catalognumber ?? [],
807
+ tvShow: common?.tvShow ?? null,
808
+ tvShowSort: common?.tvShowSort ?? null,
809
+ tvSeason: common?.tvSeason ?? null,
810
+ tvEpisode: common?.tvEpisode ?? null,
811
+ tvEpisodeId: common?.tvEpisodeId ?? null,
812
+ tvNetwork: common?.tvNetwork ?? null,
813
+ podcast: common?.podcast ?? null,
814
+ podcasturl: common?.podcasturl ?? null,
815
+ releasestatus: common?.releasestatus ?? null,
816
+ releasetype: common?.releasetype ?? [],
817
+ releasecountry: common?.releasecountry ?? null,
818
+ script: common?.script ?? null,
819
+ language: common?.language ?? null,
820
+ copyright: common?.copyright ?? null,
821
+ license: common?.license ?? null,
822
+ encodedby: common?.encodedby ?? null,
823
+ encodersettings: common?.encodersettings ?? null,
824
+ gapless: common?.gapless ?? null,
825
+ barcode: common?.barcode ?? null,
826
+ isrc: common?.isrc ?? [],
827
+ asin: common?.asin ?? null,
828
+ website: common?.website ?? null,
829
+ performer_instrument: common?.performer_instrument ?? [],
830
+ averageLevel: common?.averageLevel ?? null,
831
+ peakLevel: common?.peakLevel ?? null,
832
+ notes: common?.notes ?? [],
833
+ originalalbum: common?.originalalbum ?? null,
834
+ originalartist: common?.originalartist ?? null,
835
+ key: common?.key ?? null,
836
+ category: common?.category ?? [],
837
+ hdVideo: common?.hdVideo ?? null,
838
+ keywords: common?.keywords ?? [],
839
+ movement: common?.movement ?? null,
840
+ podcastId: common?.podcastId ?? null,
841
+ showMovement: common?.showMovement ?? null,
842
+ stik: common?.stik ?? null,
843
+ playCounter: common?.playCounter ?? null,
844
+ }).filter(filterContent));
845
+ result._fetchData = metadata;
846
+ return result;
410
847
  }
411
848
  catch (error) {
412
849
  // Re-throwing the error allows the caller to handle specific failure cases
@@ -460,7 +897,7 @@ export const extractMediaId3Tags = async (url, parseFile, convertBase64toBlob =
460
897
  export const parseMediaMetadata = async (source, defaultMetadata = {}, metadata = {}, parseFile = (data) => {
461
898
  if (!(data instanceof Blob))
462
899
  throw new TypeError('Expected "data" to be a Blob.');
463
- return new Promise((resolve, reject) => reject(new TypeError('parseFile library not found.')));
900
+ return new Promise((_, reject) => reject(new TypeError('parseFile library not found.')));
464
901
  }, callbacks = {}, convertBase64toBlob = true, unknownArtist = 'null') => {
465
902
  // Argument Validation
466
903
  if (typeof source !== 'string' && !(source instanceof HTMLMediaElement))
@@ -501,10 +938,10 @@ export const parseMediaMetadata = async (source, defaultMetadata = {}, metadata
501
938
  const notifyError = (/** @type {Error} */ error, /** @type {LoadingErrorStage} */ stage) => {
502
939
  if (!(error instanceof Error))
503
940
  throw new TypeError('Expected "error" to be an Error.');
504
- checkString(stage, 'stage'); // Adicionado
941
+ checkString(stage, 'stage');
505
942
  if (callbacks.onError) {
506
943
  callbacks.onError({
507
- error,
944
+ error: error instanceof Error ? error : new Error(String(error)),
508
945
  url: url || (source instanceof HTMLMediaElement ? source.src : 'unknown'),
509
946
  stage,
510
947
  });
@@ -596,11 +1033,36 @@ export const parseMediaMetadata = async (source, defaultMetadata = {}, metadata
596
1033
  };
597
1034
  // 5. Final Merge Logic
598
1035
  // Priority: Manual Metadata (highest) > Extracted ID3 > Default values
1036
+ /**
1037
+ * @template {Partial<MediaContent<PictureData>>} Metadata
1038
+ * @param {Metadata} meta
1039
+ */
1040
+ const mergeMetadata = (meta) => {
1041
+ const metadataEdit = { ...meta };
1042
+ if (isJsonObject(metadataEdit._fetchData)) {
1043
+ if (isJsonObject(extractedMetadata._fetchData)) {
1044
+ metadataEdit._fetchData = {
1045
+ ...extractedMetadata._fetchData,
1046
+ };
1047
+ }
1048
+ if (isJsonObject(meta._fetchData)) {
1049
+ if (typeof meta._fetchData.common !== 'undefined')
1050
+ metadataEdit._fetchData.common = meta._fetchData.common;
1051
+ if (typeof meta._fetchData.native !== 'undefined')
1052
+ metadataEdit._fetchData.native = meta._fetchData.native;
1053
+ if (typeof meta._fetchData.format !== 'undefined')
1054
+ metadataEdit._fetchData.format = meta._fetchData.format;
1055
+ if (typeof meta._fetchData.quality !== 'undefined')
1056
+ metadataEdit._fetchData.quality = meta._fetchData.quality;
1057
+ }
1058
+ }
1059
+ return metadataEdit;
1060
+ };
599
1061
  const finalContent = {
600
1062
  ...baseData,
601
- ...defaultMetadata,
1063
+ ...mergeMetadata(defaultMetadata),
602
1064
  ...extractedMetadata,
603
- ...metadata,
1065
+ ...mergeMetadata(metadata),
604
1066
  // Explicitly ensure title and artist are resolved from the hierarchy
605
1067
  title: extractedMetadata.title || metadata.title || getFallbackTitleFromUrl(url),
606
1068
  artist: extractedMetadata.artist ||
@@ -609,24 +1071,17 @@ export const parseMediaMetadata = async (source, defaultMetadata = {}, metadata
609
1071
  };
610
1072
  // Notify Success
611
1073
  if (callbacks.onProgress) {
612
- callbacks.onProgress({
613
- status: 'success',
614
- stage: 'COMPLETE',
615
- url: url,
616
- });
1074
+ callbacks.onProgress({ status: 'success', stage: 'COMPLETE', url: url });
617
1075
  }
618
1076
  return /** @type {MediaContent<PictureData>} */ (finalContent);
619
1077
  }
620
1078
  catch (err) {
621
1079
  // If it's already a MediaLoadingError, re-throw it.
622
1080
  // Otherwise, wrap it.
623
- if (err instanceof MediaLoadingError) {
1081
+ if (err instanceof MediaLoadingError)
624
1082
  throw err;
625
- }
626
- else {
627
- const wrappedError = new MediaLoadingError(err instanceof Error ? err.message : 'UNKNOWN ERROR', url, 'INITIALIZING');
628
- notifyError(wrappedError, 'INITIALIZING');
629
- throw wrappedError;
630
- }
1083
+ const wrappedError = new MediaLoadingError(err instanceof Error ? err.message : 'UNKNOWN ERROR', url, 'INITIALIZING');
1084
+ notifyError(wrappedError, 'INITIALIZING');
1085
+ throw wrappedError;
631
1086
  }
632
1087
  };