diff --git a/dev/convert.html b/dev/convert.html
index d093a9d..7ab5593 100644
--- a/dev/convert.html
+++ b/dev/convert.html
@@ -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({
diff --git a/src/metadata.ts b/src/metadata.ts
index 8aecafb..1737491 100644
--- a/src/metadata.ts
+++ b/src/metadata.ts
@@ -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);
};
diff --git a/src/mp3/mp3-muxer.ts b/src/mp3/mp3-muxer.ts
index 8c19404..ea9ce89 100644
--- a/src/mp3/mp3-muxer.ts
+++ b/src/mp3/mp3-muxer.ts
@@ -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);
}
}
diff --git a/src/mp3/mp3-reader.ts b/src/mp3/mp3-reader.ts
index 3c0cd64..105877e 100644
--- a/src/mp3/mp3-reader.ts
+++ b/src/mp3/mp3-reader.ts
@@ -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();
diff --git a/src/mp3/mp3-writer.ts b/src/mp3/mp3-writer.ts
index 6c63b9d..4bd726f 100644
--- a/src/mp3/mp3-writer.ts
+++ b/src/mp3/mp3-writer.ts
@@ -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);