const fs = require('fs'); const path = require('path'); const { execFile } = require('child_process'); const { promisify } = require('util'); const execFileAsync = promisify(execFile); // ========== 用 pdfinfo 获取页数(保持不变) ========== async function getPDFPageCount(pdfPath) { try { const { stdout } = await execFileAsync('pdfinfo', [pdfPath], { timeout: 10000, maxBuffer: 1024 * 1024 }); const match = stdout.match(/Pages:\s*(\d+)/i); return match ? parseInt(match[1], 10) : 1; } catch (error) { console.error(`[PDF2IMG] pdfinfo failed:`, error.message); return 1; } } // ========== 直接调用 pdftoppm 转换单页 ========== async function convertPdfPageToPng(pdfPath, outputDir, pageNum, dpi = 150) { // 通过 -singlefile + 带页码的基础名,确保输出文件名稳定为 page-.png const expectedOutputPath = path.join(outputDir, `page-${pageNum}.png`); const outputBaseName = path.join(outputDir, `page-${pageNum}`); // 不包含扩展名 try { await execFileAsync('pdftoppm', [ '-png', '-singlefile', '-r', dpi.toString(), '-f', pageNum.toString(), '-l', pageNum.toString(), pdfPath, outputBaseName // 只提供基础名称 ], { timeout: 30000, // 30秒超时 windowsHide: true }); if (fs.existsSync(expectedOutputPath)) { const size = fs.statSync(expectedOutputPath).size; console.log(`[PDF2IMG] Page ${pageNum} saved (${size} bytes): ${expectedOutputPath}`); return expectedOutputPath; } else { throw new Error(`Expected output file not found: ${expectedOutputPath}`); } } catch (error) { console.error(`[PDF2IMG] pdftoppm failed for page ${pageNum}:`, error.message); throw error; } } // ========== 主转换函数 ========== async function convertPdfToImages( pdfPath, outputDir, dpi = 150, startPage = 1, endPage = null ) { const startTime = Date.now(); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } const stats = fs.statSync(pdfPath); console.log(`[PDF2IMG] Input PDF: ${pdfPath} (${(stats.size / 1024 / 1024).toFixed(2)} MB)`); const totalPages = await getPDFPageCount(pdfPath); console.log(`[PDF2IMG] Detected ${totalPages} pages`); const normalizedStartPage = Math.max( 1, Math.min(totalPages, Number(startPage) || 1) ); const normalizedEndPage = Math.max( normalizedStartPage, Math.min(totalPages, Number(endPage) || totalPages) ); const imagePaths = []; console.log( `[PDF2IMG] Converting pages ${normalizedStartPage}-${normalizedEndPage} with DPI=${dpi}...` ); for (let pageNum = normalizedStartPage; pageNum <= normalizedEndPage; pageNum++) { try { const imagePath = await convertPdfPageToPng(pdfPath, outputDir, pageNum, dpi); imagePaths.push(imagePath); } catch (err) { console.warn(`[PDF2IMG] Skipping page ${pageNum} due to error:`, err.message); } } const duration = Date.now() - startTime; const plannedPages = normalizedEndPage - normalizedStartPage + 1; console.log(`[PDF2IMG] ✅ Converted ${imagePaths.length}/${plannedPages} pages in ${duration}ms`); return imagePaths; } module.exports = { convertPdfToImages };