taglib-wasm
Version:
TagLib-Wasm is the universal tagging library for TypeScript/JavaScript platforms: Browsers, Node.js, Deno, Bun, Cloudflare Workers, and Electron apps
170 lines (169 loc) • 5.2 kB
JavaScript
import { fromTagLibKey } from "../constants/properties.js";
import { parseLeadingInt as parseNumeric } from "./mirror-fields.js";
const BASIC_PROPERTY_KEYS = {
title: "title",
artist: "artist",
album: "album",
comment: "comment",
genre: "genre",
date: "year",
trackNumber: "track"
};
const BASIC_FIELDS = /* @__PURE__ */ new Set([
"title",
"artist",
"album",
"comment",
"genre",
"year",
"date",
"track",
"trackNumber"
]);
const NUMERIC_FIELDS = /* @__PURE__ */ new Set([
"year",
"track",
"discNumber",
"totalTracks",
"totalDiscs",
"bpm"
]);
function readExtendedTag(audioFile) {
const tag = mapPropertiesToExtendedTag(audioFile.properties());
const pictures = audioFile.getPictures();
if (pictures.length > 0) tag.pictures = pictures;
const ratings = audioFile.getRatings();
if (ratings.length > 0) tag.ratings = ratings;
const lyrics = audioFile.getLyrics();
if (lyrics.length > 0) tag.lyrics = lyrics;
const chapters = audioFile.getChapters();
if (chapters.length > 0) tag.chapters = chapters;
const bext = audioFile.getBext();
if (bext !== void 0) tag.bext = bext;
const bextData = audioFile.getBextData();
if (bextData !== void 0) tag.bextData = bextData;
const ixml = audioFile.getIxml();
if (ixml !== void 0) tag.ixml = ixml;
return tag;
}
function mapPropertiesToExtendedTag(props) {
const tag = {};
for (const [propKey, tagField] of Object.entries(BASIC_PROPERTY_KEYS)) {
const values = props[propKey];
if (!values || values.length === 0) continue;
if (tagField === "year" || tagField === "track") {
const num = parseNumeric(values[0]);
if (num !== void 0) tag[tagField] = num;
if (propKey === "date") {
tag.date = values.length === 1 ? values[0] : values;
}
if (propKey === "trackNumber") {
tag.trackNumber = values.length === 1 ? values[0] : values;
}
} else {
tag[tagField] = values;
}
}
for (const [propKey, totalField] of [
["trackNumber", "totalTracks"],
["discNumber", "totalDiscs"]
]) {
if (tag[totalField] !== void 0) continue;
const raw = props[propKey]?.[0];
const slash = raw?.indexOf("/") ?? -1;
if (raw === void 0 || slash === -1) continue;
const total = parseNumeric(raw.slice(slash + 1));
if (total !== void 0) tag[totalField] = total;
}
for (const [key, values] of Object.entries(props)) {
if (BASIC_PROPERTY_KEYS[key]) continue;
if (!values || values.length === 0) continue;
const camelKey = fromTagLibKey(key);
if (NUMERIC_FIELDS.has(camelKey)) {
const num = parseNumeric(values[0]);
if (num !== void 0) tag[camelKey] = num;
} else if (camelKey === "compilation") {
tag[camelKey] = values[0] === "1";
} else {
tag[camelKey] = values;
}
}
return tag;
}
function mergeTagUpdates(file, tags) {
const currentProps = file.properties();
const newProps = normalizeTagInput(tags);
reconcilePairFields(currentProps, newProps, tags);
file.setProperties({ ...currentProps, ...newProps });
}
function reconcilePairFields(currentProps, newProps, input) {
const pairs = [
["trackNumber", "totalTracks", "track"],
["discNumber", "totalDiscs", "discNumber"]
];
for (const [rawKey, totalKey, numericKey] of pairs) {
const incoming = newProps[rawKey]?.[0];
if (incoming?.includes("/")) {
newProps[totalKey] = [];
continue;
}
const numericOnly = input[numericKey] !== void 0 && input[rawKey] === void 0;
if (!numericOnly || incoming === void 0) continue;
const existing = currentProps[rawKey]?.[0];
const slash = existing?.indexOf("/") ?? -1;
if (existing !== void 0 && slash !== -1) {
newProps[rawKey] = [`${incoming}${existing.slice(slash)}`];
}
}
}
function normalizeTagInput(input) {
const props = {};
for (const field of [
"title",
"artist",
"album",
"comment",
"genre"
]) {
const val = input[field];
if (val === void 0) continue;
props[field] = Array.isArray(val) ? val : [val];
}
if (input.year !== void 0) {
props.date = [String(input.year)];
}
if (input.date !== void 0) {
props.date = Array.isArray(input.date) ? input.date : [input.date];
}
if (input.track !== void 0) {
props.trackNumber = [String(input.track)];
}
if (input.trackNumber !== void 0) {
props.trackNumber = Array.isArray(input.trackNumber) ? input.trackNumber : [input.trackNumber];
}
for (const [field, val] of Object.entries(input)) {
if (BASIC_FIELDS.has(field) || val === void 0) continue;
if (field === "compilation") {
props[field] = [val ? "1" : "0"];
} else if (NUMERIC_FIELDS.has(field)) {
props[field] = [String(val)];
} else if (typeof val === "string") {
props[field] = [val];
} else if (Array.isArray(val)) {
props[field] = val;
}
}
for (const [rawField, totalField] of [
["trackNumber", "totalTracks"],
["discNumber", "totalDiscs"]
]) {
if (props[rawField]?.[0]?.includes("/")) delete props[totalField];
}
return props;
}
export {
mapPropertiesToExtendedTag,
mergeTagUpdates,
normalizeTagInput,
readExtendedTag
};