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