UNPKG

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
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 };