91 lines
2.5 KiB
JavaScript
91 lines
2.5 KiB
JavaScript
const fs = require("fs");
|
|
const path = require("path");
|
|
const { PDFDocument } = require("pdf-lib");
|
|
|
|
function clean(value) {
|
|
if (value == null) return "";
|
|
return String(value).replace(/\0/g, "").replace(/\s+/g, " ").trim();
|
|
}
|
|
|
|
function titleFromFilename(filePath) {
|
|
return path
|
|
.basename(filePath, path.extname(filePath))
|
|
.replace(/[_]+/g, " ")
|
|
.replace(/\s+/g, " ")
|
|
.trim();
|
|
}
|
|
|
|
function parseKeywords(value) {
|
|
if (!value) return [];
|
|
const list = Array.isArray(value) ? value : String(value).split(/[,;]+/);
|
|
return [...new Set(list.map(clean).filter(Boolean))];
|
|
}
|
|
|
|
function uniqueDest(dir, originalName) {
|
|
const ext = path.extname(originalName) || ".pdf";
|
|
const stem =
|
|
path
|
|
.basename(originalName, ext)
|
|
.replace(/[<>:"/\\|?*]+/g, "-")
|
|
.slice(0, 80) || "document";
|
|
let dest = path.join(dir, `${stem}${ext}`);
|
|
let n = 2;
|
|
while (fs.existsSync(dest)) {
|
|
dest = path.join(dir, `${stem}-${n}${ext}`);
|
|
n += 1;
|
|
}
|
|
return dest;
|
|
}
|
|
|
|
async function extractMetadata(filePath) {
|
|
const bytes = fs.readFileSync(filePath);
|
|
const pdf = await PDFDocument.load(bytes, {
|
|
ignoreEncryption: true,
|
|
updateMetadata: false,
|
|
});
|
|
return {
|
|
title: clean(pdf.getTitle()) || titleFromFilename(filePath),
|
|
author: clean(pdf.getAuthor()),
|
|
totalPages: pdf.getPageCount(),
|
|
notes: clean(pdf.getSubject()),
|
|
tags: parseKeywords(pdf.getKeywords()),
|
|
};
|
|
}
|
|
|
|
async function importPdfFiles(filePaths, destDir, existingNames) {
|
|
fs.mkdirSync(destDir, { recursive: true });
|
|
const known = new Set((existingNames || []).map((name) => name.toLowerCase()));
|
|
const imported = [];
|
|
const skipped = [];
|
|
const errors = [];
|
|
|
|
for (const filePath of filePaths) {
|
|
const sourceName = path.basename(filePath);
|
|
if (!filePath.toLowerCase().endsWith(".pdf")) {
|
|
skipped.push({ file: sourceName, reason: "not a PDF" });
|
|
continue;
|
|
}
|
|
if (known.has(sourceName.toLowerCase())) {
|
|
skipped.push({ file: sourceName, reason: "already in catalog" });
|
|
continue;
|
|
}
|
|
try {
|
|
const meta = await extractMetadata(filePath);
|
|
const dest = uniqueDest(destDir, sourceName);
|
|
fs.copyFileSync(filePath, dest);
|
|
known.add(sourceName.toLowerCase());
|
|
imported.push({
|
|
...meta,
|
|
pdfPath: dest,
|
|
sourceName,
|
|
});
|
|
} catch (err) {
|
|
errors.push({ file: sourceName, message: err.message });
|
|
}
|
|
}
|
|
|
|
return { imported, skipped, errors };
|
|
}
|
|
|
|
module.exports = { importPdfFiles };
|