This commit is contained in:
Vanilagy
2025-09-05 18:59:13 +02:00
parent dd7804e5ab
commit b58476b1b0
19 changed files with 393 additions and 395 deletions
+9 -9
View File
@@ -10,7 +10,7 @@ import { AudioCodec } from '../codec';
import { Demuxer } from '../demuxer';
import { Input } from '../input';
import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
import { MediaMetadata } from '../metadata';
import { MetadataTags } from '../tags';
import { PacketRetrievalOptions } from '../media-sink';
import { assert, AsyncMutex, binarySearchExact, binarySearchLessOrEqual, UNDETERMINED_LANGUAGE } from '../misc';
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
@@ -38,7 +38,7 @@ export class Mp3Demuxer extends Demuxer {
metadataPromise: Promise<void> | null = null;
firstFrameHeader: FrameHeader | null = null;
loadedSamples: Sample[] = []; // All samples from the start of the file to lastLoadedPos
metadata: MediaMetadata | null = null;
metadataTags: MetadataTags | null = null;
tracks: InputAudioTrack[] = [];
@@ -148,17 +148,17 @@ export class Mp3Demuxer extends Demuxer {
return track.computeDuration();
}
async getMetadata() {
async getMetadataTags() {
const release = await this.readingMutex.acquire();
try {
await this.readMetadata();
if (this.metadata) {
return this.metadata;
if (this.metadataTags) {
return this.metadataTags;
}
this.metadata = {};
this.metadataTags = {};
let currentPos = 0;
let id3V2HeaderFound = false;
@@ -178,7 +178,7 @@ export class Mp3Demuxer extends Demuxer {
if (contentSlice instanceof Promise) contentSlice = await contentSlice;
if (!contentSlice) break;
parseId3V2Tag(contentSlice, id3V2Header, this.metadata);
parseId3V2Tag(contentSlice, id3V2Header, this.metadataTags);
currentPos = headerSlice.filePos + id3V2Header.size;
}
@@ -191,11 +191,11 @@ export class Mp3Demuxer extends Demuxer {
const tag = readAscii(slice, 3);
if (tag === 'TAG') {
parseId3V1Tag(slice, this.metadata);
parseId3V1Tag(slice, this.metadataTags);
}
}
return this.metadata;
return this.metadataTags;
} finally {
release();
}
+14 -14
View File
@@ -7,7 +7,7 @@
*/
import { assert, assertNever, keyValueIterator, textEncoder, toDataView } from '../misc';
import { mediaMetadataIsEmpty, MediaMetadata } from '../metadata';
import { metadataTagsAreEmpty, MetadataTags } from '../tags';
import { Muxer } from '../muxer';
import { Output, OutputAudioTrack } from '../output';
import { Mp3OutputFormat } from '../output-format';
@@ -35,8 +35,8 @@ export class Mp3Muxer extends Muxer {
}
async start() {
if (!mediaMetadataIsEmpty(this.output._metadata)) {
this.writeId3v2Tag(this.output._metadata);
if (!metadataTagsAreEmpty(this.output._metadataTags)) {
this.writeId3v2Tag(this.output._metadataTags);
}
}
@@ -122,7 +122,7 @@ export class Mp3Muxer extends Muxer {
throw new Error('MP3 does not support subtitles.');
}
writeId3v2Tag(metadata: MediaMetadata) {
writeId3v2Tag(tags: MetadataTags) {
this.mp3Writer.writeAscii('ID3');
this.mp3Writer.writeU8(0x04); // Version 2.4
this.mp3Writer.writeU8(0x00); // Revision 0
@@ -132,7 +132,7 @@ export class Mp3Muxer extends Muxer {
const startPos = this.writer.getPos();
const writtenTags = new Set<string>();
for (const { key, value } of keyValueIterator(metadata)) {
for (const { key, value } of keyValueIterator(tags)) {
switch (key) {
case 'title': {
this.mp3Writer.writeId3V2TextFrame('TIT2', value);
@@ -160,8 +160,8 @@ export class Mp3Muxer extends Muxer {
}; break;
case 'trackNumber': {
const string = metadata.trackNumberMax !== undefined
? `${value}/${metadata.trackNumberMax}`
const string = tags.tracksTotal !== undefined
? `${value}/${tags.tracksTotal}`
: value.toString();
this.mp3Writer.writeId3V2TextFrame('TRCK', string);
@@ -169,8 +169,8 @@ export class Mp3Muxer extends Muxer {
}; break;
case 'discNumber': {
const string = metadata.discNumberMax !== undefined
? `${value}/${metadata.discNumberMax}`
const string = tags.discsTotal !== undefined
? `${value}/${tags.discsTotal}`
: value.toString();
this.mp3Writer.writeId3V2TextFrame('TPOS', string);
@@ -206,8 +206,8 @@ export class Mp3Muxer extends Muxer {
}
}; break;
case 'trackNumberMax':
case 'discNumberMax': {
case 'tracksTotal':
case 'discsTotal': {
// Handled with trackNumber and discNumber respectively
}; break;
@@ -219,9 +219,9 @@ export class Mp3Muxer extends Muxer {
}
}
if (metadata.raw) {
for (const key in metadata.raw) {
const value = metadata.raw[key];
if (tags.raw) {
for (const key in tags.raw) {
const value = tags.raw[key];
if (value == null || key.length !== 4 || writtenTags.has(key)) {
continue;
}
+33 -33
View File
@@ -7,7 +7,7 @@
*/
import { decodeSynchsafe, FRAME_HEADER_SIZE, FrameHeader, readFrameHeader } from '../../shared/mp3-misc';
import { MediaMetadata } from '../metadata';
import { MetadataTags } from '../tags';
import { coalesceIndex, textDecoder } from '../misc';
import { FileSlice, readAscii, readBytes, Reader, readU32Be, readU8 } from '../reader';
@@ -89,25 +89,25 @@ export const readNextFrameHeader = async (reader: Reader, startPos: number, unti
return null;
};
export const parseId3V1Tag = (slice: FileSlice, metadata: MediaMetadata) => {
export const parseId3V1Tag = (slice: FileSlice, tags: MetadataTags) => {
const startPos = slice.filePos;
metadata.raw ??= {};
metadata.raw['TAG'] ??= readBytes(slice, ID3_V1_TAG_SIZE - 3); // Dump the whole tag into the raw metadata
tags.raw ??= {};
tags.raw['TAG'] ??= readBytes(slice, ID3_V1_TAG_SIZE - 3); // Dump the whole tag into the raw metadata
slice.filePos = startPos;
const title = readId3V1String(slice, 30);
if (title) metadata.title ??= title;
if (title) tags.title ??= title;
const artist = readId3V1String(slice, 30);
if (artist) metadata.artist ??= artist;
if (artist) tags.artist ??= artist;
const album = readId3V1String(slice, 30);
if (album) metadata.album ??= album;
if (album) tags.album ??= album;
const yearText = readId3V1String(slice, 4);
const year = Number.parseInt(yearText, 10);
if (Number.isInteger(year) && year > 0) {
metadata.date ??= new Date(year, 0, 1);
tags.date ??= new Date(year, 0, 1);
}
const commentBytes = readBytes(slice, 30);
@@ -118,7 +118,7 @@ export const parseId3V1Tag = (slice: FileSlice, metadata: MediaMetadata) => {
if (commentBytes[28] === 0 && commentBytes[29] !== 0) {
const trackNum = commentBytes[29]!;
if (trackNum > 0) {
metadata.trackNumber ??= trackNum;
tags.trackNumber ??= trackNum;
}
slice.skip(-30);
@@ -129,11 +129,11 @@ export const parseId3V1Tag = (slice: FileSlice, metadata: MediaMetadata) => {
comment = readId3V1String(slice, 30);
}
if (comment) metadata.comment ??= comment;
if (comment) tags.comment ??= comment;
const genreIndex = readU8(slice);
if (genreIndex < ID3_V1_GENRES.length) {
metadata.genre ??= ID3_V1_GENRES[genreIndex];
tags.genre ??= ID3_V1_GENRES[genreIndex];
}
};
@@ -171,7 +171,7 @@ export const readId3V2Header = (slice: FileSlice): Id3V2Header | null => {
return { majorVersion, revision, flags, size };
};
export const parseId3V2Tag = (slice: FileSlice, header: Id3V2Header, metadata: MediaMetadata) => {
export const parseId3V2Tag = (slice: FileSlice, header: Id3V2Header, tags: MetadataTags) => {
// https://id3.org/id3v2.3.0
if (![2, 3, 4].includes(header.majorVersion)) {
@@ -239,13 +239,13 @@ export const parseId3V2Tag = (slice: FileSlice, header: Id3V2Header, metadata: M
reader.ununsynchronizeRegion(reader.pos, frameEndPos);
}
metadata.raw ??= {};
tags.raw ??= {};
if (frame.id[0] === 'T') {
// It's a text frame, let's decode as text
metadata.raw[frame.id] ??= reader.readId3V2EncodingAndText(frameEndPos);
tags.raw[frame.id] ??= reader.readId3V2EncodingAndText(frameEndPos);
} else {
// For the others, let's just get the bytes
metadata.raw[frame.id] ??= reader.readBytes(frame.size);
tags.raw[frame.id] ??= reader.readBytes(frame.size);
}
reader.pos = frameStartPos;
@@ -253,27 +253,27 @@ export const parseId3V2Tag = (slice: FileSlice, header: Id3V2Header, metadata: M
switch (frame.id) {
case 'TIT2':
case 'TT2': {
metadata.title ??= reader.readId3V2EncodingAndText(frameEndPos);
tags.title ??= reader.readId3V2EncodingAndText(frameEndPos);
}; break;
case 'TIT3':
case 'TT3': {
metadata.description ??= reader.readId3V2EncodingAndText(frameEndPos);
tags.description ??= reader.readId3V2EncodingAndText(frameEndPos);
}; break;
case 'TPE1':
case 'TP1': {
metadata.artist ??= reader.readId3V2EncodingAndText(frameEndPos);
tags.artist ??= reader.readId3V2EncodingAndText(frameEndPos);
}; break;
case 'TALB':
case 'TAL': {
metadata.album ??= reader.readId3V2EncodingAndText(frameEndPos);
tags.album ??= reader.readId3V2EncodingAndText(frameEndPos);
}; break;
case 'TPE2':
case 'TP2': {
metadata.albumArtist ??= reader.readId3V2EncodingAndText(frameEndPos);
tags.albumArtist ??= reader.readId3V2EncodingAndText(frameEndPos);
}; break;
case 'TRCK':
@@ -284,10 +284,10 @@ export const parseId3V2Tag = (slice: FileSlice, header: Id3V2Header, metadata: M
const trackNumMax = parts[1] && Number.parseInt(parts[1], 10);
if (Number.isInteger(trackNum) && trackNum > 0) {
metadata.trackNumber ??= trackNum;
tags.trackNumber ??= trackNum;
}
if (trackNumMax && Number.isInteger(trackNumMax) && trackNumMax > 0) {
metadata.trackNumberMax ??= trackNumMax;
tags.tracksTotal ??= trackNumMax;
}
}; break;
@@ -299,10 +299,10 @@ export const parseId3V2Tag = (slice: FileSlice, header: Id3V2Header, metadata: M
const discNumMax = parts[1] && Number.parseInt(parts[1], 10);
if (Number.isInteger(discNum) && discNum > 0) {
metadata.discNumber ??= discNum;
tags.discNumber ??= discNum;
}
if (discNumMax && Number.isInteger(discNumMax) && discNumMax > 0) {
metadata.discNumberMax ??= discNumMax;
tags.discsTotal ??= discNumMax;
}
}; break;
@@ -313,7 +313,7 @@ export const parseId3V2Tag = (slice: FileSlice, header: Id3V2Header, metadata: M
if (match) {
const genreNumber = Number.parseInt(match[1]!);
if (ID3_V1_GENRES[genreNumber] !== undefined) {
metadata.genre ??= ID3_V1_GENRES[genreNumber];
tags.genre ??= ID3_V1_GENRES[genreNumber];
break;
}
}
@@ -322,12 +322,12 @@ export const parseId3V2Tag = (slice: FileSlice, header: Id3V2Header, metadata: M
if (match) {
const genreNumber = Number.parseInt(match[0]);
if (ID3_V1_GENRES[genreNumber] !== undefined) {
metadata.genre ??= ID3_V1_GENRES[genreNumber];
tags.genre ??= ID3_V1_GENRES[genreNumber];
break;
}
}
metadata.genre ??= genreText;
tags.genre ??= genreText;
}; break;
case 'TDRC':
@@ -336,7 +336,7 @@ export const parseId3V2Tag = (slice: FileSlice, header: Id3V2Header, metadata: M
const date = new Date(dateText);
if (!Number.isNaN(date.getTime())) {
metadata.date ??= date;
tags.date ??= date;
}
}; break;
@@ -346,7 +346,7 @@ export const parseId3V2Tag = (slice: FileSlice, header: Id3V2Header, metadata: M
const year = Number.parseInt(yearText, 10);
if (Number.isInteger(year)) {
metadata.date ??= new Date(year, 0, 1);
tags.date ??= new Date(year, 0, 1);
}
}; break;
@@ -355,7 +355,7 @@ export const parseId3V2Tag = (slice: FileSlice, header: Id3V2Header, metadata: M
const encoding = reader.readU8();
reader.pos += 3; // Skip language
reader.readId3V2Text(encoding, frameEndPos); // Short content description
metadata.lyrics ??= reader.readId3V2Text(encoding, frameEndPos);
tags.lyrics ??= reader.readId3V2Text(encoding, frameEndPos);
}; break;
case 'COMM':
@@ -363,7 +363,7 @@ export const parseId3V2Tag = (slice: FileSlice, header: Id3V2Header, metadata: M
const encoding = reader.readU8();
reader.pos += 3; // Skip language
reader.readId3V2Text(encoding, frameEndPos); // Short content description
metadata.comment ??= reader.readId3V2Text(encoding, frameEndPos);
tags.comment ??= reader.readId3V2Text(encoding, frameEndPos);
}; break;
case 'APIC':
@@ -389,8 +389,8 @@ export const parseId3V2Tag = (slice: FileSlice, header: Id3V2Header, metadata: M
if (imageDataSize >= 0) {
const imageData = reader.readBytes(imageDataSize);
if (!metadata.images) metadata.images = [];
metadata.images.push({
if (!tags.images) tags.images = [];
tags.images.push({
data: imageData,
mimeType,
kind: pictureType === 3