tiny-essentials 1.28.12 → 1.28.15
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/changelog/1/28/14.md +22 -0
- package/changelog/1/28/15.md +11 -0
- package/dist/v1/TinyBasicsEs.min.js +1 -1
- package/dist/v1/TinyEssentials.min.js +1 -1
- package/dist/v1/TinyMediaPlayer.min.js +1 -1
- package/dist/v1/TinyRadioFm.min.js +1 -1
- package/dist/v1/basics/index.cjs +1 -1
- package/dist/v1/basics/index.d.mts +2 -2
- package/dist/v1/basics/index.mjs +2 -2
- package/dist/v1/basics/mediaContent.cjs +652 -145
- package/dist/v1/basics/mediaContent.d.mts +457 -34
- package/dist/v1/basics/mediaContent.mjs +595 -141
- package/dist/v1/index.cjs +1 -1
- package/dist/v1/index.d.mts +2 -2
- package/dist/v1/index.mjs +2 -2
- package/dist/v1/libs/TinyMediaPlayer.cjs +1 -1
- package/dist/v1/libs/TinyMediaPlayer.mjs +2 -2
- package/dist/v1/libs/TinyRadioFm.cjs +12 -1
- package/dist/v1/libs/TinyRadioFm.mjs +11 -2
- package/docs/v1/basics/mediaContent.md +1 -2
- package/package.json +1 -1
|
@@ -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,54 @@
|
|
|
28
30
|
|
|
29
31
|
/**
|
|
30
32
|
* A numeric structure representing track or disk indexing.
|
|
31
|
-
* @typedef {{no
|
|
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 {string} [descriptor] - A descriptor for the lyrics.
|
|
71
|
+
* @property {string} [language] - The language of the lyrics.
|
|
72
|
+
* @property {ILyricsText[]} [syncText] - An array of synchronized lyric text objects.
|
|
73
|
+
* @property {TimestampFormat} [timeStampFormat] - The format of the timestamp.
|
|
74
|
+
* @property {LyricsContentType} [contentType] - The type of lyrical content.
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @typedef {Object} IRating
|
|
79
|
+
* @property {string} [source] - The source of the rating.
|
|
80
|
+
* @property {number} [rating] - The numeric rating value.
|
|
32
81
|
*/
|
|
33
82
|
|
|
34
83
|
/**
|
|
@@ -36,19 +85,121 @@
|
|
|
36
85
|
*
|
|
37
86
|
* @template {IPictureTemplate<PictureDataType>} IPictureContent
|
|
38
87
|
* @typedef {Object} ContentMetadataTemplate
|
|
39
|
-
* @property {
|
|
40
|
-
* @property {string
|
|
41
|
-
* @property {string
|
|
42
|
-
* @property {string[]
|
|
43
|
-
* @property {string[]}
|
|
44
|
-
* @property {string[]}
|
|
45
|
-
* @property {string[]}
|
|
46
|
-
* @property {
|
|
47
|
-
* @property {
|
|
48
|
-
* @property {string[]
|
|
49
|
-
* @property {
|
|
50
|
-
* @property {MediaNumber}
|
|
88
|
+
* @property {MediaContentFetchData<PictureDataType>|null} [_fetchData]
|
|
89
|
+
* @property {string} [title] - The title of the track.
|
|
90
|
+
* @property {string} [album] - The name of the album.
|
|
91
|
+
* @property {string} [albumartist] - The primary artist of the album.
|
|
92
|
+
* @property {string[]} [albumartists] - An array of artists associated with the album.
|
|
93
|
+
* @property {string[]} [genre] - An array of genres associated with the track.
|
|
94
|
+
* @property {string[]} [label] - The record label.
|
|
95
|
+
* @property {string[]} [composer] - The composer of the track.
|
|
96
|
+
* @property {number} [year] - The release year.
|
|
97
|
+
* @property {string} [artist] - The primary artist of the track.
|
|
98
|
+
* @property {string[]} [artists] - An array of artists associated with the track.
|
|
99
|
+
* @property {MediaNumber} [disk] - Disk information containing the current disk number and total disks.
|
|
100
|
+
* @property {MediaNumber} [track] - Track information containing the current track number and total tracks.
|
|
51
101
|
* @property {IPictureContent[]} [picture] - An array of picture objects containing album art.
|
|
102
|
+
* @property {string} [date] - The date.
|
|
103
|
+
* @property {string} [originaldate] - The original date.
|
|
104
|
+
* @property {number} [originalyear] - The original release year.
|
|
105
|
+
* @property {string} [releasedate] - The release date.
|
|
106
|
+
* @property {IComment[]} [comment] - An array of comments.
|
|
107
|
+
* @property {ILyricsTag[]} [lyrics] - An array of lyric tags.
|
|
108
|
+
* @property {string} [albumsort] - The sortable album name.
|
|
109
|
+
* @property {string} [titlesort] - The sortable title.
|
|
110
|
+
* @property {string} [work] - The work/composition name.
|
|
111
|
+
* @property {string} [artistsort] - The sortable artist name.
|
|
112
|
+
* @property {string} [albumartistsort] - The sortable album artist name.
|
|
113
|
+
* @property {string} [composersort] - The sortable composer name.
|
|
114
|
+
* @property {string[]} [lyricist] - An array of lyricists.
|
|
115
|
+
* @property {string[]} [writer] - An array of writers.
|
|
116
|
+
* @property {string[]} [conductor] - An array of conductors.
|
|
117
|
+
* @property {string[]} [remixer] - An array of remixers.
|
|
118
|
+
* @property {string[]} [arranger] - An array of arrangers.
|
|
119
|
+
* @property {string[]} [engineer] - An array of engineers.
|
|
120
|
+
* @property {string[]} [publisher] - An array of publishers.
|
|
121
|
+
* @property {string[]} [producer] - An array of producers.
|
|
122
|
+
* @property {string[]} [djmixer] - An array of DJ mixers.
|
|
123
|
+
* @property {string[]} [mixer] - An array of mixers.
|
|
124
|
+
* @property {string[]} [technician] - An array of technicians.
|
|
125
|
+
* @property {string} [grouping] - The grouping name.
|
|
126
|
+
* @property {string[]} [subtitle] - An array of subtitles.
|
|
127
|
+
* @property {string[]} [description] - An array of descriptions.
|
|
128
|
+
* @property {string} [longDescription] - A long description.
|
|
129
|
+
* @property {string[]} [discsubtitle] - An array of disc subtitles.
|
|
130
|
+
* @property {string} [totaltracks] - Total number of tracks.
|
|
131
|
+
* @property {string} [totaldiscs] - Total number of discs.
|
|
132
|
+
* @property {number} [movementTotal] - Total number of movements.
|
|
133
|
+
* @property {boolean} [compilation] - Whether it is a compilation.
|
|
134
|
+
* @property {IRating[]} [rating] - An array of ratings.
|
|
135
|
+
* @property {number} [bpm] - Beats per minute.
|
|
136
|
+
* @property {string} [mood] - The mood of the track.
|
|
137
|
+
* @property {string} [media] - The media type.
|
|
138
|
+
* @property {string[]} [catalognumber] - An array of catalog numbers.
|
|
139
|
+
* @property {string} [tvShow] - The TV show name.
|
|
140
|
+
* @property {string} [tvShowSort] - The sortable TV show name.
|
|
141
|
+
* @property {number} [tvSeason] - The TV season number.
|
|
142
|
+
* @property {number} [tvEpisode] - The TV episode number.
|
|
143
|
+
* @property {string} [tvEpisodeId] - The TV episode ID.
|
|
144
|
+
* @property {string} [tvNetwork] - The TV network.
|
|
145
|
+
* @property {boolean} [podcast] - Whether it is a podcast.
|
|
146
|
+
* @property {string} [podcasturl] - The podcast URL.
|
|
147
|
+
* @property {string} [releasestatus] - The release status.
|
|
148
|
+
* @property {string[]} [releasetype] - An array of release types.
|
|
149
|
+
* @property {string} [releasecountry] - The release country.
|
|
150
|
+
* @property {string} [script] - The script.
|
|
151
|
+
* @property {string} [language] - The language.
|
|
152
|
+
* @property {string} [copyright] - The copyright information.
|
|
153
|
+
* @property {string} [license] - The license information.
|
|
154
|
+
* @property {string} [encodedby] - The encoder.
|
|
155
|
+
* @property {string} [encodersettings] - The encoder settings.
|
|
156
|
+
* @property {boolean} [gapless] - Whether it is gapless.
|
|
157
|
+
* @property {string} [barcode] - The barcode.
|
|
158
|
+
* @property {string[]} [isrc] - An array of ISRC codes.
|
|
159
|
+
* @property {string} [asin] - The ASIN.
|
|
160
|
+
* @property {string} [website] - The website.
|
|
161
|
+
* @property {string[]} [performer_instrument] - An array of performers and their instruments.
|
|
162
|
+
* @property {number} [averageLevel] - The average loudness level.
|
|
163
|
+
* @property {number} [peakLevel] - The peak loudness level.
|
|
164
|
+
* @property {string[]} [notes] - An array of notes.
|
|
165
|
+
* @property {string} [originalalbum] - The original album.
|
|
166
|
+
* @property {string} [originalartist] - The original artist.
|
|
167
|
+
* @property {string} [key] - The initial key of the music in the file, e.g. "A Minor".
|
|
168
|
+
* @property {string[]} [category] - An array of categories.
|
|
169
|
+
* @property {number} [hdVideo] - HD video flag.
|
|
170
|
+
* @property {string[]} [keywords] - An array of keywords.
|
|
171
|
+
* @property {string} [movement] - The movement.
|
|
172
|
+
* @property {MediaNumber} [movementIndex] - The movement index.
|
|
173
|
+
* @property {string} [podcastId] - The podcast ID.
|
|
174
|
+
* @property {boolean} [showMovement] - Whether to show movement.
|
|
175
|
+
* @property {number} [stik] - The stik value.
|
|
176
|
+
* @property {number} [playCounter] - The play counter.
|
|
177
|
+
*/
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @typedef {Object} MediaContentFetchNativeData
|
|
181
|
+
* @property {string} id - The unique identifier for the native data.
|
|
182
|
+
* @property {any|Record<string|number|symbol, unknown>} [value] - The actual data value.
|
|
183
|
+
*/
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* A record containing common metadata tags.
|
|
187
|
+
* @template {PictureDataType} PictureData
|
|
188
|
+
* @typedef {Record<string|number|symbol, unknown> & ContentMetadataTemplate<IPictureTemplate<PictureData>>} ICommonTagsResult
|
|
189
|
+
*/
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* A mapping of keys to arrays of native metadata fetch data.
|
|
193
|
+
* @typedef {Record<string|number|symbol, MediaContentFetchNativeData[]>} INativeTags
|
|
194
|
+
*/
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @template {PictureDataType} PictureData
|
|
198
|
+
* @typedef {Object} MediaContentFetchData
|
|
199
|
+
* @property {ICommonTagsResult<PictureData>} common - The common metadata properties.
|
|
200
|
+
* @property {INativeTags} native - The native metadata properties.
|
|
201
|
+
* @property {unknown} format - The format of the media.
|
|
202
|
+
* @property {unknown} quality - The quality of the media.
|
|
52
203
|
*/
|
|
53
204
|
|
|
54
205
|
/**
|
|
@@ -85,7 +236,7 @@
|
|
|
85
236
|
* A promise that resolves to an object containing the extracted metadata.
|
|
86
237
|
* @callback ParseMediaContentMetadata
|
|
87
238
|
* @param {Blob} data - The raw file blob.
|
|
88
|
-
* @returns {Promise<
|
|
239
|
+
* @returns {Promise<MediaContentFetchData<PictureDataType>>} A promise resolving to the common metadata properties.
|
|
89
240
|
*/
|
|
90
241
|
|
|
91
242
|
/**
|
|
@@ -137,6 +288,35 @@ const checkObject = (/** @type {Object<string, any> | null} */ val, /** @type {s
|
|
|
137
288
|
}
|
|
138
289
|
};
|
|
139
290
|
|
|
291
|
+
const isUndefinedAllowed = (/** @type {any} */ v, forceNoUndefined = false) =>
|
|
292
|
+
!forceNoUndefined && typeof v === 'undefined';
|
|
293
|
+
|
|
294
|
+
const isString = (/** @type {string | null | undefined} */ v, forceNoUndefined = false) =>
|
|
295
|
+
isUndefinedAllowed(v, forceNoUndefined) ||
|
|
296
|
+
typeof v === 'string' ||
|
|
297
|
+
(!forceNoUndefined && v === null);
|
|
298
|
+
|
|
299
|
+
const isNumber = (/** @type {number | null | undefined} */ v, forceNoUndefined = false) =>
|
|
300
|
+
isUndefinedAllowed(v, forceNoUndefined) ||
|
|
301
|
+
typeof v === 'number' ||
|
|
302
|
+
(!forceNoUndefined && v === null);
|
|
303
|
+
|
|
304
|
+
const isBoolean = (/** @type {boolean | null | undefined} */ v, forceNoUndefined = false) =>
|
|
305
|
+
isUndefinedAllowed(v, forceNoUndefined) ||
|
|
306
|
+
typeof v === 'boolean' ||
|
|
307
|
+
(!forceNoUndefined && v === null);
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* @template {any} T
|
|
311
|
+
* @param {T[]|undefined} v
|
|
312
|
+
* @param {(value: T) => boolean} valueValidator
|
|
313
|
+
*/
|
|
314
|
+
const isArray = (v, valueValidator) => {
|
|
315
|
+
if (isUndefinedAllowed(v)) return true;
|
|
316
|
+
checkFunction(valueValidator, 'valueValidator');
|
|
317
|
+
return Array.isArray(v) && v.every(valueValidator);
|
|
318
|
+
};
|
|
319
|
+
|
|
140
320
|
/** @type {Map<string, number>} */
|
|
141
321
|
const _blobCounter = new Map();
|
|
142
322
|
|
|
@@ -153,26 +333,6 @@ const getMediaContentBase = () => ({
|
|
|
153
333
|
weight: 1,
|
|
154
334
|
});
|
|
155
335
|
|
|
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
336
|
/**
|
|
177
337
|
* Custom error class to provide detailed context during the content preparation process.
|
|
178
338
|
* @extends Error
|
|
@@ -298,55 +458,213 @@ const revokeContentUrls = (content) => {
|
|
|
298
458
|
};
|
|
299
459
|
|
|
300
460
|
/**
|
|
301
|
-
*
|
|
302
|
-
* @param {
|
|
303
|
-
* @
|
|
461
|
+
* @param {MediaContentFetchData<PictureDataType>|null} [fetchData] - The fetch object to be validated.
|
|
462
|
+
* @param {boolean} [returnsBoolean=false] - Ignores the throw error to return a boolean.
|
|
463
|
+
* @returns {boolean}
|
|
304
464
|
*/
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
465
|
+
const valFetchMediaContent = (fetchData, returnsBoolean = false) => {
|
|
466
|
+
if (objChecker.isJsonObject(fetchData)) {
|
|
467
|
+
if (objChecker.countObj(fetchData) !== 4) {
|
|
468
|
+
if (returnsBoolean) return false;
|
|
469
|
+
throw new TypeError(`Invalid fetch data: Object must be an MediaContentFetchData.`);
|
|
470
|
+
}
|
|
471
|
+
valMediaContentMetadata(fetchData.common);
|
|
472
|
+
checkObject(fetchData.native, '_fetchData.native');
|
|
308
473
|
|
|
309
|
-
|
|
474
|
+
// @ts-ignore
|
|
475
|
+
checkObject(fetchData.format, '_fetchData.format');
|
|
476
|
+
// @ts-ignore
|
|
477
|
+
checkObject(fetchData.quality, '_fetchData.quality');
|
|
310
478
|
|
|
311
|
-
|
|
312
|
-
|
|
479
|
+
for (const id in fetchData.native) {
|
|
480
|
+
const item = fetchData.native[id];
|
|
313
481
|
|
|
314
|
-
|
|
315
|
-
|
|
482
|
+
if (!isArray(item, (data) => typeof data.id === 'string')) {
|
|
483
|
+
if (returnsBoolean) return false;
|
|
484
|
+
throw new TypeError(`Invalid native data: "${id}" must be an array of native fetch data.`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return true;
|
|
489
|
+
};
|
|
316
490
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
491
|
+
/**
|
|
492
|
+
* Central logic of metadata validation.
|
|
493
|
+
* @param {ContentMetadataTemplate<IPictureTemplate<string | Uint8Array>>} common - The object to be validated.
|
|
494
|
+
*/
|
|
495
|
+
const valMediaContentMetadata = (common) => {
|
|
496
|
+
checkObject(common, 'common');
|
|
497
|
+
valFetchMediaContent(common._fetchData);
|
|
498
|
+
|
|
499
|
+
// --- Primitive Validation ---
|
|
326
500
|
|
|
327
|
-
// Validate Primitives
|
|
328
501
|
if (!isString(common.title))
|
|
329
502
|
throw new TypeError('Invalid metadata: "title" must be a string or null.');
|
|
330
503
|
if (!isString(common.album))
|
|
331
504
|
throw new TypeError('Invalid metadata: "album" must be a string or null.');
|
|
332
505
|
if (!isString(common.albumartist))
|
|
333
|
-
throw new TypeError('Invalid metadata: "albumartist" must be a string or null.');
|
|
506
|
+
throw new TypeError('Invalid metadata: "albumartist" must be a a string or null.');
|
|
334
507
|
if (!isString(common.artist))
|
|
335
508
|
throw new TypeError('Invalid metadata: "artist" must be a string or null.');
|
|
509
|
+
if (!isBoolean(common.compilation))
|
|
510
|
+
throw new TypeError('Invalid metadata: "compilation" must be a boolean or null.');
|
|
511
|
+
if (!isBoolean(common.podcast))
|
|
512
|
+
throw new TypeError('Invalid metadata: "podcast" must be a boolean or null.');
|
|
513
|
+
if (!isBoolean(common.gapless))
|
|
514
|
+
throw new TypeError('Invalid metadata: "gapless" must be a boolean or null.');
|
|
515
|
+
if (!isBoolean(common.showMovement))
|
|
516
|
+
throw new TypeError('Invalid metadata: "showMovement" must be a boolean or null.');
|
|
517
|
+
if (!isNumber(common.averageLevel))
|
|
518
|
+
throw new TypeError('Invalid metadata: "averageLevel" must be a number or null.');
|
|
519
|
+
if (!isNumber(common.peakLevel))
|
|
520
|
+
throw new TypeError('Invalid metadata: "peakLevel" must be a number or null.');
|
|
521
|
+
if (!isNumber(common.hdVideo))
|
|
522
|
+
throw new TypeError('Invalid metadata: "hdVideo" must be a number or null.');
|
|
523
|
+
if (!isNumber(common.stik))
|
|
524
|
+
throw new TypeError('Invalid metadata: "stik" must be a number or null.');
|
|
525
|
+
if (!isNumber(common.playCounter))
|
|
526
|
+
throw new TypeError('Invalid metadata: "playCounter" must be a number or null.');
|
|
527
|
+
if (!isNumber(common.tvSeason))
|
|
528
|
+
throw new TypeError('Invalid metadata: "tvSeason" must be a number or null.');
|
|
529
|
+
if (!isNumber(common.tvEpisode))
|
|
530
|
+
throw new TypeError('Invalid metadata: "tvEpisode" must be a number or null.');
|
|
336
531
|
if (!isNumber(common.year))
|
|
337
532
|
throw new TypeError('Invalid metadata: "year" must be a number or null.');
|
|
533
|
+
if (!isNumber(common.bpm))
|
|
534
|
+
throw new TypeError('Invalid metadata: "bpm" must be a number or null.');
|
|
535
|
+
if (!isNumber(common.originalyear))
|
|
536
|
+
throw new TypeError('Invalid metadata: "originalyear" must be a number or null.');
|
|
537
|
+
if (!isNumber(common.movementTotal))
|
|
538
|
+
throw new TypeError('Invalid metadata: "movementTotal" must be a number or null.');
|
|
539
|
+
if (!isString(common.date))
|
|
540
|
+
throw new TypeError('Invalid metadata: "date" must be a string or null.');
|
|
541
|
+
if (!isString(common.originaldate))
|
|
542
|
+
throw new TypeError('Invalid metadata: "originaldate" must be a string or null.');
|
|
543
|
+
if (!isString(common.releasedate))
|
|
544
|
+
throw new TypeError('Invalid metadata: "releasedate" must be a string or null.');
|
|
545
|
+
if (!isString(common.albumsort))
|
|
546
|
+
throw new TypeError('Invalid metadata: "albumsort" must be a string or null.');
|
|
547
|
+
if (!isString(common.titlesort))
|
|
548
|
+
throw new TypeError('Invalid metadata: "titlesort" must be a string or null.');
|
|
549
|
+
if (!isString(common.work))
|
|
550
|
+
throw new TypeError('Invalid metadata: "work" must be a string or null.');
|
|
551
|
+
if (!isString(common.artistsort))
|
|
552
|
+
throw new TypeError('Invalid metadata: "artistsort" must be a string or null.');
|
|
553
|
+
if (!isString(common.albumartistsort))
|
|
554
|
+
throw new TypeError('Invalid metadata: "albumartistsort" must be a string or null.');
|
|
555
|
+
if (!isString(common.composersort))
|
|
556
|
+
throw new TypeError('Invalid metadata: "composersort" must be a string or null.');
|
|
557
|
+
if (!isString(common.grouping))
|
|
558
|
+
throw new TypeError('Invalid metadata: "grouping" must be a string or null.');
|
|
559
|
+
if (!isString(common.longDescription))
|
|
560
|
+
throw new TypeError('Invalid metadata: "longDescription" must be a string or null.');
|
|
561
|
+
if (!isString(common.totaltracks))
|
|
562
|
+
throw new TypeError('Invalid metadata: "totaltracks" must be a string or null.');
|
|
563
|
+
if (!isString(common.totaldiscs))
|
|
564
|
+
throw new TypeError('Invalid metadata: "totaldiscs" must be a string or null.');
|
|
565
|
+
if (!isString(common.mood))
|
|
566
|
+
throw new TypeError('Invalid metadata: "mood" must be a string or null.');
|
|
567
|
+
if (!isString(common.media))
|
|
568
|
+
throw new TypeError('Invalid metadata: "media" must be a string or null.');
|
|
569
|
+
if (!isString(common.tvShow))
|
|
570
|
+
throw new TypeError('Invalid metadata: "tvShow" must be a string or null.');
|
|
571
|
+
if (!isString(common.tvShowSort))
|
|
572
|
+
throw new TypeError('Invalid metadata: "tvShowSort" must be a string or null.');
|
|
573
|
+
if (!isString(common.tvEpisodeId))
|
|
574
|
+
throw new TypeError('Invalid metadata: "tvEpisodeId" must be a string or null.');
|
|
575
|
+
if (!isString(common.tvNetwork))
|
|
576
|
+
throw new TypeError('Invalid metadata: "tvNetwork" must be a string or null.');
|
|
577
|
+
if (!isString(common.podcasturl))
|
|
578
|
+
throw new TypeError('Invalid metadata: "podcasturl" must be a string or null.');
|
|
579
|
+
if (!isString(common.releasestatus))
|
|
580
|
+
throw new TypeError('Invalid metadata: "releasestatus" must be a string or null.');
|
|
581
|
+
if (!isString(common.releasecountry))
|
|
582
|
+
throw new TypeError('Invalid metadata: "releasecountry" must be a string or null.');
|
|
583
|
+
if (!isString(common.script))
|
|
584
|
+
throw new TypeError('Invalid metadata: "script" must be a string or null.');
|
|
585
|
+
if (!isString(common.language))
|
|
586
|
+
throw new TypeError('Invalid metadata: "language" must be a string or null.');
|
|
587
|
+
if (!isString(common.copyright))
|
|
588
|
+
throw new TypeError('Invalid metadata: "copyright" must be a string or null.');
|
|
589
|
+
if (!isString(common.license))
|
|
590
|
+
throw new TypeError('Invalid metadata: "license" must be a string or null.');
|
|
591
|
+
if (!isString(common.encodedby))
|
|
592
|
+
throw new TypeError('Invalid metadata: "encodedby" must be a string or null.');
|
|
593
|
+
if (!isString(common.encodersettings))
|
|
594
|
+
throw new TypeError('Invalid metadata: "encodersettings" must be a string or null.');
|
|
595
|
+
if (!isString(common.barcode))
|
|
596
|
+
throw new TypeError('Invalid metadata: "barcode" must be a string or null.');
|
|
597
|
+
if (!isString(common.asin))
|
|
598
|
+
throw new TypeError('Invalid metadata: "asin" must be a string or null.');
|
|
599
|
+
if (!isString(common.website))
|
|
600
|
+
throw new TypeError('Invalid metadata: "website" must be a string or null.');
|
|
601
|
+
if (!isString(common.originalalbum))
|
|
602
|
+
throw new TypeError('Invalid metadata: "originalalbum" must be a string or null.');
|
|
603
|
+
if (!isString(common.originalartist))
|
|
604
|
+
throw new TypeError('Invalid metadata: "originalartist" must be a string or null.');
|
|
605
|
+
if (!isString(common.key))
|
|
606
|
+
throw new TypeError('Invalid metadata: "key" must be a string or null.');
|
|
607
|
+
if (!isString(common.movement))
|
|
608
|
+
throw new TypeError('Invalid metadata: "movement" must be a string or null.');
|
|
609
|
+
if (!isString(common.podcastId))
|
|
610
|
+
throw new TypeError('Invalid metadata: "podcastId" must be a string or null.');
|
|
338
611
|
|
|
339
612
|
// Validate Arrays
|
|
340
|
-
if (!isArray(common.albumartists, (
|
|
341
|
-
throw new TypeError('Invalid metadata: "albumartists" must be an array of
|
|
342
|
-
if (!isArray(common.genre, (
|
|
343
|
-
throw new TypeError('Invalid metadata: "genre" must be an array of
|
|
344
|
-
if (!isArray(common.label, (
|
|
345
|
-
throw new TypeError('Invalid metadata: "label" must be an array of
|
|
346
|
-
if (!isArray(common.composer, (
|
|
347
|
-
throw new TypeError('Invalid metadata: "composer" must be an array of
|
|
348
|
-
if (!isArray(common.artists, (
|
|
349
|
-
throw new TypeError('Invalid metadata: "artists" must be an array of
|
|
613
|
+
if (!isArray(common.albumartists, (v) => typeof v === 'string'))
|
|
614
|
+
throw new TypeError('Invalid metadata: "albumartists" must be an array of strings.');
|
|
615
|
+
if (!isArray(common.genre, (v) => typeof v === 'string'))
|
|
616
|
+
throw new TypeError('Invalid metadata: "genre" must be an array of strings.');
|
|
617
|
+
if (!isArray(common.label, (v) => typeof v === 'string'))
|
|
618
|
+
throw new TypeError('Invalid metadata: "label" must be an array of strings.');
|
|
619
|
+
if (!isArray(common.composer, (v) => typeof v === 'string'))
|
|
620
|
+
throw new TypeError('Invalid metadata: "composer" must be an array of strings.');
|
|
621
|
+
if (!isArray(common.artists, (v) => typeof v === 'string'))
|
|
622
|
+
throw new TypeError('Invalid metadata: "artists" must be an array of strings.');
|
|
623
|
+
if (!isArray(common.lyricist, (v) => typeof v === 'string'))
|
|
624
|
+
throw new TypeError('Invalid metadata: "lyricist" must be an array of strings.');
|
|
625
|
+
if (!isArray(common.writer, (v) => typeof v === 'string'))
|
|
626
|
+
throw new TypeError('Invalid metadata: "writer" must be an array of strings.');
|
|
627
|
+
if (!isArray(common.conductor, (v) => typeof v === 'string'))
|
|
628
|
+
throw new TypeError('Invalid metadata: "conductor" must be an array of strings.');
|
|
629
|
+
if (!isArray(common.remixer, (v) => typeof v === 'string'))
|
|
630
|
+
throw new TypeError('Invalid metadata: "remixer" must be an array of strings.');
|
|
631
|
+
if (!isArray(common.arranger, (v) => typeof v === 'string'))
|
|
632
|
+
throw new TypeError('Invalid metadata: "arranger" must be an array of strings.');
|
|
633
|
+
if (!isArray(common.engineer, (v) => typeof v === 'string'))
|
|
634
|
+
throw new TypeError('Invalid metadata: "engineer" must be an array of strings.');
|
|
635
|
+
if (!isArray(common.publisher, (v) => typeof v === 'string'))
|
|
636
|
+
throw new TypeError('Invalid metadata: "publisher" must be an array of strings.');
|
|
637
|
+
if (!isArray(common.producer, (v) => typeof v === 'string'))
|
|
638
|
+
throw new TypeError('Invalid metadata: "producer" must be an array of strings.');
|
|
639
|
+
if (!isArray(common.djmixer, (v) => typeof v === 'string'))
|
|
640
|
+
throw new TypeError('Invalid metadata: "djmixer" must be an array of strings.');
|
|
641
|
+
if (!isArray(common.mixer, (v) => typeof v === 'string'))
|
|
642
|
+
throw new TypeError('Invalid metadata: "mixer" must be an array of strings.');
|
|
643
|
+
if (!isArray(common.technician, (v) => typeof v === 'string'))
|
|
644
|
+
throw new TypeError('Invalid metadata: "technician" must be an array of strings.');
|
|
645
|
+
if (!isArray(common.subtitle, (v) => typeof v === 'string'))
|
|
646
|
+
throw new TypeError('Invalid metadata: "subtitle" must be an array of strings.');
|
|
647
|
+
if (!isArray(common.description, (v) => typeof v === 'string'))
|
|
648
|
+
throw new TypeError('Invalid metadata: "description" must be an array of strings.');
|
|
649
|
+
if (!isArray(common.discsubtitle, (v) => typeof v === 'string'))
|
|
650
|
+
throw new TypeError('Invalid metadata: "discsubtitle" must be an array of strings.');
|
|
651
|
+
if (!isArray(common.releasetype, (v) => typeof v === 'string'))
|
|
652
|
+
throw new TypeError('Invalid metadata: "releasetype" must be an array of strings.');
|
|
653
|
+
if (!isArray(common.isrc, (v) => typeof v === 'string'))
|
|
654
|
+
throw new TypeError('Invalid metadata: "isrc" must be an array of strings.');
|
|
655
|
+
if (!isArray(common.performer_instrument, (v) => typeof v === 'string'))
|
|
656
|
+
throw new TypeError('Invalid metadata: "performer_instrument" must be an array of strings.');
|
|
657
|
+
if (!isArray(common.notes, (v) => typeof v === 'string'))
|
|
658
|
+
throw new TypeError('Invalid metadata: "notes" must be an array of strings.');
|
|
659
|
+
if (!isArray(common.category, (v) => typeof v === 'string'))
|
|
660
|
+
throw new TypeError('Invalid metadata: "category" must be an array of strings.');
|
|
661
|
+
if (!isArray(common.keywords, (v) => typeof v === 'string'))
|
|
662
|
+
throw new TypeError('Invalid metadata: "keywords" must be an array of strings.');
|
|
663
|
+
if (!isArray(common.catalognumber, (v) => typeof v === 'string'))
|
|
664
|
+
throw new TypeError('Invalid metadata: "catalognumber" must be an array of strings.');
|
|
665
|
+
|
|
666
|
+
// Validate Complex Arrays
|
|
667
|
+
|
|
350
668
|
if (
|
|
351
669
|
!isArray(
|
|
352
670
|
common.picture,
|
|
@@ -362,19 +680,77 @@ const validateMediaContent = (common, isPartial) => {
|
|
|
362
680
|
)
|
|
363
681
|
throw new TypeError('Invalid metadata: "picture" must be an array of pictures.');
|
|
364
682
|
|
|
683
|
+
if (
|
|
684
|
+
!isArray(
|
|
685
|
+
common.comment,
|
|
686
|
+
(v) =>
|
|
687
|
+
v !== null &&
|
|
688
|
+
typeof v === 'object' &&
|
|
689
|
+
isString(v.descriptor) &&
|
|
690
|
+
isString(v.language) &&
|
|
691
|
+
isString(v.text),
|
|
692
|
+
)
|
|
693
|
+
)
|
|
694
|
+
throw new TypeError('Invalid metadata: "comment" must be an array of valid IComment objects.');
|
|
695
|
+
|
|
696
|
+
if (
|
|
697
|
+
!isArray(common.lyrics, (v) => {
|
|
698
|
+
const isLyricsContentType = (/** @type {LyricsContentType|null|undefined} */ v) =>
|
|
699
|
+
v === undefined ||
|
|
700
|
+
(v !== null &&
|
|
701
|
+
typeof v === 'object' &&
|
|
702
|
+
isNumber(v.other) &&
|
|
703
|
+
isNumber(v.lyrics) &&
|
|
704
|
+
isNumber(v.text) &&
|
|
705
|
+
isNumber(v.movement_part) &&
|
|
706
|
+
isNumber(v.events) &&
|
|
707
|
+
isNumber(v.chord) &&
|
|
708
|
+
isNumber(v.trivia_pop));
|
|
709
|
+
|
|
710
|
+
const isTimestampFormat = (/** @type {TimestampFormat|null|undefined} */ v) =>
|
|
711
|
+
v === undefined ||
|
|
712
|
+
(v !== null &&
|
|
713
|
+
typeof v === 'object' &&
|
|
714
|
+
isNumber(v.notSynchronized) &&
|
|
715
|
+
isNumber(v.mpegFrameNumber) &&
|
|
716
|
+
isNumber(v.milliseconds));
|
|
717
|
+
|
|
718
|
+
return (
|
|
719
|
+
v !== null &&
|
|
720
|
+
typeof v === 'object' &&
|
|
721
|
+
isString(v.text) &&
|
|
722
|
+
isString(v.descriptor) &&
|
|
723
|
+
isString(v.language) &&
|
|
724
|
+
(!Array.isArray(v) ||
|
|
725
|
+
isArray(
|
|
726
|
+
v.syncText,
|
|
727
|
+
(v) => v !== null && typeof v === 'object' && isString(v.text) && isNumber(v.timestamp),
|
|
728
|
+
)) &&
|
|
729
|
+
isTimestampFormat(v.timeStampFormat) &&
|
|
730
|
+
isLyricsContentType(v.contentType)
|
|
731
|
+
);
|
|
732
|
+
})
|
|
733
|
+
)
|
|
734
|
+
throw new TypeError('Invalid metadata: "lyrics" must be an array of valid ILyricsTag objects.');
|
|
735
|
+
|
|
736
|
+
if (
|
|
737
|
+
!isArray(
|
|
738
|
+
common.rating,
|
|
739
|
+
(v) => v !== null && typeof v === 'object' && isString(v.source) && isNumber(v.rating),
|
|
740
|
+
)
|
|
741
|
+
)
|
|
742
|
+
throw new TypeError('Invalid metadata: "rating" must be an array of valid IRating objects.');
|
|
743
|
+
|
|
744
|
+
// Validate Nested Objects (Disk, Track, Movement, etc.)
|
|
745
|
+
|
|
365
746
|
/**
|
|
366
747
|
* Validate Nested Objects (Disk and Track)
|
|
367
748
|
* @param {string} name
|
|
368
749
|
* @param {MediaNumber|null} [info]
|
|
369
750
|
*/
|
|
370
751
|
const validateTrackInfo = (name, info) => {
|
|
371
|
-
|
|
372
|
-
if (
|
|
373
|
-
|
|
374
|
-
if (info === undefined || info === null) {
|
|
375
|
-
throw new TypeError(`Invalid metadata: "${name}" is required.`);
|
|
376
|
-
}
|
|
377
|
-
|
|
752
|
+
if (isUndefinedAllowed(info)) return;
|
|
753
|
+
if (info === null) return;
|
|
378
754
|
if (typeof info !== 'object')
|
|
379
755
|
throw new TypeError(`Invalid metadata: "${name}" must be an object or null.`);
|
|
380
756
|
if (typeof info.no !== 'number' && info.no !== null)
|
|
@@ -385,26 +761,7 @@ const validateMediaContent = (common, isPartial) => {
|
|
|
385
761
|
|
|
386
762
|
validateTrackInfo('disk', common.disk);
|
|
387
763
|
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);
|
|
764
|
+
validateTrackInfo('movementIndex', common.movementIndex);
|
|
408
765
|
};
|
|
409
766
|
|
|
410
767
|
/**
|
|
@@ -422,29 +779,87 @@ const extractMediaId3Tags = async (url, parseFile, convertBase64toBlob = true) =
|
|
|
422
779
|
checkString(url, 'url');
|
|
423
780
|
checkFunction(parseFile, 'parseFile');
|
|
424
781
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
const response = await fetch(url);
|
|
782
|
+
// 1. Download the file from the provided URL
|
|
783
|
+
const response = await fetch(url);
|
|
428
784
|
|
|
429
|
-
|
|
430
|
-
|
|
785
|
+
if (!response.ok)
|
|
786
|
+
throw new Error(`Failed to fetch audio file: ${response.status} ${response.statusText}`);
|
|
431
787
|
|
|
432
|
-
|
|
433
|
-
|
|
788
|
+
// 2. Convert the response into a Blob so the parser can read it
|
|
789
|
+
const blob = await response.blob();
|
|
790
|
+
|
|
791
|
+
// 3. Use the provided parser function on the Blob to read metadata
|
|
792
|
+
const metadata = await parseFile(blob);
|
|
434
793
|
|
|
435
|
-
|
|
436
|
-
|
|
794
|
+
// 4. Complete Validation of the parsed metadata structure
|
|
795
|
+
if (!metadata || typeof metadata.common !== 'object')
|
|
796
|
+
throw new TypeError('Invalid metadata: "common" property is missing.');
|
|
797
|
+
if (typeof metadata.common['performer:instrument'] !== undefined)
|
|
798
|
+
// @ts-ignore
|
|
799
|
+
metadata.common.performer_instrument = metadata.common['performer:instrument'];
|
|
800
|
+
valFetchMediaContent(metadata);
|
|
437
801
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
throw new TypeError('Invalid metadata: "common" property is missing or not an object.');
|
|
802
|
+
const common = metadata.common;
|
|
803
|
+
valMediaContentMetadata(common);
|
|
441
804
|
|
|
442
|
-
|
|
443
|
-
|
|
805
|
+
/**
|
|
806
|
+
* @type {(value: [string, any]) => boolean}
|
|
807
|
+
*/
|
|
808
|
+
const filterContent = ([_, value]) =>
|
|
809
|
+
Array.isArray(value)
|
|
810
|
+
? value.length > 0
|
|
811
|
+
: objChecker.isJsonObject(value)
|
|
812
|
+
? (() => {
|
|
813
|
+
const d = Object.fromEntries(Object.entries(value).filter(filterContent));
|
|
814
|
+
const amount = objChecker.countObj(d);
|
|
815
|
+
return amount > 0;
|
|
816
|
+
})()
|
|
817
|
+
: value !== null;
|
|
818
|
+
|
|
819
|
+
// Fix blob values.
|
|
820
|
+
const picture =
|
|
821
|
+
common?.picture?.map((value) => ({
|
|
822
|
+
...value,
|
|
823
|
+
data: convertBase64toBlob ? convertToBlobUrl(value.data, value.format) : value.data,
|
|
824
|
+
})) ?? [];
|
|
825
|
+
|
|
826
|
+
metadata.common = {
|
|
827
|
+
...metadata.common,
|
|
828
|
+
picture: picture.map((value) => ({
|
|
829
|
+
...value,
|
|
830
|
+
data: '',
|
|
831
|
+
})),
|
|
832
|
+
};
|
|
444
833
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
834
|
+
// Sanitize metadata.native to prevent Uint8Array leakage
|
|
835
|
+
if (convertBase64toBlob) {
|
|
836
|
+
Object.keys(metadata.native).forEach((key) => {
|
|
837
|
+
metadata.native[key] = metadata.native[key].map((item) => {
|
|
838
|
+
// Check if item.value is an object and contains a 'data' property that is a Uint8Array
|
|
839
|
+
if (item.value && typeof item.value === 'object' && item.value.data instanceof Uint8Array) {
|
|
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
|
+
}
|
|
857
|
+
|
|
858
|
+
// 5. Return the specific metadata fields requested
|
|
859
|
+
// We structure the return to match the MediaContentMetadata typedef
|
|
860
|
+
/** @type {MediaContentMetadata<PictureDataType>} */
|
|
861
|
+
const result = Object.fromEntries(
|
|
862
|
+
Object.entries({
|
|
448
863
|
title: common?.title ?? null,
|
|
449
864
|
album: common?.album ?? null,
|
|
450
865
|
albumartist: common?.albumartist ?? null,
|
|
@@ -455,18 +870,91 @@ const extractMediaId3Tags = async (url, parseFile, convertBase64toBlob = true) =
|
|
|
455
870
|
year: common?.year ?? null,
|
|
456
871
|
artist: common?.artist ?? null,
|
|
457
872
|
artists: common?.artists ?? [],
|
|
458
|
-
disk: common?.disk ? { no: common.disk.no, of: common.disk.of } :
|
|
459
|
-
track: common?.track ? { no: common.track.no, of: common.track.of } :
|
|
460
|
-
|
|
461
|
-
common
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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;
|
|
470
958
|
};
|
|
471
959
|
|
|
472
960
|
/**
|
|
@@ -519,7 +1007,7 @@ const parseMediaMetadata = async (
|
|
|
519
1007
|
metadata = {},
|
|
520
1008
|
parseFile = (data) => {
|
|
521
1009
|
if (!(data instanceof Blob)) throw new TypeError('Expected "data" to be a Blob.');
|
|
522
|
-
return new Promise((
|
|
1010
|
+
return new Promise((_, reject) => reject(new TypeError('parseFile library not found.')));
|
|
523
1011
|
},
|
|
524
1012
|
callbacks = {},
|
|
525
1013
|
convertBase64toBlob = true,
|
|
@@ -570,11 +1058,11 @@ const parseMediaMetadata = async (
|
|
|
570
1058
|
|
|
571
1059
|
const notifyError = (/** @type {Error} */ error, /** @type {LoadingErrorStage} */ stage) => {
|
|
572
1060
|
if (!(error instanceof Error)) throw new TypeError('Expected "error" to be an Error.');
|
|
573
|
-
checkString(stage, 'stage');
|
|
1061
|
+
checkString(stage, 'stage');
|
|
574
1062
|
|
|
575
1063
|
if (callbacks.onError) {
|
|
576
1064
|
callbacks.onError({
|
|
577
|
-
error,
|
|
1065
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
578
1066
|
url: url || (source instanceof HTMLMediaElement ? source.src : 'unknown'),
|
|
579
1067
|
stage,
|
|
580
1068
|
});
|
|
@@ -678,11 +1166,38 @@ const parseMediaMetadata = async (
|
|
|
678
1166
|
|
|
679
1167
|
// 5. Final Merge Logic
|
|
680
1168
|
// Priority: Manual Metadata (highest) > Extracted ID3 > Default values
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* @template {Partial<MediaContent<PictureData>>} Metadata
|
|
1172
|
+
* @param {Metadata} meta
|
|
1173
|
+
*/
|
|
1174
|
+
const mergeMetadata = (meta) => {
|
|
1175
|
+
const metadataEdit = { ...meta };
|
|
1176
|
+
if (objChecker.isJsonObject(metadataEdit._fetchData)) {
|
|
1177
|
+
if (objChecker.isJsonObject(extractedMetadata._fetchData)) {
|
|
1178
|
+
metadataEdit._fetchData = {
|
|
1179
|
+
...extractedMetadata._fetchData,
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
if (objChecker.isJsonObject(meta._fetchData)) {
|
|
1183
|
+
if (typeof meta._fetchData.common !== 'undefined')
|
|
1184
|
+
metadataEdit._fetchData.common = meta._fetchData.common;
|
|
1185
|
+
if (typeof meta._fetchData.native !== 'undefined')
|
|
1186
|
+
metadataEdit._fetchData.native = meta._fetchData.native;
|
|
1187
|
+
if (typeof meta._fetchData.format !== 'undefined')
|
|
1188
|
+
metadataEdit._fetchData.format = meta._fetchData.format;
|
|
1189
|
+
if (typeof meta._fetchData.quality !== 'undefined')
|
|
1190
|
+
metadataEdit._fetchData.quality = meta._fetchData.quality;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
return metadataEdit;
|
|
1194
|
+
};
|
|
1195
|
+
|
|
681
1196
|
const finalContent = {
|
|
682
1197
|
...baseData,
|
|
683
|
-
...defaultMetadata,
|
|
1198
|
+
...mergeMetadata(defaultMetadata),
|
|
684
1199
|
...extractedMetadata,
|
|
685
|
-
...metadata,
|
|
1200
|
+
...mergeMetadata(metadata),
|
|
686
1201
|
// Explicitly ensure title and artist are resolved from the hierarchy
|
|
687
1202
|
title: extractedMetadata.title || metadata.title || getFallbackTitleFromUrl(url),
|
|
688
1203
|
artist:
|
|
@@ -693,28 +1208,21 @@ const parseMediaMetadata = async (
|
|
|
693
1208
|
|
|
694
1209
|
// Notify Success
|
|
695
1210
|
if (callbacks.onProgress) {
|
|
696
|
-
callbacks.onProgress({
|
|
697
|
-
status: 'success',
|
|
698
|
-
stage: 'COMPLETE',
|
|
699
|
-
url: url,
|
|
700
|
-
});
|
|
1211
|
+
callbacks.onProgress({ status: 'success', stage: 'COMPLETE', url: url });
|
|
701
1212
|
}
|
|
702
1213
|
|
|
703
1214
|
return /** @type {MediaContent<PictureData>} */ (finalContent);
|
|
704
1215
|
} catch (err) {
|
|
705
1216
|
// If it's already a MediaLoadingError, re-throw it.
|
|
706
1217
|
// Otherwise, wrap it.
|
|
707
|
-
if (err instanceof MediaLoadingError)
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
notifyError(wrappedError, 'INITIALIZING');
|
|
716
|
-
throw wrappedError;
|
|
717
|
-
}
|
|
1218
|
+
if (err instanceof MediaLoadingError) throw err;
|
|
1219
|
+
const wrappedError = new MediaLoadingError(
|
|
1220
|
+
err instanceof Error ? err.message : 'UNKNOWN ERROR',
|
|
1221
|
+
url,
|
|
1222
|
+
'INITIALIZING',
|
|
1223
|
+
);
|
|
1224
|
+
notifyError(wrappedError, 'INITIALIZING');
|
|
1225
|
+
throw wrappedError;
|
|
718
1226
|
}
|
|
719
1227
|
};
|
|
720
1228
|
|
|
@@ -725,8 +1233,7 @@ exports.convertToBlobUrl = convertToBlobUrl;
|
|
|
725
1233
|
exports.extractMediaId3Tags = extractMediaId3Tags;
|
|
726
1234
|
exports.generateSimpleHash = generateSimpleHash;
|
|
727
1235
|
exports.getMediaContentBase = getMediaContentBase;
|
|
728
|
-
exports.getMediaContentMetadata = getMediaContentMetadata;
|
|
729
1236
|
exports.parseMediaMetadata = parseMediaMetadata;
|
|
730
1237
|
exports.revokeContentUrls = revokeContentUrls;
|
|
1238
|
+
exports.valFetchMediaContent = valFetchMediaContent;
|
|
731
1239
|
exports.valMediaContentMetadata = valMediaContentMetadata;
|
|
732
|
-
exports.valMediaContentMetadataPartial = valMediaContentMetadataPartial;
|