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,
kind: 'coverFront',
mimeType: 'image/jpeg'
}]
}],
lyrics: "There's no way\nThat it's not going there"
});
const conversion = await Mediabunny.Conversion.init({
+14 -9
View File
@@ -9,6 +9,7 @@ export type MediaMetadata = {
discNumber?: number;
genre?: string;
releasedAt?: Date;
lyrics?: string;
images?: {
data: Uint8Array;
mimeType: string;
@@ -47,6 +48,9 @@ export const validateMediaMetadata = (metadata: MediaMetadata) => {
if (metadata.releasedAt !== undefined && !(metadata.releasedAt instanceof 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') {
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) => {
return !metadata.title
&& !metadata.artist
&& !metadata.album
&& !metadata.albumArtist
&& !metadata.trackNumber
&& !metadata.discNumber
&& !metadata.genre
&& !metadata.releasedAt
&& !metadata.comment
return metadata.title === undefined
&& metadata.artist === undefined
&& metadata.album === undefined
&& metadata.albumArtist === undefined
&& metadata.trackNumber === undefined
&& metadata.discNumber === undefined
&& metadata.genre === undefined
&& metadata.releasedAt === undefined
&& metadata.lyrics === undefined
&& metadata.comment === undefined
&& (!metadata.images || metadata.images.length === 0);
};
+13 -10
View File
@@ -130,38 +130,41 @@ export class Mp3Muxer extends Muxer {
const startPos = this.writer.getPos();
if (metadata.title) {
if (metadata.title !== undefined) {
this.mp3Writer.writeId3V2TextFrame('TIT2', metadata.title);
}
if (metadata.artist) {
if (metadata.artist !== undefined) {
this.mp3Writer.writeId3V2TextFrame('TPE1', metadata.artist);
}
if (metadata.album) {
if (metadata.album !== undefined) {
this.mp3Writer.writeId3V2TextFrame('TALB', metadata.album);
}
if (metadata.albumArtist) {
if (metadata.albumArtist !== undefined) {
this.mp3Writer.writeId3V2TextFrame('TPE2', metadata.albumArtist);
}
if (metadata.trackNumber) {
if (metadata.trackNumber !== undefined) {
this.mp3Writer.writeId3V2TextFrame('TRCK', metadata.trackNumber.toString());
}
if (metadata.discNumber) {
if (metadata.discNumber !== undefined) {
this.mp3Writer.writeId3V2TextFrame('TPOS', metadata.discNumber.toString());
}
if (metadata.genre) {
if (metadata.genre !== undefined) {
this.mp3Writer.writeId3V2TextFrame('TCON', metadata.genre);
}
if (metadata.releasedAt) {
if (metadata.releasedAt !== undefined) {
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);
}
if (metadata.images) {
const pictureTypeMap = { coverFront: 0x03, coverBack: 0x04, unknown: 0x00 };
for (const image of metadata.images) {
const pictureType = pictureTypeMap[image.kind];
const description = image.description || '';
const description = image.description ?? '';
this.mp3Writer.writeId3V2ApicFrame(image.mimeType, pictureType, description, image.data);
}
}
+8
View File
@@ -350,6 +350,14 @@ export class Mp3Reader {
}
}; 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 'COM': {
const encoding = reader.readU8();
+26 -4
View File
@@ -17,6 +17,7 @@ import {
SAMPLING_RATES,
XING,
} from '../../shared/mp3-misc';
import { Id3V2TextEncoding } from './mp3-reader';
export type XingFrameData = {
mpegVersionId: number;
@@ -89,7 +90,7 @@ export class Mp3Writer {
this.writeSynchsafeU32(frameSize);
this.writeU16(0x0000);
this.writeU8(useIso88591 ? 0x00 : 0x03);
this.writeU8(useIso88591 ? Id3V2TextEncoding.ISO_8859_1 : Id3V2TextEncoding.UTF_8);
if (useIso88591) {
this.writeIsoString(text);
} 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) {
const useIso88591 = isIso88591Compatible(comment);
const textDataLength = useIso88591 ? comment.length : textEncoder.encode(comment).byteLength;
const frameSize = 1 + 3 + 1 + textDataLength + 1;
const shortDescription = '';
const frameSize = 1 + 3 + shortDescription.length + 1 + textDataLength + 1;
this.writeAscii('COMM');
this.writeSynchsafeU32(frameSize);
this.writeU16(0x0000);
this.writeU8(useIso88591 ? 0x00 : 0x03);
this.writeU8(useIso88591 ? Id3V2TextEncoding.ISO_8859_1 : Id3V2TextEncoding.UTF_8);
this.writeU8(0x75); // 'u'
this.writeU8(0x6E); // 'n'
this.writeU8(0x64); // 'd'
@@ -130,7 +152,7 @@ export class Mp3Writer {
this.writeSynchsafeU32(frameSize);
this.writeU16(0x0000);
this.writeU8(useIso88591 ? 0x00 : 0x03);
this.writeU8(useIso88591 ? Id3V2TextEncoding.ISO_8859_1 : Id3V2TextEncoding.UTF_8);
if (useIso88591) {
this.writeIsoString(mimeType);