Shell-Scripts/photo-toolkit/recover-exif.js

209 lines
7.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 };