fix: ENOENT download error for archive-only converters (PDFMathTranslate/MinerU)
- Add existsSync checks in download.tsx before file access - Fix normalizeFiletype.ts: md-t/md-i return original format (not tar.gz) - Add outputMode: 'archive' support in main.ts with auto .tar suffix - PDFMathTranslate outputs translated-<lang>.pdf + bilingual-<lang>.pdf (no original) - MinerU outputs complete folder in .tar with logging - Hide preview icon for .tar files in results.tsx - Update Bun version: 1.2.2 -> 1.3.6 - Add Step 19: auto-deploy to remote server after Docker push All 159 tests pass.
This commit is contained in:
parent
1f800c3619
commit
912546aaa7
10 changed files with 190 additions and 63 deletions
|
|
@ -39,6 +39,7 @@ const properties: Record<
|
|||
properties: {
|
||||
from: Record<string, string[]>;
|
||||
to: Record<string, string[]>;
|
||||
outputMode?: "archive";
|
||||
options?: Record<
|
||||
string,
|
||||
Record<
|
||||
|
|
@ -142,7 +143,7 @@ const properties: Record<
|
|||
properties: propertiesMarkitdown,
|
||||
converter: convertMarkitdown,
|
||||
},
|
||||
mineru: {
|
||||
MinerU: {
|
||||
properties: propertiesMineru,
|
||||
converter: convertMineru,
|
||||
},
|
||||
|
|
@ -173,6 +174,10 @@ export async function handleConvert(
|
|||
"INSERT INTO file_names (job_id, file_name, output_file_name, status) VALUES (?1, ?2, ?3, ?4)",
|
||||
);
|
||||
|
||||
// Check if the converter outputs an archive (.tar)
|
||||
const converterProps = properties[converterName]?.properties;
|
||||
const isArchiveOutput = converterProps?.outputMode === "archive";
|
||||
|
||||
for (const chunk of chunks(fileNames, MAX_CONVERT_PROCESS)) {
|
||||
const toProcess: Promise<string>[] = [];
|
||||
for (const fileName of chunk) {
|
||||
|
|
@ -180,11 +185,17 @@ export async function handleConvert(
|
|||
const fileTypeOrig = fileName.split(".").pop() ?? "";
|
||||
const fileType = normalizeFiletype(fileTypeOrig);
|
||||
const newFileExt = normalizeOutputFiletype(convertTo);
|
||||
const newFileName = fileName.replace(
|
||||
let newFileName = fileName.replace(
|
||||
new RegExp(`${fileTypeOrig}(?!.*${fileTypeOrig})`),
|
||||
newFileExt,
|
||||
);
|
||||
const targetPath = `${userOutputDir}${newFileName}`;
|
||||
|
||||
// For archive output converters, the actual file will have .tar extension
|
||||
if (isArchiveOutput) {
|
||||
newFileName = `${newFileName}.tar`;
|
||||
}
|
||||
|
||||
const targetPath = `${userOutputDir}${newFileName.replace(/\.tar$/, "")}`;
|
||||
toProcess.push(
|
||||
new Promise((resolve, reject) => {
|
||||
mainConverter(filePath, fileType, convertTo, targetPath, {}, converterName)
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ export async function convert(
|
|||
// Create .tar archive from the output directory (不使用壓縮)
|
||||
// 強制使用 .tar 格式,禁止 .tar.gz
|
||||
const tarPath = getArchiveFileName(targetPath);
|
||||
console.log(`[MinerU] Target tar path: ${tarPath}`);
|
||||
|
||||
// Ensure the parent directory exists
|
||||
const tarDir = dirname(tarPath);
|
||||
|
|
@ -121,11 +122,21 @@ export async function convert(
|
|||
}
|
||||
|
||||
// Use the actual MinerU output directory for archiving
|
||||
// MinerU 產生完整資料夾結構,全部封裝進 .tar
|
||||
const outputToArchive = existsSync(mineruActualOutput)
|
||||
? mineruActualOutput
|
||||
: mineruOutputDir;
|
||||
|
||||
console.log(`[MinerU] Archiving directory: ${outputToArchive}`);
|
||||
|
||||
// 列出要封裝的內容
|
||||
if (existsSync(outputToArchive)) {
|
||||
const contents = readdirSync(outputToArchive);
|
||||
console.log(`[MinerU] Archive contents: ${contents.join(", ")}`);
|
||||
}
|
||||
|
||||
await createTarArchive(outputToArchive, tarPath, execFile);
|
||||
console.log(`[MinerU] Created archive: ${tarPath}`);
|
||||
|
||||
// Clean up the temporary directory
|
||||
removeDir(mineruOutputDir);
|
||||
|
|
|
|||
|
|
@ -261,49 +261,75 @@ export async function convert(
|
|||
// 5. 執行 pdf2zh 翻譯
|
||||
const { monoPath, dualPath } = await runPdf2zh(filePath, tempDir, targetLang, execFile);
|
||||
|
||||
// 6. 複製原始檔案到封裝目錄
|
||||
const originalDest = join(archiveDir, "original.pdf");
|
||||
copyFileSync(filePath, originalDest);
|
||||
|
||||
// 7. 複製翻譯後的檔案(優先使用 mono,因為它是純翻譯版本)
|
||||
// 6. 複製翻譯後的檔案到封裝目錄
|
||||
// PDFMathTranslate 輸出:
|
||||
// - mono: 純翻譯版本(translated-<lang>.pdf)
|
||||
// - dual: 雙語對照版本(bilingual-<lang>.pdf)
|
||||
// ⚠️ 不包含原始 PDF,只包含翻譯結果
|
||||
|
||||
const translatedDest = join(archiveDir, `translated-${targetLang}.pdf`);
|
||||
const bilingualDest = join(archiveDir, `bilingual-${targetLang}.pdf`);
|
||||
|
||||
// 檢查各種可能的輸出檔案名稱
|
||||
const possibleOutputs = [
|
||||
// 檢查各種可能的 mono 輸出檔案名稱
|
||||
const possibleMonoOutputs = [
|
||||
monoPath,
|
||||
dualPath,
|
||||
join(tempDir, `${inputFileName}-mono.pdf`),
|
||||
];
|
||||
|
||||
// 檢查各種可能的 dual 輸出檔案名稱
|
||||
const possibleDualOutputs = [
|
||||
dualPath,
|
||||
join(tempDir, `${inputFileName}-dual.pdf`),
|
||||
];
|
||||
|
||||
let foundOutput = false;
|
||||
for (const outputPath of possibleOutputs) {
|
||||
let foundMono = false;
|
||||
let foundDual = false;
|
||||
|
||||
// 複製 mono(翻譯版)
|
||||
for (const outputPath of possibleMonoOutputs) {
|
||||
if (existsSync(outputPath)) {
|
||||
copyFileSync(outputPath, translatedDest);
|
||||
foundOutput = true;
|
||||
console.log(`[PDFMathTranslate] Using output: ${outputPath}`);
|
||||
foundMono = true;
|
||||
console.log(`[PDFMathTranslate] Copied mono (translated): ${outputPath}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 複製 dual(對照版)
|
||||
for (const outputPath of possibleDualOutputs) {
|
||||
if (existsSync(outputPath)) {
|
||||
copyFileSync(outputPath, bilingualDest);
|
||||
foundDual = true;
|
||||
console.log(`[PDFMathTranslate] Copied dual (bilingual): ${outputPath}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果找不到預期的輸出檔案,嘗試找任何 PDF
|
||||
if (!foundOutput) {
|
||||
if (!foundMono && !foundDual) {
|
||||
const allFiles = readdirSync(tempDir);
|
||||
const pdfFiles = allFiles.filter((f) => f.endsWith(".pdf") && f !== basename(filePath));
|
||||
const firstPdfName = pdfFiles[0];
|
||||
|
||||
if (firstPdfName) {
|
||||
const firstPdf = join(tempDir, firstPdfName);
|
||||
copyFileSync(firstPdf, translatedDest);
|
||||
foundOutput = true;
|
||||
console.log(`[PDFMathTranslate] Using fallback output: ${firstPdf}`);
|
||||
|
||||
for (const pdfName of pdfFiles) {
|
||||
const pdfPath = join(tempDir, pdfName);
|
||||
if (pdfName.includes("mono") || pdfName.includes("translated")) {
|
||||
copyFileSync(pdfPath, translatedDest);
|
||||
foundMono = true;
|
||||
console.log(`[PDFMathTranslate] Using fallback mono: ${pdfPath}`);
|
||||
} else if (pdfName.includes("dual") || pdfName.includes("bilingual")) {
|
||||
copyFileSync(pdfPath, bilingualDest);
|
||||
foundDual = true;
|
||||
console.log(`[PDFMathTranslate] Using fallback dual: ${pdfPath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundOutput) {
|
||||
throw new Error("No translated PDF output found");
|
||||
if (!foundMono && !foundDual) {
|
||||
throw new Error("No translated PDF output found (neither mono nor dual)");
|
||||
}
|
||||
|
||||
console.log(`[PDFMathTranslate] Archive contents: mono=${foundMono}, dual=${foundDual}`);
|
||||
|
||||
// 8. 建立 .tar 封裝
|
||||
const tarPath = getArchiveFileName(targetPath);
|
||||
const tarDir = dirname(tarPath);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue