taglib-wasm
Version:
TagLib-Wasm is the universal tagging library for TypeScript/JavaScript platforms: Browsers, Node.js, Deno, Bun, Cloudflare Workers, and Electron apps
1,675 lines (1,644 loc) • 100 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
// src/types/audio-formats.ts
function isNamedAudioInput(input) {
return typeof input === "object" && input !== null && "name" in input && "data" in input && !(input instanceof File) && !(input instanceof Uint8Array) && !(input instanceof ArrayBuffer);
}
var init_audio_formats = __esm({
"src/types/audio-formats.ts"() {
"use strict";
}
});
// src/types/tags.ts
var init_tags = __esm({
"src/types/tags.ts"() {
"use strict";
}
});
// src/types/metadata-mappings.ts
var init_metadata_mappings = __esm({
"src/types/metadata-mappings.ts"() {
"use strict";
}
});
// src/types/pictures.ts
var PICTURE_TYPE_VALUES, PICTURE_TYPE_NAMES;
var init_pictures = __esm({
"src/types/pictures.ts"() {
"use strict";
PICTURE_TYPE_VALUES = {
Other: 0,
FileIcon: 1,
OtherFileIcon: 2,
FrontCover: 3,
BackCover: 4,
LeafletPage: 5,
Media: 6,
LeadArtist: 7,
Artist: 8,
Conductor: 9,
Band: 10,
Composer: 11,
Lyricist: 12,
RecordingLocation: 13,
DuringRecording: 14,
DuringPerformance: 15,
MovieScreenCapture: 16,
ColouredFish: 17,
Illustration: 18,
BandLogo: 19,
PublisherLogo: 20
};
PICTURE_TYPE_NAMES = {
0: "Other",
1: "FileIcon",
2: "OtherFileIcon",
3: "FrontCover",
4: "BackCover",
5: "LeafletPage",
6: "Media",
7: "LeadArtist",
8: "Artist",
9: "Conductor",
10: "Band",
11: "Composer",
12: "Lyricist",
13: "RecordingLocation",
14: "DuringRecording",
15: "DuringPerformance",
16: "MovieScreenCapture",
17: "ColouredFish",
18: "Illustration",
19: "BandLogo",
20: "PublisherLogo"
};
}
});
// src/types/chapters.ts
var init_chapters = __esm({
"src/types/chapters.ts"() {
"use strict";
}
});
// src/types/bwf.ts
var init_bwf = __esm({
"src/types/bwf.ts"() {
"use strict";
}
});
// src/types/config.ts
var init_config = __esm({
"src/types/config.ts"() {
"use strict";
}
});
// src/types/format-property-keys.ts
var init_format_property_keys = __esm({
"src/types/format-property-keys.ts"() {
"use strict";
}
});
// src/types/index.ts
var init_types = __esm({
"src/types/index.ts"() {
"use strict";
init_audio_formats();
init_tags();
init_metadata_mappings();
init_pictures();
init_chapters();
init_bwf();
init_config();
init_format_property_keys();
}
});
// src/types.ts
var init_types2 = __esm({
"src/types.ts"() {
"use strict";
init_types();
}
});
// src/bwf/bext.ts
function readFixedString(bytes, offset, len) {
let end = offset;
const max = Math.min(offset + len, bytes.length);
while (end < max && bytes[end] !== 0) end++;
let s = "";
for (let i = offset; i < end; i++) s += String.fromCharCode(bytes[i]);
return s;
}
function writeFixedString(bytes, offset, len, value) {
for (let i = 0; i < len; i++) {
bytes[offset + i] = i < value.length ? value.charCodeAt(i) & 255 : 0;
}
}
function clampInt16(n) {
return Math.max(-32768, Math.min(32767, Math.round(n)));
}
function loudnessRaw(v) {
return v === void 0 ? LOUDNESS_UNSET : clampInt16(v * 100);
}
function loudnessFromRaw(raw) {
return raw === LOUDNESS_UNSET ? void 0 : raw / 100;
}
function bytesToHex(bytes) {
let s = "";
for (const b of bytes) s += b.toString(16).padStart(2, "0");
return s;
}
function hexToBytes(hex, outLen) {
const clean = hex.replace(/[^0-9a-fA-F]/g, "");
const out = new Uint8Array(outLen);
for (let i = 0; i + 1 < clean.length + 1 && i / 2 < outLen; i += 2) {
const pair = clean.slice(i, i + 2);
if (pair.length === 2) out[i / 2] = parseInt(pair, 16);
}
return out;
}
function decodeBext(bytes) {
if (bytes.length < VERSION_OFFSET + 2) return void 0;
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
const timeRefLow = dv.getUint32(338, true);
const timeRefHigh = dv.getUint32(342, true);
const version = dv.getUint16(VERSION_OFFSET, true);
const result = {
description: readFixedString(bytes, 0, 256),
originator: readFixedString(bytes, 256, 32),
originatorReference: readFixedString(bytes, 288, 32),
originationDate: readFixedString(bytes, 320, 10),
originationTime: readFixedString(bytes, 330, 8),
timeReferenceSamples: BigInt(timeRefHigh) << 32n | BigInt(timeRefLow),
version,
codingHistory: bytes.length > FIXED_PREFIX_LEN ? readFixedString(
bytes,
FIXED_PREFIX_LEN,
bytes.length - FIXED_PREFIX_LEN
) : ""
};
if (version >= 1 && bytes.length >= UMID_OFFSET + UMID_LEN) {
result.umid = bytesToHex(
bytes.subarray(UMID_OFFSET, UMID_OFFSET + UMID_LEN)
);
}
if (version >= 2 && bytes.length >= LOUDNESS_OFFSET + 10) {
const lv = loudnessFromRaw(dv.getInt16(LOUDNESS_OFFSET, true));
const lr = loudnessFromRaw(dv.getInt16(LOUDNESS_OFFSET + 2, true));
const mtp = loudnessFromRaw(dv.getInt16(LOUDNESS_OFFSET + 4, true));
const mml = loudnessFromRaw(dv.getInt16(LOUDNESS_OFFSET + 6, true));
const msl = loudnessFromRaw(dv.getInt16(LOUDNESS_OFFSET + 8, true));
if (lv !== void 0) result.loudnessValueDb = lv;
if (lr !== void 0) result.loudnessRangeDb = lr;
if (mtp !== void 0) result.maxTruePeakLevelDbtp = mtp;
if (mml !== void 0) result.maxMomentaryLoudnessDb = mml;
if (msl !== void 0) result.maxShortTermLoudnessDb = msl;
}
return result;
}
function encodeBext(b) {
const codingHistory = b.codingHistory ?? "";
const bytes = new Uint8Array(FIXED_PREFIX_LEN + codingHistory.length);
const dv = new DataView(bytes.buffer);
const hasLoudness = b.loudnessValueDb !== void 0 || b.loudnessRangeDb !== void 0 || b.maxTruePeakLevelDbtp !== void 0 || b.maxMomentaryLoudnessDb !== void 0 || b.maxShortTermLoudnessDb !== void 0;
const version = Number.isInteger(b.version) ? b.version : hasLoudness ? 2 : b.umid ? 1 : 0;
writeFixedString(bytes, 0, 256, b.description ?? "");
writeFixedString(bytes, 256, 32, b.originator ?? "");
writeFixedString(bytes, 288, 32, b.originatorReference ?? "");
writeFixedString(bytes, 320, 10, b.originationDate ?? "");
writeFixedString(bytes, 330, 8, b.originationTime ?? "");
const tr = b.timeReferenceSamples ?? 0n;
dv.setUint32(338, Number(tr & 0xffffffffn), true);
dv.setUint32(342, Number(tr >> 32n & 0xffffffffn), true);
dv.setUint16(VERSION_OFFSET, version, true);
if (version >= 1 && b.umid) {
bytes.set(hexToBytes(b.umid, UMID_LEN), UMID_OFFSET);
}
if (version >= 2) {
dv.setInt16(LOUDNESS_OFFSET, loudnessRaw(b.loudnessValueDb), true);
dv.setInt16(LOUDNESS_OFFSET + 2, loudnessRaw(b.loudnessRangeDb), true);
dv.setInt16(LOUDNESS_OFFSET + 4, loudnessRaw(b.maxTruePeakLevelDbtp), true);
dv.setInt16(
LOUDNESS_OFFSET + 6,
loudnessRaw(b.maxMomentaryLoudnessDb),
true
);
dv.setInt16(
LOUDNESS_OFFSET + 8,
loudnessRaw(b.maxShortTermLoudnessDb),
true
);
}
writeFixedString(
bytes,
FIXED_PREFIX_LEN,
codingHistory.length,
codingHistory
);
return bytes;
}
var FIXED_PREFIX_LEN, VERSION_OFFSET, UMID_OFFSET, UMID_LEN, LOUDNESS_OFFSET, LOUDNESS_UNSET;
var init_bext = __esm({
"src/bwf/bext.ts"() {
"use strict";
FIXED_PREFIX_LEN = 602;
VERSION_OFFSET = 346;
UMID_OFFSET = 348;
UMID_LEN = 64;
LOUDNESS_OFFSET = 412;
LOUDNESS_UNSET = 32767;
}
});
// src/errors/base.ts
var SUPPORTED_FORMATS, TagLibError;
var init_base = __esm({
"src/errors/base.ts"() {
"use strict";
SUPPORTED_FORMATS = [
"MP3",
"MP4",
"M4A",
"FLAC",
"OGG",
"WAV",
"MKA"
];
TagLibError = class _TagLibError extends Error {
/**
* Creates a new TagLibError
* @param code - Error code for programmatic handling
* @param message - Human-readable error message
* @param details - Additional context about the error
*/
constructor(code, message, details) {
super(message);
__publicField(this, "code", code);
__publicField(this, "details", details);
this.name = "TagLibError";
Object.setPrototypeOf(this, _TagLibError.prototype);
}
};
}
});
// src/errors/classes.ts
function errorMessage(error) {
return error instanceof Error ? error.message : String(error);
}
function createErrorMessage(prefix, details) {
return `${prefix}: ${details}`;
}
function formatFileSize(bytes) {
if (bytes < 1024) return `${bytes} bytes`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}
var TagLibInitializationError, InvalidFormatError, UnsupportedFormatError, FileOperationError, MetadataError, MemoryError, EnvironmentError;
var init_classes = __esm({
"src/errors/classes.ts"() {
"use strict";
init_base();
TagLibInitializationError = class _TagLibInitializationError extends TagLibError {
/**
* Creates a new TagLibInitializationError
* @param message - Description of the initialization failure
* @param details - Additional context about the error
*/
constructor(message, details) {
super(
"INITIALIZATION",
createErrorMessage("Failed to initialize TagLib Wasm module", message),
details
);
this.name = "TagLibInitializationError";
Object.setPrototypeOf(this, _TagLibInitializationError.prototype);
}
};
InvalidFormatError = class _InvalidFormatError extends TagLibError {
/**
* Creates a new InvalidFormatError
* @param message - Description of the format error
* @param bufferSize - Size of the audio buffer in bytes
* @param details - Additional context about the error
*/
constructor(message, bufferSize, details) {
const errorDetails = [`Invalid audio file format: ${message}`];
if (bufferSize !== void 0) {
errorDetails.push(`Buffer size: ${formatFileSize(bufferSize)}`);
if (bufferSize < 1024) {
errorDetails.push(
"Audio files must be at least 1KB to contain valid headers."
);
}
}
super(
"INVALID_FORMAT",
errorDetails.join(". "),
{ ...details, bufferSize }
);
__publicField(this, "bufferSize", bufferSize);
this.name = "InvalidFormatError";
Object.setPrototypeOf(this, _InvalidFormatError.prototype);
}
};
UnsupportedFormatError = class _UnsupportedFormatError extends TagLibError {
/**
* Creates a new UnsupportedFormatError
* @param format - The unsupported format that was encountered
* @param supportedFormats - List of formats that are supported
* @param details - Additional context about the error
*/
constructor(format, supportedFormats = SUPPORTED_FORMATS, details) {
super(
"UNSUPPORTED_FORMAT",
`Unsupported audio format: ${format}. Supported formats: ${supportedFormats.join(", ")}`,
{ ...details, format, supportedFormats }
);
__publicField(this, "format", format);
__publicField(this, "supportedFormats", supportedFormats);
this.name = "UnsupportedFormatError";
Object.setPrototypeOf(this, _UnsupportedFormatError.prototype);
}
};
FileOperationError = class _FileOperationError extends TagLibError {
/**
* Creates a new FileOperationError
* @param operation - The file operation that failed
* @param message - Description of the failure
* @param path - File path involved in the operation
* @param details - Additional context about the error
*/
constructor(operation, message, path, details) {
const errorDetails = [`Failed to ${operation} file`];
if (path) {
errorDetails.push(`Path: ${path}`);
}
errorDetails.push(message);
super(
"FILE_OPERATION",
errorDetails.join(". "),
{ ...details, operation, path }
);
__publicField(this, "operation", operation);
__publicField(this, "path", path);
this.name = "FileOperationError";
Object.setPrototypeOf(this, _FileOperationError.prototype);
}
};
MetadataError = class _MetadataError extends TagLibError {
/**
* Creates a new MetadataError
* @param operation - The metadata operation that failed
* @param message - Description of the failure
* @param field - The metadata field involved
* @param details - Additional context about the error
*/
constructor(operation, message, field, details) {
const errorDetails = [`Failed to ${operation} metadata`];
if (field) {
errorDetails.push(`Field: ${field}`);
}
errorDetails.push(message);
super(
"METADATA",
errorDetails.join(". "),
{ ...details, operation, field }
);
__publicField(this, "operation", operation);
__publicField(this, "field", field);
this.name = "MetadataError";
Object.setPrototypeOf(this, _MetadataError.prototype);
}
};
MemoryError = class _MemoryError extends TagLibError {
/**
* Creates a new MemoryError
* @param message - Description of the memory failure
* @param details - Additional context about the error
*/
constructor(message, details) {
super(
"MEMORY",
createErrorMessage("Memory allocation failed", message),
details
);
this.name = "MemoryError";
Object.setPrototypeOf(this, _MemoryError.prototype);
}
};
EnvironmentError = class _EnvironmentError extends TagLibError {
/**
* Creates a new EnvironmentError
* @param environment - The runtime environment name
* @param reason - Why the environment is incompatible
* @param requiredFeature - The feature that is missing
*/
constructor(environment, reason, requiredFeature) {
const message = requiredFeature ? `Environment '${environment}' ${reason}. Required feature: ${requiredFeature}.` : `Environment '${environment}' ${reason}.`;
super("ENVIRONMENT", message);
__publicField(this, "environment", environment);
__publicField(this, "reason", reason);
__publicField(this, "requiredFeature", requiredFeature);
this.name = "EnvironmentError";
Object.setPrototypeOf(this, _EnvironmentError.prototype);
}
};
}
});
// src/errors/guards.ts
function isTagLibError(error) {
return error instanceof TagLibError;
}
function isInvalidFormatError(error) {
return error instanceof InvalidFormatError;
}
function isUnsupportedFormatError(error) {
return error instanceof UnsupportedFormatError;
}
function isFileOperationError(error) {
return error instanceof FileOperationError;
}
function isMetadataError(error) {
return error instanceof MetadataError;
}
function isMemoryError(error) {
return error instanceof MemoryError;
}
function isEnvironmentError(error) {
return error instanceof EnvironmentError;
}
var init_guards = __esm({
"src/errors/guards.ts"() {
"use strict";
init_base();
init_classes();
}
});
// src/errors/index.ts
var init_errors = __esm({
"src/errors/index.ts"() {
"use strict";
init_base();
init_classes();
init_guards();
}
});
// src/errors.ts
var init_errors2 = __esm({
"src/errors.ts"() {
"use strict";
init_errors();
}
});
// src/taglib/audio-file-bwf.ts
function requireBwf(format) {
if (!BWF_FORMATS.has(format)) {
throw new UnsupportedFormatError(format, ["WAV", "FLAC"], {
operation: "BWF metadata"
});
}
}
function getBextData(handle) {
const raw = handle.getBextData();
return raw && raw.length > 0 ? raw : void 0;
}
function setBextData(handle, format, data) {
requireBwf(format);
handle.setBextData(data);
}
function getBext(handle) {
const raw = handle.getBextData();
return raw && raw.length > 0 ? decodeBext(raw) : void 0;
}
function setBext(handle, format, bext) {
requireBwf(format);
handle.setBextData(encodeBext(bext));
}
function getIxml(handle) {
return handle.getIxml();
}
function setIxml(handle, format, data) {
requireBwf(format);
handle.setIxml(data);
}
var BWF_FORMATS;
var init_audio_file_bwf = __esm({
"src/taglib/audio-file-bwf.ts"() {
"use strict";
init_bext();
init_errors2();
BWF_FORMATS = /* @__PURE__ */ new Set(["WAV", "FLAC"]);
}
});
// src/taglib/id3v2-frames.ts
function assertMp3(format) {
if (format !== "MP3") {
throw new UnsupportedFormatError(format, ["MP3"], {
operation: "id3v2Frames"
});
}
}
function assertFrameId(id, operation) {
if (!FRAME_ID_PATTERN.test(id)) {
throw new MetadataError(
operation,
`Invalid ID3v2 frame ID: "${id}". Frame IDs must match [A-Z0-9]{4}`,
id
);
}
}
function assertFrameBodies(id, data) {
for (const body of data) {
if (body.length === 0) {
throw new MetadataError(
"write",
"Frame data must not be empty: zero-size ID3v2 frames are invalid",
id
);
}
}
}
function toPublicFrame(frame) {
return {
id: frame.id,
data: frame.data,
...frame.flags ? { flags: frame.flags } : {}
};
}
var FRAME_ID_PATTERN;
var init_id3v2_frames = __esm({
"src/taglib/id3v2-frames.ts"() {
"use strict";
init_classes();
FRAME_ID_PATTERN = /^[A-Z0-9]{4}$/;
}
});
// browser-stub:platform-io-browser-stub
function getPlatformIO() {
throw new Error("Filesystem operations are not available in the browser.");
}
var init_platform_io_browser_stub = __esm({
"browser-stub:platform-io-browser-stub"() {
}
});
// src/utils/write.ts
async function writeFileData(path, data) {
try {
await getPlatformIO().writeFile(path, data);
} catch (error) {
if (error instanceof EnvironmentError) throw error;
throw new FileOperationError(
"write",
error.message,
path
);
}
}
var init_write = __esm({
"src/utils/write.ts"() {
"use strict";
init_errors2();
init_platform_io_browser_stub();
}
});
// browser-stub:node-fs-browser-stub
function getNodeFsSync() {
return null;
}
var init_node_fs_browser_stub = __esm({
"browser-stub:node-fs-browser-stub"() {
}
});
// src/utils/rating.ts
function normalized(value) {
return value;
}
var init_rating = __esm({
"src/utils/rating.ts"() {
"use strict";
}
});
// src/runtime/detector.ts
function isDeno() {
return g.Deno !== void 0;
}
var g, EXNREF_PROBE;
var init_detector = __esm({
"src/runtime/detector.ts"() {
"use strict";
g = globalThis;
EXNREF_PROBE = new Uint8Array([
0,
97,
115,
109,
1,
0,
0,
0,
// Wasm header
6,
6,
// Global section, 6 bytes
1,
// 1 global
105,
0,
// exnref, const
208,
105,
11
// ref.null exnref, end
]);
}
});
// src/constants/additional-properties.ts
var ADDITIONAL_PROPERTIES;
var init_additional_properties = __esm({
"src/constants/additional-properties.ts"() {
"use strict";
ADDITIONAL_PROPERTIES = {
albumArtistSort: {
key: "ALBUMARTISTSORT",
description: "Sort name for album artist (for alphabetization)",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TSO2" },
vorbis: "ALBUMARTISTSORT",
mp4: "soaa"
}
},
composerSort: {
key: "COMPOSERSORT",
description: "Sort name for composer (for alphabetization)",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TSOC" },
vorbis: "COMPOSERSORT",
mp4: "soco"
}
},
subtitle: {
key: "SUBTITLE",
description: "Subtitle or description refinement",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TIT3" },
vorbis: "SUBTITLE",
mp4: "----:com.apple.iTunes:SUBTITLE"
}
},
label: {
key: "LABEL",
description: "Record label name",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
mappings: {
id3v2: { frame: "TPUB" },
vorbis: "LABEL",
mp4: "----:com.apple.iTunes:LABEL",
wav: "IPUB"
}
},
producer: {
key: "PRODUCER",
description: "Producer of the recording",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TXXX", description: "PRODUCER" },
vorbis: "PRODUCER",
mp4: "----:com.apple.iTunes:PRODUCER"
}
},
radioStationOwner: {
key: "RADIOSTATIONOWNER",
description: "Owner of the radio station",
type: "string",
supportedFormats: ["ID3v2"],
mappings: {
id3v2: { frame: "TRSO" }
}
},
asin: {
key: "ASIN",
description: "Amazon Standard Identification Number",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TXXX", description: "ASIN" },
vorbis: "ASIN",
mp4: "----:com.apple.iTunes:ASIN"
}
},
musicbrainzReleaseArtistId: {
key: "MUSICBRAINZ_ALBUMARTISTID",
description: "MusicBrainz Release Artist ID (UUID)",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: {
frame: "TXXX",
description: "MusicBrainz Album Artist Id"
},
vorbis: "MUSICBRAINZ_ALBUMARTISTID",
mp4: "----:com.apple.iTunes:MusicBrainz Album Artist Id"
}
},
musicbrainzWorkId: {
key: "MUSICBRAINZ_WORKID",
description: "MusicBrainz Work ID (UUID)",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TXXX", description: "MusicBrainz Work Id" },
vorbis: "MUSICBRAINZ_WORKID",
mp4: "----:com.apple.iTunes:MusicBrainz Work Id"
}
},
musicbrainzReleaseTrackId: {
key: "MUSICBRAINZ_RELEASETRACKID",
description: "MusicBrainz Release Track ID (UUID)",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: {
frame: "TXXX",
description: "MusicBrainz Release Track Id"
},
vorbis: "MUSICBRAINZ_RELEASETRACKID",
mp4: "----:com.apple.iTunes:MusicBrainz Release Track Id"
}
},
podcastId: {
key: "PODCASTID",
description: "Podcast episode identifier",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TGID" },
vorbis: "PODCASTID",
mp4: "egid"
}
},
podcastUrl: {
key: "PODCASTURL",
description: "Podcast feed URL",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "WFED" },
vorbis: "PODCASTURL",
mp4: "purl"
}
},
originalArtist: {
key: "ORIGINALARTIST",
description: "Original artist of a cover or remix",
type: "string",
supportedFormats: ["ID3v2", "Vorbis"],
mappings: {
id3v2: { frame: "TOPE" },
vorbis: "ORIGINALARTIST"
}
},
originalAlbum: {
key: "ORIGINALALBUM",
description: "Original album of a cover or remix",
type: "string",
supportedFormats: ["ID3v2", "Vorbis"],
mappings: {
id3v2: { frame: "TOAL" },
vorbis: "ORIGINALALBUM"
}
},
originalDate: {
key: "ORIGINALDATE",
description: "Original release date",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TDOR" },
vorbis: "ORIGINALDATE",
mp4: "----:com.apple.iTunes:ORIGINALDATE"
}
},
script: {
key: "SCRIPT",
description: "Writing script used for text (e.g., Latn, Jpan)",
type: "string",
supportedFormats: ["MP4", "Vorbis"],
mappings: {
vorbis: "SCRIPT",
mp4: "----:com.apple.iTunes:SCRIPT"
}
},
involvedPeople: {
key: "INVOLVEDPEOPLE",
description: "List of involved people and their roles",
type: "string",
supportedFormats: ["ID3v2"],
mappings: {
id3v2: { frame: "TIPL" }
}
},
encoding: {
key: "ENCODING",
description: "Encoding software or settings",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
mappings: {
id3v2: { frame: "TSSE" },
vorbis: "ENCODING",
mp4: "\xA9too",
wav: "ISFT"
}
}
};
}
});
// src/constants/basic-properties.ts
var BASIC_PROPERTIES;
var init_basic_properties = __esm({
"src/constants/basic-properties.ts"() {
"use strict";
BASIC_PROPERTIES = {
title: {
key: "TITLE",
description: "The title of the track",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
mappings: {
id3v2: { frame: "TIT2" },
vorbis: "TITLE",
mp4: "\xA9nam",
wav: "INAM"
}
},
artist: {
key: "ARTIST",
description: "The primary performer(s) of the track",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
mappings: {
id3v2: { frame: "TPE1" },
vorbis: "ARTIST",
mp4: "\xA9ART",
wav: "IART"
}
},
album: {
key: "ALBUM",
description: "The album/collection name",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
mappings: {
id3v2: { frame: "TALB" },
vorbis: "ALBUM",
mp4: "\xA9alb",
wav: "IPRD"
}
},
date: {
key: "DATE",
description: "The date of recording (typically year)",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
mappings: {
id3v2: { frame: "TDRC" },
vorbis: "DATE",
mp4: "\xA9day",
wav: "ICRD"
}
},
trackNumber: {
key: "TRACKNUMBER",
description: "The track number within the album",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
mappings: {
id3v2: { frame: "TRCK" },
vorbis: "TRACKNUMBER",
mp4: "trkn",
wav: "ITRK"
}
},
genre: {
key: "GENRE",
description: "The musical genre",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
mappings: {
id3v2: { frame: "TCON" },
vorbis: "GENRE",
mp4: "\xA9gen",
wav: "IGNR"
}
},
comment: {
key: "COMMENT",
description: "Comments or notes about the track",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
mappings: {
id3v2: { frame: "COMM" },
vorbis: "COMMENT",
mp4: "\xA9cmt",
wav: "ICMT"
}
}
};
}
});
// src/constants/general-extended-properties.ts
var GENERAL_EXTENDED_PROPERTIES;
var init_general_extended_properties = __esm({
"src/constants/general-extended-properties.ts"() {
"use strict";
GENERAL_EXTENDED_PROPERTIES = {
albumArtist: {
key: "ALBUMARTIST",
description: "The album artist (band/orchestra/ensemble)",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TPE2" },
vorbis: "ALBUMARTIST",
mp4: "aART"
}
},
composer: {
key: "COMPOSER",
description: "The original composer(s) of the track",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TCOM" },
vorbis: "COMPOSER",
mp4: "\xA9wrt"
}
},
copyright: {
key: "COPYRIGHT",
description: "Copyright information",
type: "string",
supportedFormats: ["ID3v2", "Vorbis"],
mappings: {
vorbis: "COPYRIGHT"
}
},
encodedBy: {
key: "ENCODEDBY",
description: "The encoding software or person",
type: "string",
supportedFormats: ["ID3v2", "Vorbis"],
mappings: {
vorbis: "ENCODEDBY"
}
},
discNumber: {
key: "DISCNUMBER",
description: "The disc number for multi-disc sets",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TPOS" },
vorbis: "DISCNUMBER",
mp4: "disk"
}
},
bpm: {
key: "BPM",
description: "Beats per minute",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TBPM" },
vorbis: "BPM",
mp4: "tmpo"
}
},
totalTracks: {
key: "TRACKTOTAL",
description: "Total number of tracks on the album",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TRCK" },
vorbis: "TRACKTOTAL",
mp4: "trkn"
}
},
totalDiscs: {
key: "DISCTOTAL",
description: "Total number of discs in the set",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TPOS" },
vorbis: "DISCTOTAL",
mp4: "disk"
}
},
compilation: {
key: "COMPILATION",
description: "Whether the album is a compilation (various artists)",
type: "boolean",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TCMP" },
vorbis: "COMPILATION",
mp4: "cpil"
}
},
// Sorting Properties
titleSort: {
key: "TITLESORT",
description: "Sort name for title (for alphabetization)",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TSOT" },
vorbis: "TITLESORT",
mp4: "sonm"
}
},
artistSort: {
key: "ARTISTSORT",
description: "Sort name for artist (for alphabetization)",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TSOP" },
vorbis: "ARTISTSORT",
mp4: "soar"
}
},
albumSort: {
key: "ALBUMSORT",
description: "Sort name for album (for alphabetization)",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TSOA" },
vorbis: "ALBUMSORT",
mp4: "soal"
}
},
// Additional common properties
lyricist: {
key: "LYRICIST",
description: "The lyrics/text writer(s)",
type: "string",
supportedFormats: ["Vorbis"],
mappings: {
vorbis: "LYRICIST"
}
},
conductor: {
key: "CONDUCTOR",
description: "The conductor",
type: "string",
supportedFormats: ["Vorbis"],
mappings: {
vorbis: "CONDUCTOR"
}
},
remixedBy: {
key: "REMIXER",
description: "Person who remixed the track",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TPE4" },
vorbis: "REMIXER",
mp4: "----:com.apple.iTunes:REMIXER"
}
},
language: {
key: "LANGUAGE",
description: "Language of vocals/lyrics",
type: "string",
supportedFormats: ["Vorbis"],
mappings: {
vorbis: "LANGUAGE"
}
},
publisher: {
key: "PUBLISHER",
description: "The publisher",
type: "string",
supportedFormats: ["Vorbis"],
mappings: {
vorbis: "PUBLISHER"
}
},
mood: {
key: "MOOD",
description: "The mood/atmosphere of the track",
type: "string",
supportedFormats: ["Vorbis"],
mappings: {
vorbis: "MOOD"
}
},
media: {
key: "MEDIA",
description: "Media type (CD, vinyl, etc.)",
type: "string",
supportedFormats: ["Vorbis"],
mappings: {
vorbis: "MEDIA"
}
},
grouping: {
key: "GROUPING",
description: "Content group/work",
type: "string",
supportedFormats: ["Vorbis"],
mappings: {
vorbis: "GROUPING"
}
},
work: {
key: "WORK",
description: "Work name",
type: "string",
supportedFormats: ["Vorbis"],
mappings: {
vorbis: "WORK"
}
},
lyrics: {
key: "LYRICS",
description: "Lyrics content",
type: "string",
supportedFormats: ["Vorbis"],
mappings: {
vorbis: "LYRICS"
}
},
isrc: {
key: "ISRC",
description: "International Standard Recording Code",
type: "string",
supportedFormats: ["Vorbis"],
mappings: {
vorbis: "ISRC"
}
},
catalogNumber: {
key: "CATALOGNUMBER",
description: "Catalog number",
type: "string",
supportedFormats: ["Vorbis"],
mappings: {
vorbis: "CATALOGNUMBER"
}
},
barcode: {
key: "BARCODE",
description: "Barcode (EAN/UPC)",
type: "string",
supportedFormats: ["Vorbis"],
mappings: {
vorbis: "BARCODE"
}
}
};
}
});
// src/constants/specialized-properties.ts
var SPECIALIZED_PROPERTIES;
var init_specialized_properties = __esm({
"src/constants/specialized-properties.ts"() {
"use strict";
SPECIALIZED_PROPERTIES = {
// MusicBrainz Identifiers
musicbrainzArtistId: {
key: "MUSICBRAINZ_ARTISTID",
description: "MusicBrainz Artist ID (UUID)",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TXXX", description: "MusicBrainz Artist Id" },
vorbis: "MUSICBRAINZ_ARTISTID",
mp4: "----:com.apple.iTunes:MusicBrainz Artist Id"
}
},
musicbrainzReleaseId: {
key: "MUSICBRAINZ_ALBUMID",
description: "MusicBrainz Release ID (UUID)",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TXXX", description: "MusicBrainz Album Id" },
vorbis: "MUSICBRAINZ_ALBUMID",
mp4: "----:com.apple.iTunes:MusicBrainz Album Id"
}
},
musicbrainzTrackId: {
key: "MUSICBRAINZ_TRACKID",
description: "MusicBrainz Recording ID (UUID)",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "UFID", description: "http://musicbrainz.org" },
vorbis: "MUSICBRAINZ_TRACKID",
mp4: "----:com.apple.iTunes:MusicBrainz Track Id"
}
},
musicbrainzReleaseGroupId: {
key: "MUSICBRAINZ_RELEASEGROUPID",
description: "MusicBrainz Release Group ID (UUID)",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TXXX", description: "MusicBrainz Release Group Id" },
vorbis: "MUSICBRAINZ_RELEASEGROUPID",
mp4: "----:com.apple.iTunes:MusicBrainz Release Group Id"
}
},
// ReplayGain Properties
replayGainTrackGain: {
key: "REPLAYGAIN_TRACK_GAIN",
description: "ReplayGain track gain in dB (e.g., '-6.54 dB')",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TXXX", description: "ReplayGain_Track_Gain" },
vorbis: "REPLAYGAIN_TRACK_GAIN",
mp4: "----:com.apple.iTunes:replaygain_track_gain"
}
},
replayGainTrackPeak: {
key: "REPLAYGAIN_TRACK_PEAK",
description: "ReplayGain track peak value (0.0-1.0)",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TXXX", description: "ReplayGain_Track_Peak" },
vorbis: "REPLAYGAIN_TRACK_PEAK",
mp4: "----:com.apple.iTunes:replaygain_track_peak"
}
},
replayGainAlbumGain: {
key: "REPLAYGAIN_ALBUM_GAIN",
description: "ReplayGain album gain in dB",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TXXX", description: "ReplayGain_Album_Gain" },
vorbis: "REPLAYGAIN_ALBUM_GAIN",
mp4: "----:com.apple.iTunes:replaygain_album_gain"
}
},
replayGainAlbumPeak: {
key: "REPLAYGAIN_ALBUM_PEAK",
description: "ReplayGain album peak value (0.0-1.0)",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TXXX", description: "ReplayGain_Album_Peak" },
vorbis: "REPLAYGAIN_ALBUM_PEAK",
mp4: "----:com.apple.iTunes:replaygain_album_peak"
}
},
// AcoustID Properties
acoustidFingerprint: {
key: "ACOUSTID_FINGERPRINT",
description: "AcoustID fingerprint (Chromaprint)",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TXXX", description: "Acoustid Fingerprint" },
vorbis: "ACOUSTID_FINGERPRINT",
mp4: "----:com.apple.iTunes:Acoustid Fingerprint"
}
},
acoustidId: {
key: "ACOUSTID_ID",
description: "AcoustID UUID",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TXXX", description: "Acoustid Id" },
vorbis: "ACOUSTID_ID",
mp4: "----:com.apple.iTunes:Acoustid Id"
}
},
// Apple Sound Check
appleSoundCheck: {
key: "ITUNNORM",
description: "Apple Sound Check normalization data",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TXXX", description: "iTunNORM" },
vorbis: "ITUNNORM",
mp4: "----:com.apple.iTunes:iTunNORM"
}
},
/**
* Apple gapless-playback data (encoder delay, padding, original sample count).
*
* Exists so the two Apple freeform atoms are presented consistently: without
* it, `properties()` answered `appleSoundCheck` for iTunNORM but the raw
* uppercased `ITUNSMPB` for its sibling, because only one had an alias. The
* name mirrors `appleSoundCheck` — vendor-prefixed, feature-named rather than
* atom-named (tuneup-ibo).
*/
appleGaplessInfo: {
key: "ITUNSMPB",
description: "Apple gapless playback data (encoder delay and padding)",
type: "string",
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
mappings: {
id3v2: { frame: "TXXX", description: "iTunSMPB" },
vorbis: "ITUNSMPB",
mp4: "----:com.apple.iTunes:iTunSMPB"
}
}
};
}
});
// src/constants/properties.ts
function toTagLibKey(key) {
return _toTagLib[key] ?? key;
}
function mp4AtomWireKey(atom) {
return _mp4AtomToWireKey[atom];
}
function mp4FreeformAtomNames(wireKeys) {
const names = [];
for (const key of wireKeys) {
const atom = _mp4FreeformAtoms[key];
if (atom !== void 0) names.push(atom);
}
return names;
}
function fromTagLibKey(key) {
return _fromTagLib[key] ?? key;
}
function remapKeysFromTagLib(obj) {
const result = {};
for (const [key, value] of Object.entries(obj)) {
result[fromTagLibKey(key)] = value;
}
return result;
}
var PROPERTIES, _toTagLib, _fromTagLib, _mp4FreeformAtoms, _mp4AtomToWireKey;
var init_properties = __esm({
"src/constants/properties.ts"() {
"use strict";
init_additional_properties();
init_basic_properties();
init_general_extended_properties();
init_specialized_properties();
PROPERTIES = {
...BASIC_PROPERTIES,
...GENERAL_EXTENDED_PROPERTIES,
...SPECIALIZED_PROPERTIES,
...ADDITIONAL_PROPERTIES
};
_toTagLib = {};
_fromTagLib = {};
for (const [camelKey, meta] of Object.entries(PROPERTIES)) {
const wireKey = meta.key;
_toTagLib[camelKey] = wireKey;
_fromTagLib[wireKey] = camelKey;
}
_toTagLib["year"] = "DATE";
_toTagLib["track"] = "TRACKNUMBER";
_fromTagLib["disc"] = "discNumber";
_mp4FreeformAtoms = {};
for (const meta of Object.values(PROPERTIES)) {
const { key, mappings } = meta;
const atom = mappings?.mp4;
if (typeof atom !== "string" || !atom.startsWith("----:")) continue;
const bare = atom.slice(atom.lastIndexOf(":") + 1);
if (bare === bare.toUpperCase()) continue;
_mp4FreeformAtoms[key] = atom;
const readKey = bare.toUpperCase();
const camel = _fromTagLib[key];
if (camel !== void 0 && _fromTagLib[readKey] === void 0) {
_fromTagLib[readKey] = camel;
}
}
_mp4AtomToWireKey = {};
for (const meta of Object.values(PROPERTIES)) {
const { key, mappings } = meta;
const atom = mappings?.mp4;
if (typeof atom !== "string" || atom.startsWith("----:")) continue;
if (_mp4AtomToWireKey[atom] === void 0) _mp4AtomToWireKey[atom] = key;
}
}
});
// src/taglib/mp4-item-names.ts
var MP4_ITEM_NAMES_KEY;
var init_mp4_item_names = __esm({
"src/taglib/mp4-item-names.ts"() {
"use strict";
MP4_ITEM_NAMES_KEY = "_mp4ItemNames";
}
});
// src/taglib/mutable-tag-impl.ts
function buildMutableTag(handle) {
let data = handle.getTagData();
const tag = {
get title() {
return data.title;
},
get artist() {
return data.artist;
},
get album() {
return data.album;
},
get comment() {
return data.comment;
},
get genre() {
return data.genre;
},
get year() {
return data.year;
},
get track() {
return data.track;
},
get date() {
return handle.getProperty("DATE") || void 0;
},
setTitle: (value) => {
handle.setTagData({ title: value });
data = handle.getTagData();
return tag;
},
setArtist: (value) => {
handle.setTagData({ artist: value });
data = handle.getTagData();
return tag;
},
setAlbum: (value) => {
handle.setTagData({ album: value });
data = handle.getTagData();
return tag;
},
setComment: (value) => {
handle.setTagData({ comment: value });
data = handle.getTagData();
return tag;
},
setGenre: (value) => {
handle.setTagData({ genre: value });
data = handle.getTagData();
return tag;
},
setYear: (value) => {
handle.setTagData({ year: value });
data = handle.getTagData();
return tag;
},
setTrack: (value) => {
const existing = value > 0 ? handle.getProperty(RAW_TRACK_WIRE_KEY) : "";
const slash = existing.indexOf("/");
if (slash !== -1) {
handle.setProperty(
RAW_TRACK_WIRE_KEY,
`${value}${existing.slice(slash)}`
);
} else {
handle.setTagData({ track: value });
}
data = handle.getTagData();
return tag;
},
setDate: (value) => {
if (value === "") {
handle.setTagData({ year: 0 });
} else {
handle.setProperty("DATE", value);
}
data = handle.getTagData();
return tag;
}
};
return tag;
}
var RAW_TRACK_WIRE_KEY;
var init_mutable_tag_impl = __esm({
"src/taglib/mutable-tag-impl.ts"() {
"use strict";
init_properties();
RAW_TRACK_WIRE_KEY = toTagLibKey("trackNumber");
}
});
// src/taglib/audio-file-base.ts
var LYRICS_PROPERTY_KEY, LYRICS_WIRE_KEY, EMPTY_KEY_SET, BaseAudioFileImpl;
var init_audio_file_base = __esm({
"src/taglib/audio-file-base.ts"() {
"use strict";
init_properties();
init_errors2();
init_mp4_item_names();
init_mutable_tag_impl();
LYRICS_PROPERTY_KEY = "lyrics";
LYRICS_WIRE_KEY = toTagLibKey(LYRICS_PROPERTY_KEY);
EMPTY_KEY_SET = /* @__PURE__ */ new Set();
BaseAudioFileImpl = class {
constructor(module, fileHandle, sourcePath, originalSource, isPartiallyLoaded = false, partialLoadOptions) {
__publicField(this, "module", module);
__publicField(this, "fileHandle");
__publicField(this, "cachedAudioProperties", null);
__publicField(this, "sourcePath");
__publicField(this, "originalSource");
__publicField(this, "isPartiallyLoaded", false);
__publicField(this, "partialLoadOptions");
/** Text-property wire keys in the partial header at load; undefined if full. */
__publicField(this, "partialKeysAtLoad");
this.fileHandle = fileHandle;
this.sourcePath = sourcePath;
this.originalSource = originalSource;
this.isPartiallyLoaded = isPartiallyLoaded;
this.partialLoadOptions = partialLoadOptions;
this.partialKeysAtLoad = isPartiallyLoaded ? new Set(Object.keys(fileHandle.getProperties())) : void 0;
}
/**
* Wire-key text properties present in the partial header at load but now
* absent — the user deleted them. The partial-load reconstruct subtracts these
* from its preserve-the-original merge so a deletion persists without wiping
* frames beyond the loaded header (taglib-d14). Lyrics are excluded (they ride
* their own reconstruct path); empty for a full load.
*/
partialDeletedPropertyKeys() {
if (!this.partialKeysAtLoad) return EMPTY_KEY_SET;
const current = new Set(Object.keys(this.handle.getProperties()));
const deleted = /* @__PURE__ */ new Set();
for (const key of this.partialKeysAtLoad) {
if (key !== LYRICS_WIRE_KEY && !current.has(key)) deleted.add(key);
}
return deleted;
}
get handle() {
if (!this.fileHandle) {
throw new MetadataError("read", "File handle has been disposed");
}
return this.fileHandle;
}
getFormat() {
return this.handle.getFormat();
}
isFormat(format) {
return this.getFormat() === format;
}
tag() {
return buildMutableTag(this.handle);
}
audioProperties() {
if (!this.cachedAudioProperties) {
this.cachedAudioProperties = this.handle.getAudioProperties() ?? null;
}
return this.cachedAudioProperties ?? void 0;
}
properties() {
const remapped = remapKeysFromTagLib(this.handle.getProperties());
delete remapped[LYRICS_PROPERTY_KEY];
return remapped;
}
setProperties(properties) {
const translated = {};
for (const [key, values] of Object.entries(properties)) {
if (values !== void 0) translated[toTagLibKey(key)] = values;
}
if (!(LYRICS_WIRE_KEY in translated)) {
const existing = this.handle.getProperties()[LYRICS_WIRE_KEY];
if (existing !== void 0) translated[LYRICS_WIRE_KEY] = existing;
}
const atomNames = mp4FreeformAtomNames(Object.keys(translated));
if (atomNames.length > 0) translated[MP4_ITEM_NAMES_KEY] = atomNames;
this.handle.setProperties(translated);
}
getProperty(key) {
const value = this.handle.getProperty(toTagLibKey(key));
if (value !== "") return value;
if (!this.isMP4()) return void 0;
const remapped = this.properties()[key]?.[0];
return remapped === "" ? void 0 : remapped;
}
setProperty(key, value) {
const wireKey = toTagLibKey(key);
const atomNames = mp4FreeformAtomNames([wireKey]);
if (atomNames.length > 0 && this.isMP4()) {
this.handle.setProperties({
...