const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); const { question, normalizeBasename } = require('./utils'); // 转换 PNG 为 JPG function convertPngToJpg(pngFile, targetDir, succeedDir) { const filename = path.basename(pngFile); const baseNoExt = path.basename(pngFile, path.extname(pngFile)); const baseFilename = normalizeBasename(baseNoExt); const jpgFile = path.join(targetDir, `${baseFilename}.jpg`); const succeedJpgFile = path.join(succeedDir, `${baseFilename}.jpg`); // 如果当前目录或 succeed 目录下同名 JPG 文件已存在,则跳过 if (fs.existsSync(jpgFile) || fs.existsSync(succeedJpgFile)) { console.log(`⏭️ ${jpgFile} 或 ${succeedJpgFile} 已存在,跳过 PNG 转换`); return null; } try { execSync(`convert "${pngFile}" -quality 100 "${jpgFile}"`, { stdio: 'inherit' }); console.log(`✅ ${filename} 已转换为 JPG: ${jpgFile}`); return jpgFile; } catch (error) { console.error(`❌ PNG 转换失败: ${filename}`); return null; } } // 查找源文件 function findSourceFile(sourceDir, baseFilename, originalFilename) { // 先尝试精确匹配 const exactMatch = path.join(sourceDir, originalFilename); if (fs.existsSync(exactMatch)) { return exactMatch; } // 尝试常见扩展名 const extensions = ['jpg', 'jpeg', 'JPG', 'JPEG', 'heic', 'heif', 'HIF', 'png', 'PNG', 'tif', 'tiff']; for (const ext of extensions) { const candidate = path.join(sourceDir, `${baseFilename}.${ext}`); if (fs.existsSync(candidate)) { return candidate; } } return null; } // 恢复 EXIF 信息 function recoverExif(targetFile, sourceFile, succeedDir) { const filename = path.basename(targetFile); const ext = path.extname(filename); const baseNoExt = path.basename(filename, ext); let succeedFile = path.join(succeedDir, filename); // 检查 succeed 目录下是否已经存在同名文件,如果存在则添加时间戳 if (fs.existsSync(succeedFile)) { const timestamp = Date.now(); const newFilename = `${baseNoExt}_${timestamp}${ext}`; succeedFile = path.join(succeedDir, newFilename); console.log(`⚠️ ${filename} 在 succeed 目录下已存在,重命名为 ${newFilename}`); } try { // 使用 exiftool 复制 EXIF 信息 execSync( `exiftool -overwrite_original -all= -UserComment= -tagsFromFile "${sourceFile}" "${targetFile}"`, { stdio: 'inherit' } ); // 移动到 succeed 文件夹 fs.renameSync(targetFile, succeedFile); console.log(`✅ 匹配成功:从 ${path.basename(sourceFile)} 复制到 ${path.basename(succeedFile)},已移动到 succeed 文件夹`); return true; } catch (error) { console.error(`❌ EXIF 恢复失败: ${filename}`); return false; } } // 处理大写 JPG 文件(去除 GPS 信息) function processUppercaseJpg(targetFile) { try { execSync( `exiftool -overwrite_original -UserComment= -gps:all= -artist="奇趣保罗" -CreatorTool= "${targetFile}"`, { stdio: 'inherit' } ); return true; } catch (error) { console.error(`❌ 处理失败: ${path.basename(targetFile)}`); return false; } } // EXIF 恢复主函数 async function runRecoverExifTool(rl) { console.log('\n🔧 EXIF 信息恢复工具'); console.log('=' .repeat(50)); // 获取源文件夹(原始 EXIF) const sourceInput = await question(rl, '\n📁 请输入源文件夹路径 (包含原始 EXIF 的图片): '); const sourceDir = sourceInput.trim(); if (!sourceDir || !fs.existsSync(sourceDir)) { console.error(`❌ 源文件夹不存在: ${sourceDir}`); return; } // 获取目标文件夹(被修改的) const targetInput = await question(rl, '📁 请输入目标文件夹路径 (被修改的图片): '); const targetDir = targetInput.trim(); if (!targetDir || !fs.existsSync(targetDir)) { console.error(`❌ 目标文件夹不存在: ${targetDir}`); return; } // 创建 succeed 目录 const succeedDir = path.join(targetDir, 'succeed'); if (!fs.existsSync(succeedDir)) { fs.mkdirSync(succeedDir, { recursive: true }); console.log(`✅ 已创建 succeed 目录: ${succeedDir}`); } console.log('\n🚀 开始处理...\n'); let stats = { pngConverted: 0, exifRecovered: 0, exifFailed: 0, uppercaseProcessed: 0 }; // 第一步:处理 PNG 文件 console.log('📋 步骤 1: 转换 PNG 文件为 JPG\n'); const pngFiles = fs.readdirSync(targetDir).filter(file => { const ext = path.extname(file).toLowerCase(); const fullPath = path.join(targetDir, file); return (ext === '.png') && fs.statSync(fullPath).isFile() && !fullPath.includes('/succeed/'); }); for (const pngFile of pngFiles) { const fullPath = path.join(targetDir, pngFile); const result = convertPngToJpg(fullPath, targetDir, succeedDir); if (result) { stats.pngConverted++; } } // 第二步:恢复 EXIF 信息 console.log('\n📋 步骤 2: 恢复 EXIF 信息\n'); const jpgFiles = fs.readdirSync(targetDir).filter(file => { const ext = path.extname(file).toLowerCase(); const fullPath = path.join(targetDir, file); return (ext === '.jpg' || ext === '.jpeg') && fs.statSync(fullPath).isFile() && !fullPath.includes('/succeed/'); }); for (const jpgFile of jpgFiles) { const fullPath = path.join(targetDir, jpgFile); const filename = path.basename(jpgFile); const baseNoExt = path.basename(jpgFile, path.extname(jpgFile)); const baseFilename = normalizeBasename(baseNoExt); // 查找源文件 const sourceFile = findSourceFile(sourceDir, baseFilename, filename); if (sourceFile) { const result = recoverExif(fullPath, sourceFile, succeedDir); if (result) { stats.exifRecovered++; } else { stats.exifFailed++; } } else { console.log(`⚠️ ${filename} 未找到对应源文件(尝试的基名:${baseFilename}),保留在原位置`); stats.exifFailed++; } } // 第三步:处理大写 JPG 文件 console.log('\n📋 步骤 3: 处理大写 JPG 文件(去除 GPS 信息)\n'); const uppercaseJpgFiles = fs.readdirSync(targetDir).filter(file => { const ext = path.extname(file); const fullPath = path.join(targetDir, file); return ext === '.JPG' && fs.statSync(fullPath).isFile() && !fullPath.includes('/succeed/'); }); for (const jpgFile of uppercaseJpgFiles) { const fullPath = path.join(targetDir, jpgFile); const result = processUppercaseJpg(fullPath); if (result) { stats.uppercaseProcessed++; } } // 显示统计 console.log('\n' + '='.repeat(50)); console.log('📊 处理完成!'); console.log(` 🔄 PNG 转换: ${stats.pngConverted} 个`); console.log(` ✅ EXIF 恢复成功: ${stats.exifRecovered} 个`); console.log(` ❌ EXIF 恢复失败: ${stats.exifFailed} 个`); console.log(` 🔧 大写 JPG 处理: ${stats.uppercaseProcessed} 个`); console.log(` 📁 成功文件目录: ${succeedDir}`); } module.exports = { runRecoverExifTool };