Adding migrations and lot of work. Starting work on the demo scope
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import { spawn } from 'node:child_process'
|
||||
|
||||
export type TextExtractionResult = {
|
||||
extractor: string
|
||||
extractorVersion: string
|
||||
language: string
|
||||
status: 'succeeded' | 'unsupported' | 'failed'
|
||||
text: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface TextExtractor {
|
||||
provider: string
|
||||
extract(file: { buffer: Buffer; mimetype: string }): Promise<TextExtractionResult>
|
||||
}
|
||||
|
||||
function plainText(file: { buffer: Buffer; mimetype: string }, maximumCharacters: number): TextExtractionResult | null {
|
||||
if (!file.mimetype.startsWith('text/')) return null
|
||||
return { extractor: 'plain-text', extractorVersion: '1', language: 'und', status: 'succeeded', text: file.buffer.toString('utf8').slice(0, maximumCharacters) }
|
||||
}
|
||||
|
||||
function runTesseract(command: string, buffer: Buffer, languages: string, pageSegmentationMode: string, timeoutMs: number) {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const child = spawn(command, ['stdin', 'stdout', '-l', languages, '--psm', pageSegmentationMode], { stdio: ['pipe', 'pipe', 'pipe'] })
|
||||
const stdout: Buffer[] = []
|
||||
const stderr: Buffer[] = []
|
||||
let settled = false
|
||||
const timer = setTimeout(() => {
|
||||
if (settled) return
|
||||
settled = true
|
||||
child.kill('SIGKILL')
|
||||
reject(new Error(`OCR timed out after ${timeoutMs}ms`))
|
||||
}, timeoutMs)
|
||||
child.stdout.on('data', chunk => stdout.push(Buffer.from(chunk)))
|
||||
child.stderr.on('data', chunk => stderr.push(Buffer.from(chunk)))
|
||||
child.once('error', error => {
|
||||
if (settled) return
|
||||
settled = true
|
||||
clearTimeout(timer)
|
||||
reject(error)
|
||||
})
|
||||
child.once('close', code => {
|
||||
if (settled) return
|
||||
settled = true
|
||||
clearTimeout(timer)
|
||||
if (code === 0) resolve(Buffer.concat(stdout).toString('utf8').trim())
|
||||
else reject(new Error(Buffer.concat(stderr).toString('utf8').trim() || `OCR exited with status ${code}`))
|
||||
})
|
||||
child.stdin.end(buffer)
|
||||
})
|
||||
}
|
||||
|
||||
export function createTextExtractorFromEnv(): TextExtractor {
|
||||
const enabled = process.env.OCR_ENABLED !== 'false'
|
||||
const command = process.env.OCR_COMMAND || 'tesseract'
|
||||
const languages = process.env.OCR_LANGUAGES || 'nor+eng'
|
||||
const extractorVersion = process.env.OCR_ENGINE_VERSION || 'tesseract-cli-5'
|
||||
const pageSegmentationMode = process.env.OCR_PAGE_SEGMENTATION_MODE || '3'
|
||||
const timeoutMs = Math.max(1_000, Number(process.env.OCR_TIMEOUT_MS || 20_000))
|
||||
const maximumBytes = Math.max(1, Number(process.env.MAX_OCR_BYTES || 15 * 1024 * 1024))
|
||||
const maximumCharacters = Math.max(1_000, Number(process.env.MAX_EXTRACTED_TEXT_CHARACTERS || 200_000))
|
||||
return {
|
||||
provider: enabled ? 'tesseract' : 'disabled',
|
||||
async extract(file) {
|
||||
const direct = plainText(file, maximumCharacters)
|
||||
if (direct) return direct
|
||||
if (!file.mimetype.startsWith('image/')) return { extractor: 'none', extractorVersion: '1', language: 'und', status: 'unsupported', text: '' }
|
||||
if (!enabled) return { extractor: 'tesseract', extractorVersion, language: languages, status: 'unsupported', text: '' }
|
||||
if (file.buffer.byteLength > maximumBytes) return { extractor: 'tesseract', extractorVersion, language: languages, status: 'failed', text: '', error: `Image exceeds the ${maximumBytes}-byte OCR limit` }
|
||||
try {
|
||||
const text = (await runTesseract(command, file.buffer, languages, pageSegmentationMode, timeoutMs)).slice(0, maximumCharacters)
|
||||
return { extractor: 'tesseract', extractorVersion, language: languages, status: 'succeeded', text }
|
||||
} catch (error) {
|
||||
return { extractor: 'tesseract', extractorVersion, language: languages, status: 'failed', text: '', error: error instanceof Error ? error.message : 'OCR failed' }
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user