feat(tools): 2.0 phase 5 wave 5b - ai pool: ocr-pdf, transcription, background composites (5 tools) (#226)

This commit is contained in:
SnapOtter
2026-06-13 10:19:47 +08:00
parent 6e1b9865f1
commit 51666cdd5f
59 changed files with 5423 additions and 611 deletions
+4 -1
View File
@@ -18,10 +18,13 @@ export type { FaceLandmarkPoint, FaceLandmarks, FaceLandmarksResult } from "./fa
export { detectFaceLandmarks } from "./face-landmarks.js";
export { inpaint } from "./inpainting.js";
export { noiseRemoval } from "./noise-removal.js";
export { extractText } from "./ocr.js";
export type { PdfOcrOptions, PdfOcrResult } from "./ocr.js";
export { extractPdfText, extractText } from "./ocr.js";
export type { OutpaintOptions } from "./outpainting.js";
export { outpaint } from "./outpainting.js";
export { removeRedEye } from "./red-eye-removal.js";
export { restorePhoto } from "./restoration.js";
export { seamCarve } from "./seam-carving.js";
export type { TranscribeOptions, TranscriptionResult, TranscriptSegment } from "./transcription.js";
export { transcribeAudio } from "./transcription.js";
export { upscale } from "./upscaling.js";
+45
View File
@@ -53,3 +53,48 @@ export async function extractText(
engine: result.engine,
};
}
// ── PDF OCR ───────────────────────────────────────────────────────────
export interface PdfOcrOptions {
quality?: OcrQuality;
language?: string;
pages?: string;
}
export interface PdfOcrResult {
text: string;
engine: string;
pages: number;
}
export async function extractPdfText(
inputPath: string,
opts: PdfOcrOptions = {},
onProgress?: ProgressCallback,
): Promise<PdfOcrResult> {
const optionsJson = JSON.stringify({
quality: opts.quality ?? "balanced",
language: opts.language ?? "auto",
pages: opts.pages ?? "all",
});
const { stdout } = await runPythonWithProgress("ocr_pdf.py", [inputPath, optionsJson], {
timeout: 30 * 60_000,
onProgress,
});
const result = parseStdoutJson(stdout);
if (result.error) {
throw new Error(result.error);
}
if (!result.success) {
throw new Error(result.error || "PDF OCR failed");
}
return {
text: result.text ?? "",
engine: result.engine ?? "unknown",
pages: result.pages ?? 0,
};
}
+63
View File
@@ -0,0 +1,63 @@
import { type ProgressCallback, parseStdoutJson, runPythonWithProgress } from "./bridge.js";
/**
* A single timed segment of transcribed speech.
* Defined locally (packages/ai cannot depend on apps/api);
* the route layer adapts to the api-side TranscriptSegment shape.
*/
export interface TranscriptSegment {
startS: number;
endS: number;
text: string;
}
export interface TranscriptionResult {
language: string;
text: string;
segments: TranscriptSegment[];
}
export interface TranscribeOptions {
language: string;
}
export async function transcribeAudio(
inputPath: string,
opts: TranscribeOptions,
onProgress?: ProgressCallback,
): Promise<TranscriptionResult> {
const optionsJson = JSON.stringify({
language: opts.language,
task: "transcribe",
});
const { stdout } = await runPythonWithProgress("transcribe.py", [inputPath, optionsJson], {
timeout: 30 * 60_000,
onProgress,
});
const result = parseStdoutJson(stdout);
if (result.error) {
throw new Error(result.error);
}
// Map python segment keys {start, end, text} to {startS, endS, text}.
// Defensive: if segments is missing or not an array, default to [].
const rawSegments: Array<{ start?: number; end?: number; text?: string }> = Array.isArray(
result.segments,
)
? result.segments
: [];
const segments: TranscriptSegment[] = rawSegments.map((seg) => ({
startS: seg.start ?? 0,
endS: seg.end ?? 0,
text: (seg.text ?? "").trim(),
}));
return {
language: result.language ?? "en",
text: result.text ?? "",
segments,
};
}