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
+43 -43
View File
@@ -44,7 +44,7 @@ import {
Sample,
} from './isobmff-muxer';
import { parseOpusIdentificationHeader } from '../codec-data';
import { MediaMetadata, RichImageData } from '../metadata';
import { MetadataTags, RichImageData } from '../tags';
export class IsobmffBoxWriter {
private helper = new Uint8Array(8);
@@ -1274,11 +1274,11 @@ export const vtta = (notes: string) => box('vtta', [...textEncoder.encode(notes)
const udta = (muxer: IsobmffMuxer) => {
const boxes: Box[] = [];
// Depending on the format, metadata is written differently
// Depending on the format, metadata tags are written differently
if (muxer.isQuickTime) {
addQuickTimeMetadataboxes(boxes, muxer.output._metadata);
addQuickTimeMetadataTagBoxes(boxes, muxer.output._metadataTags);
} else {
const metaBox = meta(muxer.output._metadata);
const metaBox = meta(muxer.output._metadataTags);
if (metaBox) {
boxes.push(metaBox);
}
@@ -1291,46 +1291,46 @@ const udta = (muxer: IsobmffMuxer) => {
return box('udta', undefined, boxes);
};
const addQuickTimeMetadataboxes = (boxes: Box[], metadata: MediaMetadata) => {
const addQuickTimeMetadataTagBoxes = (boxes: Box[], tags: MetadataTags) => {
// https://exiftool.org/TagNames/QuickTime.html (QuickTime UserData Tags)
// For QuickTime files, metadata is dumped into the udta box
// For QuickTime files, metadata tags are dumped into the udta box
for (const { key, value } of keyValueIterator(metadata)) {
for (const { key, value } of keyValueIterator(tags)) {
switch (key) {
case 'title': {
boxes.push(metadataStringBoxShort('©nam', value));
boxes.push(metadataTagStringBoxShort('©nam', value));
}; break;
case 'description': {
boxes.push(metadataStringBoxShort('©des', value));
boxes.push(metadataTagStringBoxShort('©des', value));
}; break;
case 'artist': {
boxes.push(metadataStringBoxShort('©ART', value));
boxes.push(metadataTagStringBoxShort('©ART', value));
}; break;
case 'album': {
boxes.push(metadataStringBoxShort('©alb', value));
boxes.push(metadataTagStringBoxShort('©alb', value));
}; break;
case 'albumArtist': {
boxes.push(metadataStringBoxShort('albr', value));
boxes.push(metadataTagStringBoxShort('albr', value));
}; break;
case 'genre': {
boxes.push(metadataStringBoxShort('©gen', value));
boxes.push(metadataTagStringBoxShort('©gen', value));
}; break;
case 'date': {
boxes.push(metadataStringBoxShort('©day', value.toISOString().slice(0, 10)));
boxes.push(metadataTagStringBoxShort('©day', value.toISOString().slice(0, 10)));
}; break;
case 'comment': {
boxes.push(metadataStringBoxShort('©cmt', value));
boxes.push(metadataTagStringBoxShort('©cmt', value));
}; break;
case 'lyrics': {
boxes.push(metadataStringBoxShort('©lyr', value));
boxes.push(metadataTagStringBoxShort('©lyr', value));
}; break;
case 'raw': {
@@ -1338,9 +1338,9 @@ const addQuickTimeMetadataboxes = (boxes: Box[], metadata: MediaMetadata) => {
}; break;
case 'discNumber':
case 'discNumberMax':
case 'discsTotal':
case 'trackNumber':
case 'trackNumberMax':
case 'tracksTotal':
case 'images': {
// Not written for QuickTime (common Apple L)
}; break;
@@ -1349,15 +1349,15 @@ const addQuickTimeMetadataboxes = (boxes: Box[], metadata: MediaMetadata) => {
}
}
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 || boxes.some(x => x.type === key)) {
continue;
}
if (typeof value === 'string') {
boxes.push(metadataStringBoxShort(key, value));
boxes.push(metadataTagStringBoxShort(key, value));
} else if (value instanceof Uint8Array) {
boxes.push(box(key, Array.from(value)));
}
@@ -1365,7 +1365,7 @@ const addQuickTimeMetadataboxes = (boxes: Box[], metadata: MediaMetadata) => {
}
};
const metadataStringBoxShort = (name: string, value: string) => {
const metadataTagStringBoxShort = (name: string, value: string) => {
const encoded = textEncoder.encode(value);
return box(name, [
@@ -1382,48 +1382,48 @@ const DATA_BOX_MIME_TYPE_MAP: Record<string, number> = {
};
/** Metadata Box */
const meta = (metadata: MediaMetadata) => {
const meta = (tags: MetadataTags) => {
const boxes: Box[] = [];
// https://exiftool.org/TagNames/QuickTime.html (QuickTime ItemList Tags)
// This is the metadata format used for MP4 files
for (const { key, value } of keyValueIterator(metadata)) {
for (const { key, value } of keyValueIterator(tags)) {
switch (key) {
case 'title': {
boxes.push(metadataStringBoxLong('©nam', value));
boxes.push(metadataTagStringBoxLong('©nam', value));
}; break;
case 'description': {
boxes.push(metadataStringBoxLong('©des', value));
boxes.push(metadataTagStringBoxLong('©des', value));
}; break;
case 'artist': {
boxes.push(metadataStringBoxLong('©ART', value));
boxes.push(metadataTagStringBoxLong('©ART', value));
}; break;
case 'album': {
boxes.push(metadataStringBoxLong('©alb', value));
boxes.push(metadataTagStringBoxLong('©alb', value));
}; break;
case 'albumArtist': {
boxes.push(metadataStringBoxLong('aART', value));
boxes.push(metadataTagStringBoxLong('aART', value));
}; break;
case 'comment': {
boxes.push(metadataStringBoxLong('©cmt', value));
boxes.push(metadataTagStringBoxLong('©cmt', value));
}; break;
case 'genre': {
boxes.push(metadataStringBoxLong('©gen', value));
boxes.push(metadataTagStringBoxLong('©gen', value));
}; break;
case 'lyrics': {
boxes.push(metadataStringBoxLong('©lyr', value));
boxes.push(metadataTagStringBoxLong('©lyr', value));
}; break;
case 'date': {
boxes.push(metadataStringBoxLong('©day', value.toISOString().slice(0, 10)));
boxes.push(metadataTagStringBoxLong('©day', value.toISOString().slice(0, 10)));
}; break;
case 'images': {
@@ -1449,7 +1449,7 @@ const meta = (metadata: MediaMetadata) => {
u32(0),
u16(0), // Empty
u16(value),
u16(metadata.trackNumberMax ?? 0),
u16(tags.tracksTotal ?? 0),
u16(0), // Empty
]),
]));
@@ -1462,14 +1462,14 @@ const meta = (metadata: MediaMetadata) => {
u32(0),
u16(0), // Empty
u16(value),
u16(metadata.discNumberMax ?? 0),
u16(tags.discsTotal ?? 0),
u16(0), // Empty
]),
]));
}; break;
case 'trackNumberMax':
case 'discNumberMax':{
case 'tracksTotal':
case 'discsTotal':{
// These are included with 'trackNumber' and 'discNumber' respectively
}; break;
@@ -1481,15 +1481,15 @@ const meta = (metadata: MediaMetadata) => {
}
}
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 || boxes.some(x => x.type === key)) {
continue;
}
if (typeof value === 'string') {
boxes.push(metadataStringBoxLong(key, value));
boxes.push(metadataTagStringBoxLong(key, value));
} else if (value instanceof Uint8Array) {
boxes.push(box(key, undefined, [
box('data', [
@@ -1520,7 +1520,7 @@ const meta = (metadata: MediaMetadata) => {
]);
};
const metadataStringBoxLong = (name: string, value: string) => {
const metadataTagStringBoxLong = (name: string, value: string) => {
return box(name, undefined, [
box('data', [
u32(1), // Type indicator (UTF-8)
+37 -37
View File
@@ -86,7 +86,7 @@ import {
readU8,
readAscii,
} from '../reader';
import { MediaMetadata, RichImageData } from '../metadata';
import { MetadataTags, RichImageData } from '../tags';
// https://exiftool.org/TagNames/QuickTime.html
const UDTA_STRING_KEYS = new Set([
@@ -249,7 +249,7 @@ export class IsobmffDemuxer extends Demuxer {
movieTimescale = -1;
movieDurationInTimescale = -1;
isQuickTime = false;
metadata: MediaMetadata = {};
metadataTags: MetadataTags = {};
currentMetadataKeys: Map<number, string> | null = null;
isFragmented = false;
@@ -288,9 +288,9 @@ export class IsobmffDemuxer extends Demuxer {
});
}
async getMetadata() {
async getMetadataTags() {
await this.readMetadata();
return this.metadata;
return this.metadataTags;
}
readMetadata() {
@@ -2057,12 +2057,12 @@ export class IsobmffDemuxer extends Demuxer {
for (const { boxInfo, slice } of iterator) {
if (boxInfo.name !== 'meta' && !this.currentTrack) {
const startPos = slice.filePos;
this.metadata.raw ??= {};
this.metadataTags.raw ??= {};
if (UDTA_STRING_KEYS.has(boxInfo.name)) {
this.metadata.raw[boxInfo.name] ??= readMetadataStringShort(slice);
this.metadataTags.raw[boxInfo.name] ??= readMetadataStringShort(slice);
} else {
this.metadata.raw[boxInfo.name] ??= readBytes(slice, boxInfo.contentSize);
this.metadataTags.raw[boxInfo.name] ??= readBytes(slice, boxInfo.contentSize);
}
slice.filePos = startPos;
@@ -2079,37 +2079,37 @@ export class IsobmffDemuxer extends Demuxer {
if (this.currentTrack) {
this.currentTrack.name = textDecoder.decode(readBytes(slice, boxInfo.contentSize));
} else {
this.metadata.title ??= readMetadataStringShort(slice);
this.metadataTags.title ??= readMetadataStringShort(slice);
}
}; break;
case '©des': {
if (!this.currentTrack) {
this.metadata.description ??= readMetadataStringShort(slice);
this.metadataTags.description ??= readMetadataStringShort(slice);
}
}; break;
case '©ART': {
if (!this.currentTrack) {
this.metadata.artist ??= readMetadataStringShort(slice);
this.metadataTags.artist ??= readMetadataStringShort(slice);
}
}; break;
case '©alb': {
if (!this.currentTrack) {
this.metadata.album ??= readMetadataStringShort(slice);
this.metadataTags.album ??= readMetadataStringShort(slice);
}
}; break;
case 'albr': {
if (!this.currentTrack) {
this.metadata.albumArtist ??= readMetadataStringShort(slice);
this.metadataTags.albumArtist ??= readMetadataStringShort(slice);
}
}; break;
case '©gen': {
if (!this.currentTrack) {
this.metadata.genre ??= readMetadataStringShort(slice);
this.metadataTags.genre ??= readMetadataStringShort(slice);
}
}; break;
@@ -2117,20 +2117,20 @@ export class IsobmffDemuxer extends Demuxer {
if (!this.currentTrack) {
const date = new Date(readMetadataStringShort(slice));
if (!Number.isNaN(date.getTime())) {
this.metadata.date ??= date;
this.metadataTags.date ??= date;
}
}
}; break;
case '©cmt': {
if (!this.currentTrack) {
this.metadata.comment ??= readMetadataStringShort(slice);
this.metadataTags.comment ??= readMetadataStringShort(slice);
}
}; break;
case '©lyr': {
if (!this.currentTrack) {
this.metadata.lyrics ??= readMetadataStringShort(slice);
this.metadataTags.lyrics ??= readMetadataStringShort(slice);
}
}; break;
}
@@ -2199,8 +2199,8 @@ export class IsobmffDemuxer extends Demuxer {
const data = readDataBox(slice);
this.metadata.raw ??= {};
this.metadata.raw[metadataKey] ??= data;
this.metadataTags.raw ??= {};
this.metadataTags.raw[metadataKey] ??= data;
switch (metadataKey) {
case '©nam':
@@ -2208,7 +2208,7 @@ export class IsobmffDemuxer extends Demuxer {
case 'com.apple.quicktime.title':
case 'title': {
if (typeof data === 'string') {
this.metadata.title ??= data;
this.metadataTags.title ??= data;
}
}; break;
@@ -2218,7 +2218,7 @@ export class IsobmffDemuxer extends Demuxer {
case 'com.apple.quicktime.description':
case 'description': {
if (typeof data === 'string') {
this.metadata.description ??= data;
this.metadataTags.description ??= data;
}
}; break;
@@ -2226,7 +2226,7 @@ export class IsobmffDemuxer extends Demuxer {
case 'com.apple.quicktime.artist':
case 'artist': {
if (typeof data === 'string') {
this.metadata.artist ??= data;
this.metadataTags.artist ??= data;
}
}; break;
@@ -2235,14 +2235,14 @@ export class IsobmffDemuxer extends Demuxer {
case 'com.apple.quicktime.album':
case 'album': {
if (typeof data === 'string') {
this.metadata.album ??= data;
this.metadataTags.album ??= data;
}
}; break;
case 'aART':
case 'album_artist': {
if (typeof data === 'string') {
this.metadata.albumArtist ??= data;
this.metadataTags.albumArtist ??= data;
}
}; break;
@@ -2250,7 +2250,7 @@ export class IsobmffDemuxer extends Demuxer {
case 'com.apple.quicktime.comment':
case 'comment': {
if (typeof data === 'string') {
this.metadata.comment ??= data;
this.metadataTags.comment ??= data;
}
}; break;
@@ -2259,14 +2259,14 @@ export class IsobmffDemuxer extends Demuxer {
case 'com.apple.quicktime.genre':
case 'genre': {
if (typeof data === 'string') {
this.metadata.genre ??= data;
this.metadataTags.genre ??= data;
}
}; break;
case '©lyr':
case 'lyrics': {
if (typeof data === 'string') {
this.metadata.lyrics ??= data;
this.metadataTags.lyrics ??= data;
}
}; break;
@@ -2277,7 +2277,7 @@ export class IsobmffDemuxer extends Demuxer {
if (typeof data === 'string') {
const date = new Date(data);
if (!Number.isNaN(date.getTime())) {
this.metadata.date ??= date;
this.metadataTags.date ??= date;
}
}
}; break;
@@ -2285,15 +2285,15 @@ export class IsobmffDemuxer extends Demuxer {
case 'covr':
case 'com.apple.quicktime.artwork': {
if (data instanceof RichImageData) {
this.metadata.images ??= [];
this.metadata.images.push({
this.metadataTags.images ??= [];
this.metadataTags.images.push({
data: data.data,
kind: 'coverFront',
mimeType: data.mimeType,
});
} else if (data instanceof Uint8Array) {
this.metadata.images ??= [];
this.metadata.images.push({
this.metadataTags.images ??= [];
this.metadataTags.images.push({
data,
kind: 'coverFront',
mimeType: 'image/*',
@@ -2306,13 +2306,13 @@ export class IsobmffDemuxer extends Demuxer {
const view = toDataView(data);
const trackNumber = view.getUint16(2, false);
const trackNumberMax = view.getUint16(4, false);
const tracksTotal = view.getUint16(4, false);
if (trackNumber > 0) {
this.metadata.trackNumber ??= trackNumber;
this.metadataTags.trackNumber ??= trackNumber;
}
if (trackNumberMax > 0) {
this.metadata.trackNumberMax ??= trackNumberMax;
if (tracksTotal > 0) {
this.metadataTags.tracksTotal ??= tracksTotal;
}
}
}; break;
@@ -2326,10 +2326,10 @@ export class IsobmffDemuxer extends Demuxer {
const discNumberMax = view.getUint16(4, false);
if (discNumber > 0) {
this.metadata.discNumber ??= discNumber;
this.metadataTags.discNumber ??= discNumber;
}
if (discNumberMax > 0) {
this.metadata.discNumberMax ??= discNumberMax;
this.metadataTags.discsTotal ??= discNumberMax;
}
}
}; break;
+1 -1
View File
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { RichImageData } from '../metadata';
import { RichImageData } from '../tags';
import { textDecoder } from '../misc';
import { FileSlice, readAscii, readBytes, readI32Be, readU16Be, readU32Be, readU64Be, readU8 } from '../reader';