Add lyrics field to metadata

This commit is contained in:
Vanilagy
2025-08-26 17:36:49 +02:00
parent fb0c41aa09
commit c82ebb93da
5 changed files with 63 additions and 24 deletions
+2 -1
View File
@@ -54,7 +54,8 @@
data: blobData, data: blobData,
kind: 'coverFront', kind: 'coverFront',
mimeType: 'image/jpeg' mimeType: 'image/jpeg'
}] }],
lyrics: "There's no way\nThat it's not going there"
}); });
const conversion = await Mediabunny.Conversion.init({ const conversion = await Mediabunny.Conversion.init({
+14 -9
View File
@@ -9,6 +9,7 @@ export type MediaMetadata = {
discNumber?: number; discNumber?: number;
genre?: string; genre?: string;
releasedAt?: Date; releasedAt?: Date;
lyrics?: string;
images?: { images?: {
data: Uint8Array; data: Uint8Array;
mimeType: string; mimeType: string;
@@ -47,6 +48,9 @@ export const validateMediaMetadata = (metadata: MediaMetadata) => {
if (metadata.releasedAt !== undefined && !(metadata.releasedAt instanceof Date)) { if (metadata.releasedAt !== undefined && !(metadata.releasedAt instanceof Date)) {
throw new TypeError('metadata.releasedAt, when provided, must be a Date.'); throw new TypeError('metadata.releasedAt, when provided, must be a Date.');
} }
if (metadata.lyrics !== undefined && typeof metadata.lyrics !== 'string') {
throw new TypeError('metadata.lyrics, when provided, must be a string.');
}
if (metadata.comment !== undefined && typeof metadata.comment !== 'string') { if (metadata.comment !== undefined && typeof metadata.comment !== 'string') {
throw new TypeError('metadata.comment, when provided, must be a string.'); throw new TypeError('metadata.comment, when provided, must be a string.');
} }
@@ -72,14 +76,15 @@ export const validateMediaMetadata = (metadata: MediaMetadata) => {
}; };
export const mediaMetadataIsEmpty = (metadata: MediaMetadata) => { export const mediaMetadataIsEmpty = (metadata: MediaMetadata) => {
return !metadata.title return metadata.title === undefined
&& !metadata.artist && metadata.artist === undefined
&& !metadata.album && metadata.album === undefined
&& !metadata.albumArtist && metadata.albumArtist === undefined
&& !metadata.trackNumber && metadata.trackNumber === undefined
&& !metadata.discNumber && metadata.discNumber === undefined
&& !metadata.genre && metadata.genre === undefined
&& !metadata.releasedAt && metadata.releasedAt === undefined
&& !metadata.comment && metadata.lyrics === undefined
&& metadata.comment === undefined
&& (!metadata.images || metadata.images.length === 0); && (!metadata.images || metadata.images.length === 0);
}; };
+13 -10
View File
@@ -130,38 +130,41 @@ export class Mp3Muxer extends Muxer {
const startPos = this.writer.getPos(); const startPos = this.writer.getPos();
if (metadata.title) { if (metadata.title !== undefined) {
this.mp3Writer.writeId3V2TextFrame('TIT2', metadata.title); this.mp3Writer.writeId3V2TextFrame('TIT2', metadata.title);
} }
if (metadata.artist) { if (metadata.artist !== undefined) {
this.mp3Writer.writeId3V2TextFrame('TPE1', metadata.artist); this.mp3Writer.writeId3V2TextFrame('TPE1', metadata.artist);
} }
if (metadata.album) { if (metadata.album !== undefined) {
this.mp3Writer.writeId3V2TextFrame('TALB', metadata.album); this.mp3Writer.writeId3V2TextFrame('TALB', metadata.album);
} }
if (metadata.albumArtist) { if (metadata.albumArtist !== undefined) {
this.mp3Writer.writeId3V2TextFrame('TPE2', metadata.albumArtist); this.mp3Writer.writeId3V2TextFrame('TPE2', metadata.albumArtist);
} }
if (metadata.trackNumber) { if (metadata.trackNumber !== undefined) {
this.mp3Writer.writeId3V2TextFrame('TRCK', metadata.trackNumber.toString()); this.mp3Writer.writeId3V2TextFrame('TRCK', metadata.trackNumber.toString());
} }
if (metadata.discNumber) { if (metadata.discNumber !== undefined) {
this.mp3Writer.writeId3V2TextFrame('TPOS', metadata.discNumber.toString()); this.mp3Writer.writeId3V2TextFrame('TPOS', metadata.discNumber.toString());
} }
if (metadata.genre) { if (metadata.genre !== undefined) {
this.mp3Writer.writeId3V2TextFrame('TCON', metadata.genre); this.mp3Writer.writeId3V2TextFrame('TCON', metadata.genre);
} }
if (metadata.releasedAt) { if (metadata.releasedAt !== undefined) {
this.mp3Writer.writeId3V2TextFrame('TDRC', metadata.releasedAt.toISOString().split('.')[0]!); this.mp3Writer.writeId3V2TextFrame('TDRC', metadata.releasedAt.toISOString().split('.')[0]!);
} }
if (metadata.comment) { if (metadata.lyrics !== undefined) {
this.mp3Writer.writeId3V2LyricsFrame(metadata.lyrics);
}
if (metadata.comment !== undefined) {
this.mp3Writer.writeId3V2CommentFrame(metadata.comment); this.mp3Writer.writeId3V2CommentFrame(metadata.comment);
} }
if (metadata.images) { if (metadata.images) {
const pictureTypeMap = { coverFront: 0x03, coverBack: 0x04, unknown: 0x00 }; const pictureTypeMap = { coverFront: 0x03, coverBack: 0x04, unknown: 0x00 };
for (const image of metadata.images) { for (const image of metadata.images) {
const pictureType = pictureTypeMap[image.kind]; const pictureType = pictureTypeMap[image.kind];
const description = image.description || ''; const description = image.description ?? '';
this.mp3Writer.writeId3V2ApicFrame(image.mimeType, pictureType, description, image.data); this.mp3Writer.writeId3V2ApicFrame(image.mimeType, pictureType, description, image.data);
} }
} }
+8
View File
@@ -350,6 +350,14 @@ export class Mp3Reader {
} }
}; break; }; break;
case 'USLT':
case 'ULT': {
const encoding = reader.readU8();
reader.pos += 3; // Skip language
reader.readId3V2Text(encoding, frameEndPos); // Short content description
metadata.lyrics ??= reader.readId3V2Text(encoding, frameEndPos);
}; break;
case 'COMM': case 'COMM':
case 'COM': { case 'COM': {
const encoding = reader.readU8(); const encoding = reader.readU8();
+26 -4
View File
@@ -17,6 +17,7 @@ import {
SAMPLING_RATES, SAMPLING_RATES,
XING, XING,
} from '../../shared/mp3-misc'; } from '../../shared/mp3-misc';
import { Id3V2TextEncoding } from './mp3-reader';
export type XingFrameData = { export type XingFrameData = {
mpegVersionId: number; mpegVersionId: number;
@@ -89,7 +90,7 @@ export class Mp3Writer {
this.writeSynchsafeU32(frameSize); this.writeSynchsafeU32(frameSize);
this.writeU16(0x0000); this.writeU16(0x0000);
this.writeU8(useIso88591 ? 0x00 : 0x03); this.writeU8(useIso88591 ? Id3V2TextEncoding.ISO_8859_1 : Id3V2TextEncoding.UTF_8);
if (useIso88591) { if (useIso88591) {
this.writeIsoString(text); this.writeIsoString(text);
} else { } else {
@@ -97,17 +98,38 @@ export class Mp3Writer {
} }
} }
writeId3V2LyricsFrame(lyrics: string) {
const useIso88591 = isIso88591Compatible(lyrics);
const shortDescription = '';
const frameSize = 1 + 3 + shortDescription.length + 1 + lyrics.length + 1;
this.writeAscii('USLT');
this.writeSynchsafeU32(frameSize);
this.writeU16(0x0000);
this.writeU8(useIso88591 ? Id3V2TextEncoding.ISO_8859_1 : Id3V2TextEncoding.UTF_8);
this.writeAscii('und');
if (useIso88591) {
this.writeIsoString(shortDescription);
this.writeIsoString(lyrics);
} else {
this.writeUtf8String(shortDescription);
this.writeUtf8String(lyrics);
}
}
writeId3V2CommentFrame(comment: string) { writeId3V2CommentFrame(comment: string) {
const useIso88591 = isIso88591Compatible(comment); const useIso88591 = isIso88591Compatible(comment);
const textDataLength = useIso88591 ? comment.length : textEncoder.encode(comment).byteLength; const textDataLength = useIso88591 ? comment.length : textEncoder.encode(comment).byteLength;
const frameSize = 1 + 3 + 1 + textDataLength + 1;
const shortDescription = ''; const shortDescription = '';
const frameSize = 1 + 3 + shortDescription.length + 1 + textDataLength + 1;
this.writeAscii('COMM'); this.writeAscii('COMM');
this.writeSynchsafeU32(frameSize); this.writeSynchsafeU32(frameSize);
this.writeU16(0x0000); this.writeU16(0x0000);
this.writeU8(useIso88591 ? 0x00 : 0x03); this.writeU8(useIso88591 ? Id3V2TextEncoding.ISO_8859_1 : Id3V2TextEncoding.UTF_8);
this.writeU8(0x75); // 'u' this.writeU8(0x75); // 'u'
this.writeU8(0x6E); // 'n' this.writeU8(0x6E); // 'n'
this.writeU8(0x64); // 'd' this.writeU8(0x64); // 'd'
@@ -130,7 +152,7 @@ export class Mp3Writer {
this.writeSynchsafeU32(frameSize); this.writeSynchsafeU32(frameSize);
this.writeU16(0x0000); this.writeU16(0x0000);
this.writeU8(useIso88591 ? 0x00 : 0x03); this.writeU8(useIso88591 ? Id3V2TextEncoding.ISO_8859_1 : Id3V2TextEncoding.UTF_8);
if (useIso88591) { if (useIso88591) {
this.writeIsoString(mimeType); this.writeIsoString(mimeType);