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 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({
|
||||
where: {
|
||||
extractedText: null,
|
||||
sizeBytes: { gte: 2048 },
|
||||
OR: [
|
||||
{ mimeType: { startsWith: 'image/' } },
|
||||
{ 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> {
|
||||
let worker: Tesseract.Worker | null = null;
|
||||
try {
|
||||
worker = await createWorker('eng');
|
||||
const ret = await worker.recognize(imageBufferOrPath);
|
||||
const worker = await getOcrWorker();
|
||||
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() || '';
|
||||
return text.length > 0 ? text.slice(0, 50000) : null;
|
||||
} catch (err) {
|
||||
console.error('Tesseract OCR error:', err);
|
||||
return null;
|
||||
} finally {
|
||||
if (worker) {
|
||||
// If worker errored, terminate and reset so next call re-spawns cleanly
|
||||
if (sharedWorker) {
|
||||
try {
|
||||
await worker.terminate();
|
||||
await sharedWorker.terminate();
|
||||
} catch {}
|
||||
sharedWorker = null;
|
||||
sharedWorkerPromise = null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +117,10 @@ export async function extractTextFromAttachment(
|
||||
lowerName.endsWith('.bmp') ||
|
||||
lowerName.endsWith('.tiff')
|
||||
) {
|
||||
// Skip tiny tracking pixels / spacer icons under 2 KB
|
||||
if (buffer.length < 2048) {
|
||||
return null;
|
||||
}
|
||||
return await recognizeImageOcr(buffer);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user