Added getting epub metadata
This commit is contained in:
parent
96eda53f31
commit
6a3470229e
3 changed files with 155 additions and 31 deletions
118
package-lock.json
generated
118
package-lock.json
generated
|
|
@ -19,6 +19,7 @@
|
|||
"@prisma/adapter-pg": "^7.2.0",
|
||||
"@prisma/client": "^7.2.0",
|
||||
"epub": "^1.3.0",
|
||||
"epub2": "^3.0.2",
|
||||
"exif-reader": "^2.0.3",
|
||||
"next": "16.1.1",
|
||||
"next-auth": "^5.0.0-beta.30",
|
||||
|
|
@ -2205,7 +2206,6 @@
|
|||
"version": "20.19.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz",
|
||||
"integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
|
|
@ -2936,6 +2936,16 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/array-hyper-unique": {
|
||||
"version": "2.1.6",
|
||||
"resolved": "https://registry.npmjs.org/array-hyper-unique/-/array-hyper-unique-2.1.6.tgz",
|
||||
"integrity": "sha512-BdlHRqjKSYs88WFaVNVEc6Kv8ln/FdzCKPbcDPuWs4/EXkQFhnjc8TyR7hnPxRjcjo5LKOhUMGUWpAqRgeJvpA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"deep-eql": "= 4.0.0",
|
||||
"lodash": "^4.17.21"
|
||||
}
|
||||
},
|
||||
"node_modules/array-includes": {
|
||||
"version": "3.1.9",
|
||||
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz",
|
||||
|
|
@ -3163,6 +3173,12 @@
|
|||
"baseline-browser-mapping": "dist/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/bluebird": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
|
|
@ -3498,6 +3514,15 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/crlf-normalize": {
|
||||
"version": "1.0.20",
|
||||
"resolved": "https://registry.npmjs.org/crlf-normalize/-/crlf-normalize-1.0.20.tgz",
|
||||
"integrity": "sha512-h/rBerTd3YHQGfv7tNT25mfhWvRq2BBLCZZ80GFarFxf6HQGbpW6iqDL3N+HBLpjLfAdcBXfWAzVlLfHkRUQBQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"ts-type": ">=2"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
|
|
@ -3597,6 +3622,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/deep-eql": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.0.0.tgz",
|
||||
"integrity": "sha512-GxJC5MOg2KyQlv6WiUF/VAnMj4MWnYiXo4oLgeptOELVoknyErb4Z8+5F/IM/K4g9/80YzzatxmWcyRwUseH0A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"type-detect": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||
|
|
@ -3833,6 +3870,42 @@
|
|||
"zipfile": "^0.5.11"
|
||||
}
|
||||
},
|
||||
"node_modules/epub2": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/epub2/-/epub2-3.0.2.tgz",
|
||||
"integrity": "sha512-rhvpt27CV5MZfRetfNtdNwi3XcNg1Am0TwfveJkK8YWeHItHepQ8Js9J06v8XRIjuTrCW/NSGYMTy55Of7BfNQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"adm-zip": "^0.5.10",
|
||||
"array-hyper-unique": "^2.1.4",
|
||||
"bluebird": "^3.7.2",
|
||||
"crlf-normalize": "^1.0.19",
|
||||
"tslib": "^2.6.2",
|
||||
"xml2js": "^0.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/epub2/node_modules/adm-zip": {
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz",
|
||||
"integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/epub2/node_modules/xml2js": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
|
||||
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/error-ex": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
|
||||
|
|
@ -5772,6 +5845,12 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
|
|
@ -7889,6 +7968,27 @@
|
|||
"typescript": ">=4.8.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-toolbelt": {
|
||||
"version": "9.6.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz",
|
||||
"integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/ts-type": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-type/-/ts-type-3.0.1.tgz",
|
||||
"integrity": "sha512-cleRydCkBGBFQ4KAvLH0ARIkciduS745prkGVVxPGvcRGhMMoSJUB7gNR1ByKhFTEYrYRg2CsMRGYnqp+6op+g==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"tslib": ">=2",
|
||||
"typedarray-dts": "^1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ts-toolbelt": "^9.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tsconfig-paths": {
|
||||
"version": "3.15.0",
|
||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
|
||||
|
|
@ -7934,6 +8034,15 @@
|
|||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/type-detect": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz",
|
||||
"integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/typed-array-buffer": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
|
||||
|
|
@ -8012,6 +8121,12 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/typedarray-dts": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/typedarray-dts/-/typedarray-dts-1.0.0.tgz",
|
||||
"integrity": "sha512-Ka0DBegjuV9IPYFT1h0Qqk5U4pccebNIJCGl8C5uU7xtOs+jpJvKGAY4fHGK25hTmXZOEUl9Cnsg5cS6K/b5DA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
|
|
@ -8073,7 +8188,6 @@
|
|||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unrs-resolver": {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
"@prisma/adapter-pg": "^7.2.0",
|
||||
"@prisma/client": "^7.2.0",
|
||||
"epub": "^1.3.0",
|
||||
"epub2": "^3.0.2",
|
||||
"exif-reader": "^2.0.3",
|
||||
"next": "16.1.1",
|
||||
"next-auth": "^5.0.0-beta.30",
|
||||
|
|
|
|||
|
|
@ -1,30 +1,23 @@
|
|||
// src/lib/metadata-extractor.ts
|
||||
// 1. Update your import to use the new named export
|
||||
//import * as PDFLib from 'pdf-parse';
|
||||
//import { PDFParse } from 'pdf-parse';
|
||||
//import {pdf} from 'pdf-parse';
|
||||
// src/lib/metadata-extractor.ts
|
||||
import * as PdfParse from 'pdf-parse-new';
|
||||
import sharp from 'sharp';
|
||||
import exifReader from 'exif-reader';
|
||||
import EPub from 'epub2'; // New Import
|
||||
|
||||
/**
|
||||
* Converts EXIF DMS (Degrees, Minutes, Seconds) array to Decimal Degrees.
|
||||
* Converts EXIF DMS array to Decimal Degrees.
|
||||
*/
|
||||
function convertDMSToDD(dms: any, ref: string): string {
|
||||
if (!Array.isArray(dms) || dms.length < 3) return String(dms);
|
||||
|
||||
const [degrees, minutes, seconds] = dms;
|
||||
let dd = degrees + (minutes / 60) + (seconds / 3600);
|
||||
|
||||
if (ref === 'S' || ref === 'W') {
|
||||
dd = dd * -1;
|
||||
}
|
||||
if (ref === 'S' || ref === 'W') dd = dd * -1;
|
||||
return dd.toFixed(6);
|
||||
}
|
||||
|
||||
/**
|
||||
* RECURSIVE SANITIZER:
|
||||
* Converts Buffers to strings, standardizes keys, and handles GPS conversion.
|
||||
* RECURSIVE SANITIZER
|
||||
*/
|
||||
function sanitizeMetadata(obj: any): any {
|
||||
if (obj === null || typeof obj !== 'object') return obj;
|
||||
|
|
@ -36,7 +29,6 @@ function sanitizeMetadata(obj: any): any {
|
|||
for (const [key, value] of Object.entries(obj)) {
|
||||
const cleanKey = key.charAt(0).toLowerCase() + key.slice(1);
|
||||
|
||||
// Specifically handle GPS Latitude/Longitude Arrays
|
||||
if (cleanKey === 'gPSLatitude' && obj['gPSLatitudeRef']) {
|
||||
sanitized[cleanKey] = convertDMSToDD(value, obj['gPSLatitudeRef']);
|
||||
continue;
|
||||
|
|
@ -55,24 +47,15 @@ export async function extractMetadata(buffer: Buffer, filename: string): Promise
|
|||
const extension = filename.split('.').pop()?.toLowerCase();
|
||||
|
||||
try {
|
||||
// --- 1. PDF EXTRACTION (pdf-parse-new) ---
|
||||
// --- 1. PDF EXTRACTION ---
|
||||
if (extension === 'pdf') {
|
||||
const parser = new PdfParse.SmartPDFParser({
|
||||
oversaturationFactor: 2.0,
|
||||
enableFastPath: true
|
||||
});
|
||||
|
||||
console.log(`--- PDF Extraction Start: ${filename} ---`);
|
||||
const parser = new PdfParse.SmartPDFParser({ oversaturationFactor: 2.0, enableFastPath: true });
|
||||
const result = await parser.parse(buffer);
|
||||
|
||||
return {
|
||||
type: 'PDF',
|
||||
title: filename,
|
||||
// pageCount is specifically named 'numpages' in this library
|
||||
pageCount: result.numpages || 0,
|
||||
// We sanitize the result.info to handle those nested Custom objects
|
||||
details: sanitizeMetadata(result.info || {}),
|
||||
// Use result.text for a preview if available
|
||||
textPreview: result.text ? result.text.substring(0, 200).replace(/\s+/g, ' ') : ""
|
||||
};
|
||||
}
|
||||
|
|
@ -81,17 +64,13 @@ export async function extractMetadata(buffer: Buffer, filename: string): Promise
|
|||
if (['jpg', 'jpeg', 'png', 'webp'].includes(extension || '')) {
|
||||
const image = sharp(buffer);
|
||||
const metadata = await image.metadata();
|
||||
|
||||
let details = {};
|
||||
if (metadata.exif) {
|
||||
try {
|
||||
const rawExif = exifReader(metadata.exif);
|
||||
details = sanitizeMetadata(rawExif);
|
||||
} catch (e) {
|
||||
console.warn("EXIF Parse failed");
|
||||
}
|
||||
} catch (e) { console.warn("EXIF Parse failed"); }
|
||||
}
|
||||
|
||||
return {
|
||||
type: `IMAGE (${metadata.format?.toUpperCase()})`,
|
||||
dimensions: `${metadata.width}x${metadata.height}`,
|
||||
|
|
@ -100,6 +79,36 @@ export async function extractMetadata(buffer: Buffer, filename: string): Promise
|
|||
};
|
||||
}
|
||||
|
||||
// --- 3. EPUB EXTRACTION (New Section) ---
|
||||
if (extension === 'epub') {
|
||||
return new Promise((resolve, reject) => {
|
||||
// We initialize the EPub instance with a null image path and use the buffer
|
||||
const epub = new EPub(buffer);
|
||||
|
||||
epub.on('error', (err) => {
|
||||
console.error("EPUB Parser Error:", err);
|
||||
resolve({ type: 'EPUB', title: filename, error: "Failed to parse EPUB" });
|
||||
});
|
||||
|
||||
epub.on('end', () => {
|
||||
// Standardizing the metadata for your UI
|
||||
resolve({
|
||||
type: 'EPUB',
|
||||
title: epub.metadata.title || filename,
|
||||
author: epub.metadata.creator || 'Unknown',
|
||||
details: sanitizeMetadata(epub.metadata),
|
||||
// Use the number of chapters/manifest items as a rough "page" guide
|
||||
pageCount: epub.spine.contents.length || 0,
|
||||
textPreview: epub.metadata.description
|
||||
? epub.metadata.description.substring(0, 200).replace(/<[^>]*>?/gm, '')
|
||||
: ""
|
||||
});
|
||||
});
|
||||
|
||||
epub.parse();
|
||||
});
|
||||
}
|
||||
|
||||
return { type: 'FILE', title: filename };
|
||||
} catch (error: any) {
|
||||
console.error(`❌ Extraction failed for ${filename}:`, error.message);
|
||||
|
|
|
|||
Loading…
Reference in a new issue