209 lines
7.0 KiB
JavaScript
209 lines
7.0 KiB
JavaScript
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 };
|