mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-27 02:43:48 +02:00
Surface repeated keys are string[] for Vorbis-style metadata (fixes #490)
This commit is contained in:
+21
-13
@@ -2597,7 +2597,13 @@ export const readVorbisComments = (bytes: Uint8Array, metadataTags: MetadataTags
|
||||
const value = string.slice(separatorIndex + 1);
|
||||
|
||||
metadataTags.raw ??= {};
|
||||
metadataTags.raw[key] ??= value;
|
||||
if (Array.isArray(metadataTags.raw[key])) {
|
||||
metadataTags.raw[key] = [...metadataTags.raw[key], value];
|
||||
} else if (typeof metadataTags.raw[key] === 'string') {
|
||||
metadataTags.raw[key] = [metadataTags.raw[key], value];
|
||||
} else {
|
||||
metadataTags.raw[key] ??= value;
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case 'TITLE': {
|
||||
@@ -2728,7 +2734,7 @@ export const createVorbisComments = (headerBytes: Uint8Array, tags: MetadataTags
|
||||
|
||||
commentHeaderParts.push(currentBuffer);
|
||||
|
||||
const writtenTags = new Set<string>();
|
||||
const writtenTags: string[] = [];
|
||||
const addCommentTag = (key: string, value: string) => {
|
||||
const joined = `${key}=${value}`;
|
||||
const encoded = textEncoder.encode(joined);
|
||||
@@ -2740,7 +2746,7 @@ export const createVorbisComments = (headerBytes: Uint8Array, tags: MetadataTags
|
||||
currentBuffer.set(encoded, 4);
|
||||
|
||||
commentHeaderParts.push(currentBuffer);
|
||||
writtenTags.add(key);
|
||||
writtenTags.push(key);
|
||||
};
|
||||
|
||||
for (const { key, value } of keyValueIterator(tags)) {
|
||||
@@ -2770,12 +2776,7 @@ export const createVorbisComments = (headerBytes: Uint8Array, tags: MetadataTags
|
||||
}; break;
|
||||
|
||||
case 'date': {
|
||||
const rawVersion = tags.raw?.['DATE'] ?? tags.raw?.['date'];
|
||||
if (rawVersion && typeof rawVersion === 'string') {
|
||||
addCommentTag('DATE', rawVersion);
|
||||
} else {
|
||||
addCommentTag('DATE', value.toISOString().slice(0, 10));
|
||||
}
|
||||
addCommentTag('DATE', value.toISOString().slice(0, 10));
|
||||
}; break;
|
||||
|
||||
case 'comment': {
|
||||
@@ -2803,8 +2804,7 @@ export const createVorbisComments = (headerBytes: Uint8Array, tags: MetadataTags
|
||||
}; break;
|
||||
|
||||
case 'images': {
|
||||
// For example, in .flac, we put the pictures in a different section,
|
||||
// not in the Vorbis comment header.
|
||||
// For example, in .flac, we put the pictures in a different section, not in the Vorbis comment header.
|
||||
if (!writeImages) {
|
||||
break;
|
||||
}
|
||||
@@ -2861,18 +2861,26 @@ export const createVorbisComments = (headerBytes: Uint8Array, tags: MetadataTags
|
||||
if (tags.raw) {
|
||||
for (const key in tags.raw) {
|
||||
const value = tags.raw[key] ?? tags.raw[key.toLowerCase()];
|
||||
if (key === 'vendor' || value == null || writtenTags.has(key)) {
|
||||
if (key === 'vendor' || value == null || writtenTags.includes(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
addCommentTag(key, value);
|
||||
} else if (Array.isArray(value)) {
|
||||
// String arrays turn into the tag being repeated for each element
|
||||
const isOnlyStrings = value.every(x => typeof x === 'string');
|
||||
if (isOnlyStrings) {
|
||||
for (const elem of value) {
|
||||
addCommentTag(key, elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const listLengthBuffer = new Uint8Array(4);
|
||||
toDataView(listLengthBuffer).setUint32(0, writtenTags.size, true);
|
||||
toDataView(listLengthBuffer).setUint32(0, writtenTags.length, true);
|
||||
commentHeaderParts.splice(2, 0, listLengthBuffer); // Insert after the header and vendor section
|
||||
|
||||
// Merge all comment header parts into a single buffer
|
||||
|
||||
+1
-1
@@ -1101,7 +1101,7 @@ export class Conversion {
|
||||
const inputAndOutputFormatMatch = inputFormat.mimeType === this.output.format.mimeType;
|
||||
const rawTagsAreUnchanged = inputTags.raw === outputTags.raw;
|
||||
|
||||
if (inputTags.raw && rawTagsAreUnchanged && !inputAndOutputFormatMatch) {
|
||||
if (rawTagsAreUnchanged && !inputAndOutputFormatMatch) {
|
||||
// If the input and output formats aren't the same, copying over raw metadata tags makes no sense and
|
||||
// only results in junk tags, so let's cut them out.
|
||||
delete outputTags.raw;
|
||||
|
||||
+8
-5
@@ -76,14 +76,16 @@ export type MetadataTags = {
|
||||
* user-defined text frames are exposed as a `Record<string, string>`.
|
||||
* - ADTS: The ID3v2 tags, just like in MP3.
|
||||
* - Ogg: The key-value string pairs from the Vorbis-style comment header (see RFC 7845, Section 5.2).
|
||||
* Additionally, the `'vendor'` key refers to the vendor string within this header.
|
||||
* Additionally, the `'vendor'` key refers to the vendor string within this header. If a key exists more than once,
|
||||
* a string array is used instead.
|
||||
* - WAVE: The individual metadata chunks within the RIFF INFO chunk. Values are always ISO 8859-1 strings.
|
||||
* - FLAC: The key-value string pairs from the vorbis metadata block (see RFC 9639, Section D.2.3).
|
||||
* Additionally, the `'vendor'` key refers to the vendor string within this header. If ID3v2 tags appear at the
|
||||
* start of the file, their content is stored just like for MP3.
|
||||
* Additionally, the `'vendor'` key refers to the vendor string within this header. If a key exists more than once,
|
||||
* a string array is used instead. If ID3v2 tags appear at the start of the file, their content is stored just like
|
||||
* for MP3.
|
||||
* - MPEG-TS: Not supported.
|
||||
*/
|
||||
raw?: Record<string, string | Uint8Array | RichImageData | AttachedFile | Record<string, string> | null>;
|
||||
raw?: Record<string, string | string[] | Uint8Array | RichImageData | AttachedFile | Record<string, string> | null>;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -238,13 +240,14 @@ export const validateMetadataTags = (tags: MetadataTags) => {
|
||||
if (
|
||||
value !== null
|
||||
&& typeof value !== 'string'
|
||||
&& !(Array.isArray(value) && value.every(x => typeof x === 'string'))
|
||||
&& !(value instanceof Uint8Array)
|
||||
&& !(value instanceof RichImageData)
|
||||
&& !(value instanceof AttachedFile)
|
||||
&& !isRecordStringString(value)
|
||||
) {
|
||||
throw new TypeError(
|
||||
'Each value in tags.raw must be a string, Uint8Array, RichImageData, AttachedFile, '
|
||||
'Each value in tags.raw must be a string, string array, Uint8Array, RichImageData, AttachedFile, '
|
||||
+ 'Record<string, string>, or null.',
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user