fix: 簡化 MinerU 和 PDFMathTranslate 的程式碼,改善錯誤處理與參數傳遞

This commit is contained in:
Your Name 2026-01-22 22:27:40 +08:00
parent 566ee48bfe
commit 89c121bdf9
4 changed files with 36 additions and 26 deletions

View file

@ -159,9 +159,7 @@ export async function convert(
// Use the actual MinerU output directory for archiving
// MinerU 產生完整資料夾結構,全部封裝進 .tar
const outputToArchive = existsSync(mineruActualOutput)
? mineruActualOutput
: mineruOutputDir;
const outputToArchive = existsSync(mineruActualOutput) ? mineruActualOutput : mineruOutputDir;
console.log(`[MinerU] Archiving directory: ${outputToArchive}`);

View file

@ -2,6 +2,7 @@ import { execFile as execFileOriginal } from "node:child_process";
import { mkdirSync, existsSync, readdirSync, unlinkSync, rmdirSync, copyFileSync } from "node:fs";
import { join, basename, dirname } from "node:path";
import { getArchiveFileName } from "../transfer";
import type { ExecFileFn } from "./types";
// 翻譯服務優先順序(自動 fallback
const TRANSLATION_SERVICES = ["google", "bing"] as const;
@ -91,12 +92,12 @@ function normalizeLanguageCode(lang: string): string {
// 繁體中文pdf2zh 使用 "zh-TW" 或 "zh-Hant"
"zh-tw": "zh-TW",
"zh-hant": "zh-TW",
"zht": "zh-TW",
zht: "zh-TW",
// 簡體中文
"zh-cn": "zh-CN",
"zh-hans": "zh-CN",
"zhs": "zh-CN",
"zh": "zh-CN",
zhs: "zh-CN",
zh: "zh-CN",
// 其他語言保持原樣
};
@ -126,12 +127,13 @@ function checkModelsExist(): boolean {
function createTarArchive(
sourceDir: string,
outputTar: string,
execFile: ExecFileFn = execFileOriginal,
): Promise<void> {
return new Promise((resolve, reject) => {
// Use tar command to create archive (without gzip compression)
// tar -cf <output.tar> -C <sourceDir> .
// 注意:使用 -cf 而非 -czf避免 gzip 壓縮
execFileOriginal("tar", ["-cf", outputTar, "-C", sourceDir, "."], (error, stdout, stderr) => {
execFile("tar", ["-cf", outputTar, "-C", sourceDir, "."], (error, stdout, stderr) => {
if (error) {
reject(`tar error: ${error}`);
return;
@ -172,12 +174,14 @@ function removeDir(dirPath: string): void {
* @param outputDir
* @param targetLang
* @param service google, bing
* @param execFile execFile
*/
function runPdf2zhWithService(
inputPath: string,
outputDir: string,
targetLang: string,
service: TranslationService,
execFile: ExecFileFn = execFileOriginal,
): Promise<{ monoPath: string; dualPath: string }> {
return new Promise((resolve, reject) => {
// pdf2zh CLI 參數:
@ -189,15 +193,7 @@ function runPdf2zhWithService(
// - <filename>-mono.pdf: 純翻譯版本
// - <filename>-dual.pdf: 雙語對照版本
const args = [
inputPath,
"-lo",
targetLang,
"-o",
outputDir,
"-s",
service,
];
const args = [inputPath, "-lo", targetLang, "-o", outputDir, "-s", service];
// 如果設定了自訂模型路徑,使用 --onnx 參數
if (existsSync(MODELS_PATH)) {
@ -209,8 +205,8 @@ function runPdf2zhWithService(
console.log(`[PDFMathTranslate] Running: pdf2zh ${args.join(" ")} (service: ${service})`);
// 使用原生 execFileOriginal 以支援 timeout 選項
execFileOriginal("pdf2zh", args, { timeout: 300000, maxBuffer: 50 * 1024 * 1024 }, (error, stdout, stderr) => {
// 使用注入的 execFile 函數
execFile("pdf2zh", args, (error, stdout, stderr) => {
if (error) {
reject(new Error(`pdf2zh error (${service}): ${error}\nstderr: ${stderr}`));
return;
@ -258,17 +254,25 @@ function runPdf2zhWithService(
* @param inputPath PDF
* @param outputDir
* @param targetLang
* @param execFile execFile
*/
async function runPdf2zh(
inputPath: string,
outputDir: string,
targetLang: string,
execFile: ExecFileFn = execFileOriginal,
): Promise<{ monoPath: string; dualPath: string }> {
// 如果使用者指定了服務,只用那個服務
const envService = process.env.PDFMATHTRANSLATE_SERVICE;
if (envService) {
console.log(`[PDFMathTranslate] Using user-specified service: ${envService}`);
return runPdf2zhWithService(inputPath, outputDir, targetLang, envService as TranslationService);
return runPdf2zhWithService(
inputPath,
outputDir,
targetLang,
envService as TranslationService,
execFile,
);
}
// 自動 fallback依序嘗試各個服務
@ -277,7 +281,13 @@ async function runPdf2zh(
for (const service of TRANSLATION_SERVICES) {
try {
console.log(`[PDFMathTranslate] Trying translation service: ${service}`);
const result = await runPdf2zhWithService(inputPath, outputDir, targetLang, service);
const result = await runPdf2zhWithService(
inputPath,
outputDir,
targetLang,
service,
execFile,
);
console.log(`[PDFMathTranslate] ✅ Success with service: ${service}`);
return result;
} catch (error) {
@ -311,6 +321,7 @@ async function runPdf2zh(
* @param convertTo "pdf-zh"
* @param targetPath
* @param options
* @param execFile execFile
*/
export async function convert(
filePath: string,
@ -318,6 +329,7 @@ export async function convert(
convertTo: string,
targetPath: string,
_options?: unknown,
execFile: ExecFileFn = execFileOriginal,
): Promise<string> {
try {
// 1. 檢查模型(警告但不阻止,因為 pdf2zh 可能會自動下載)
@ -342,7 +354,7 @@ export async function convert(
mkdirSync(archiveDir, { recursive: true });
// 5. 執行 pdf2zh 翻譯
const { monoPath, dualPath } = await runPdf2zh(filePath, tempDir, targetLang);
const { monoPath, dualPath } = await runPdf2zh(filePath, tempDir, targetLang, execFile);
// 6. 複製翻譯後的檔案到封裝目錄
// PDFMathTranslate 輸出:
@ -414,7 +426,7 @@ export async function convert(
mkdirSync(tarDir, { recursive: true });
}
await createTarArchive(archiveDir, tarPath);
await createTarArchive(archiveDir, tarPath, execFile);
console.log(`[PDFMathTranslate] Created archive: ${tarPath}`);
// 9. 清理臨時目錄

View file

@ -203,9 +203,9 @@ describe("MinerU converter .tar output (no compression)", () => {
};
const targetPath = join(testDir, "output.tar");
expect(convert("test.pdf", "pdf", "md-t", targetPath, undefined, mockExecFile)).rejects.toMatch(
/mineru error/,
);
await expect(
convert("test.pdf", "pdf", "md-t", targetPath, undefined, mockExecFile),
).rejects.toThrow(/mineru error/);
});
test("should use correct tar arguments without compression", async () => {

View file

@ -85,7 +85,7 @@ describe("PDFMathTranslate converter - Chinese translation", () => {
expect(pdf2zhCalled).toBe(true);
expect(pdf2zhArgs).toContain("-lo");
expect(pdf2zhArgs).toContain("zh");
expect(pdf2zhArgs).toContain("zh-CN"); // zh is normalized to zh-CN
expect(pdf2zhArgs).toContain("-o");
expect(pdf2zhArgs).toContain("-s"); // Translation service
});