Fix Tesseract workerPath in Next.js standalone container, pool worker, and add timeout
This commit is contained in:
1 parent
99522b3e09
commit
739cbce042
2 files changed
+49
-9
No files matched your search
@@ -7,10 +7,11 @@ export async function POST(req: NextRequest) {
|
|||||||
const body = await req.json().catch(() => ({}));
|
const body = await req.json().catch(() => ({}));
|
||||||
const limit = Math.min(parseInt(String(body.limit || '10'), 10) || 10, 50);
|
const limit = Math.min(parseInt(String(body.limit || '10'), 10) || 10, 50);
|
||||||
|
|
||||||
// Find image or PDF attachments that do not have extractedText yet
|
// Find image or PDF attachments that do not have extractedText yet (skip tiny icons < 2 KB)
|
||||||
const pendingAttachments = await prisma.attachment.findMany({
|
const pendingAttachments = await prisma.attachment.findMany({
|
||||||
where: {
|
where: {
|
||||||
extractedText: null,
|
extractedText: null,
|
||||||
|
sizeBytes: { gte: 2048 },
|
||||||
OR: [
|
OR: [
|
||||||
{ mimeType: { startsWith: 'image/' } },
|
{ mimeType: { startsWith: 'image/' } },
|
||||||
{ mimeType: 'application/pdf' },
|
{ mimeType: 'application/pdf' },
|
||||||
|
|||||||
+47
-8
@@ -20,25 +20,60 @@ async function extractPdfText(buffer: Buffer): Promise<string | null> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let sharedWorker: Tesseract.Worker | null = null;
|
||||||
|
let sharedWorkerPromise: Promise<Tesseract.Worker> | null = null;
|
||||||
|
|
||||||
|
async function getOcrWorker(): Promise<Tesseract.Worker> {
|
||||||
|
if (sharedWorker) return sharedWorker;
|
||||||
|
if (!sharedWorkerPromise) {
|
||||||
|
let workerPath: string | undefined;
|
||||||
|
try {
|
||||||
|
workerPath = require.resolve('tesseract.js/src/worker-script/node/index.js');
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
const options: Record<string, unknown> = {};
|
||||||
|
if (workerPath) {
|
||||||
|
options.workerPath = workerPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedWorkerPromise = createWorker('eng', 1, options)
|
||||||
|
.then((w) => {
|
||||||
|
sharedWorker = w;
|
||||||
|
return w;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
sharedWorkerPromise = null;
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return sharedWorkerPromise;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recognizes text inside an image using Tesseract OCR.
|
* Recognizes text inside an image using Tesseract OCR with pooled worker and timeout.
|
||||||
*/
|
*/
|
||||||
export async function recognizeImageOcr(imageBufferOrPath: Buffer | string): Promise<string | null> {
|
export async function recognizeImageOcr(imageBufferOrPath: Buffer | string): Promise<string | null> {
|
||||||
let worker: Tesseract.Worker | null = null;
|
|
||||||
try {
|
try {
|
||||||
worker = await createWorker('eng');
|
const worker = await getOcrWorker();
|
||||||
const ret = await worker.recognize(imageBufferOrPath);
|
const ret = await Promise.race([
|
||||||
|
worker.recognize(imageBufferOrPath),
|
||||||
|
new Promise<never>((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error('OCR recognition timeout (30s)')), 30000)
|
||||||
|
),
|
||||||
|
]);
|
||||||
const text = ret.data?.text?.trim() || '';
|
const text = ret.data?.text?.trim() || '';
|
||||||
return text.length > 0 ? text.slice(0, 50000) : null;
|
return text.length > 0 ? text.slice(0, 50000) : null;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Tesseract OCR error:', err);
|
console.error('Tesseract OCR error:', err);
|
||||||
return null;
|
// If worker errored, terminate and reset so next call re-spawns cleanly
|
||||||
} finally {
|
if (sharedWorker) {
|
||||||
if (worker) {
|
|
||||||
try {
|
try {
|
||||||
await worker.terminate();
|
await sharedWorker.terminate();
|
||||||
} catch {}
|
} catch {}
|
||||||
|
sharedWorker = null;
|
||||||
|
sharedWorkerPromise = null;
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,6 +117,10 @@ export async function extractTextFromAttachment(
|
|||||||
lowerName.endsWith('.bmp') ||
|
lowerName.endsWith('.bmp') ||
|
||||||
lowerName.endsWith('.tiff')
|
lowerName.endsWith('.tiff')
|
||||||
) {
|
) {
|
||||||
|
// Skip tiny tracking pixels / spacer icons under 2 KB
|
||||||
|
if (buffer.length < 2048) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return await recognizeImageOcr(buffer);
|
return await recognizeImageOcr(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user