Compare commits
2 Commits
14699b5f26
...
6c3786f200
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c3786f200 | |||
| 70f09fa9da |
@@ -47,7 +47,7 @@ interface Args {
|
||||
api: string;
|
||||
limit: number;
|
||||
only: string | null;
|
||||
dpi: number;
|
||||
dpi: number | "auto";
|
||||
maxPages: number;
|
||||
force: boolean;
|
||||
outDir: string;
|
||||
@@ -73,7 +73,10 @@ function parseArgs(): Args {
|
||||
api,
|
||||
limit: parseInt(get("--limit", "0")!, 10) || 0,
|
||||
only: get("--only"),
|
||||
dpi: parseInt(get("--dpi", "150")!, 10) || 150,
|
||||
dpi: ((): number | "auto" => {
|
||||
const v = get("--dpi", "auto")!;
|
||||
return v === "auto" ? "auto" : parseInt(v, 10) || 150;
|
||||
})(),
|
||||
maxPages: parseInt(get("--max-pages", "8")!, 10) || 8,
|
||||
force: a.includes("--force"),
|
||||
outDir: get("--out-dir", path.dirname(input))!,
|
||||
@@ -234,6 +237,19 @@ async function renderPdf(pdfPath: string, dpi: number, maxPages: number, tmpDir:
|
||||
return files;
|
||||
}
|
||||
|
||||
/** Hat das PDF eine nennenswerte Textebene? (Hybrid: Werte getippt → 150 DPI
|
||||
* reicht. Reiner Scan: keine Textebene, Handschrift möglich → 200 DPI.) */
|
||||
async function hasTextLayer(pdfPath: string, maxPages: number): Promise<boolean> {
|
||||
try {
|
||||
const { stdout } = await execFileP("pdftotext", ["-l", String(maxPages), pdfPath, "-"], {
|
||||
timeout: 30_000, maxBuffer: 10 * 1024 * 1024,
|
||||
});
|
||||
return stdout.replace(/\s+/g, "").length > 100;
|
||||
} catch {
|
||||
return false; // im Zweifel als Scan behandeln → hohe Auflösung
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// VLM-Aufruf (llama-server, OpenAI-kompatibel, JSON-Schema)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -319,6 +335,22 @@ async function fetchModelId(api: string): Promise<string> {
|
||||
}
|
||||
}
|
||||
|
||||
/** Wartet nach einem Server-Neustart (503/Verbindungsfehler), bis /health
|
||||
* wieder OK meldet — Modell-Reload dauert 1-2 Minuten. */
|
||||
async function waitForHealthy(api: string, timeoutMs: number): Promise<void> {
|
||||
const healthUrl = api.replace(/\/v1\/?$/, "") + "/health";
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
const r = await fetch(healthUrl, { signal: AbortSignal.timeout(5000) });
|
||||
if (r.ok) return;
|
||||
} catch {
|
||||
/* Server noch weg */
|
||||
}
|
||||
await new Promise((res) => setTimeout(res, 5000));
|
||||
}
|
||||
}
|
||||
|
||||
async function callVision(api: string, images: string[], timeoutMs: number): Promise<VisionRaw> {
|
||||
const content: Array<Record<string, unknown>> = [{ type: "text", text: USER_PROMPT }];
|
||||
for (const img of images) {
|
||||
@@ -481,8 +513,11 @@ async function main(): Promise<void> {
|
||||
|
||||
const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "bvs-"));
|
||||
try {
|
||||
progress(`${tag} … rendere`);
|
||||
const images = await renderPdf(pdfPath, args.dpi, args.maxPages, tmpDir);
|
||||
const dpi = args.dpi === "auto"
|
||||
? ((await hasTextLayer(pdfPath, args.maxPages)) ? 150 : 200)
|
||||
: args.dpi;
|
||||
progress(`${tag} … rendere (${dpi} dpi)`);
|
||||
const images = await renderPdf(pdfPath, dpi, args.maxPages, tmpDir);
|
||||
|
||||
let vis: VisionRaw | null = null;
|
||||
let lastErr = "";
|
||||
@@ -493,7 +528,15 @@ async function main(): Promise<void> {
|
||||
break;
|
||||
} catch (e) {
|
||||
lastErr = e instanceof Error ? e.message : String(e);
|
||||
if (attempt < RETRIES) await new Promise((res) => setTimeout(res, 5000 * attempt));
|
||||
if (attempt < RETRIES) {
|
||||
if (/HTTP 50[23]|fetch failed|aborted|ECONNREFUSED|ECONNRESET/i.test(lastErr)) {
|
||||
// Server crasht/lädt neu → auf /health warten (Modell-Reload dauert)
|
||||
progress(`${tag} … Server neu am Laden, warte auf /health`);
|
||||
await waitForHealthy(args.api, 300_000);
|
||||
} else {
|
||||
await new Promise((res) => setTimeout(res, 5000 * attempt));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user