taglib-wasm
Version:
TagLib-Wasm is the universal tagging library for TypeScript/JavaScript platforms: Browsers, Node.js, Deno, Bun, Cloudflare Workers, and Electron apps
90 lines (89 loc) • 3.25 kB
JavaScript
import { applyTagsToFile } from "../simple/index.js";
import { writeFileData } from "../utils/write.js";
import { processBatch } from "./file-processors.js";
import { scanFolder } from "./scan-operations.js";
import { EMPTY_TAG } from "./types.js";
async function updateFolderTags(updates, options = {}) {
const startTime = Date.now();
const { continueOnError = true, concurrency = 4, signal } = options;
const items = [];
const batchSize = concurrency * 10;
for (let i = 0; i < updates.length; i += batchSize) {
signal?.throwIfAborted();
const batch = updates.slice(i, Math.min(i + batchSize, updates.length));
const updateMap = new Map(batch.map((u) => [u.path, u]));
await processBatch(
batch.map((u) => u.path),
async (path) => {
const update = updateMap.get(path);
try {
await applyTagsToFile(update.path, update.tags);
items.push({ status: "ok", path: update.path });
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
if (continueOnError) {
items.push({ status: "error", path: update.path, error: err });
} else {
throw err;
}
}
return { path, tags: EMPTY_TAG };
},
concurrency
);
}
return { items, duration: Date.now() - startTime };
}
function buildCriteriaKey(tags, criteria) {
const record = {};
for (const field of criteria) {
const val = tags[field];
const strVal = Array.isArray(val) ? val.join(", ") : String(val ?? "");
if (strVal) record[field] = strVal;
}
if (Object.keys(record).length === 0) return null;
const key = criteria.map((f) => record[f] ?? "").join("\0");
return { record, key };
}
async function findDuplicates(folderPath, options) {
const { criteria = ["artist", "title"], ...scanOptions } = options ?? {};
const result = await scanFolder(folderPath, scanOptions);
scanOptions.signal?.throwIfAborted();
const groupMap = /* @__PURE__ */ new Map();
for (const item of result.items) {
if (item.status !== "ok") continue;
const entry = buildCriteriaKey(item.tags, criteria);
if (!entry) continue;
const existing = groupMap.get(entry.key);
if (existing) {
existing.files.push(item);
} else {
groupMap.set(entry.key, { criteria: entry.record, files: [item] });
}
}
return Array.from(groupMap.values()).filter((g) => g.files.length >= 2);
}
async function exportFolderMetadata(folderPath, outputPath, options) {
const result = await scanFolder(folderPath, options);
const okItems = result.items.filter((item) => item.status === "ok");
const errorItems = result.items.filter((item) => item.status === "error");
const data = {
folder: folderPath,
scanDate: (/* @__PURE__ */ new Date()).toISOString(),
summary: {
totalFiles: result.items.length,
processedFiles: okItems.length,
errors: errorItems.length,
duration: result.duration
},
files: okItems,
errors: errorItems
};
const jsonBytes = new TextEncoder().encode(JSON.stringify(data, null, 2));
await writeFileData(outputPath, jsonBytes);
}
export {
exportFolderMetadata,
findDuplicates,
updateFolderTags
};