feat: 重构 NodeJS 版本图片处理工具
This commit is contained in:
parent
0922035a4c
commit
d277a6118f
|
|
@ -0,0 +1,149 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
const {
|
||||
question,
|
||||
getImageDimensions,
|
||||
detectImageType,
|
||||
getTargetSize,
|
||||
getOutputExtension,
|
||||
IMAGE_TYPES,
|
||||
IMAGE_TYPE_LABELS,
|
||||
} = require('./utils');
|
||||
|
||||
// 处理单个图片
|
||||
function processImage(inputPath, outputPath) {
|
||||
const filename = path.basename(inputPath);
|
||||
const dimensions = getImageDimensions(inputPath);
|
||||
const { type, reason } = detectImageType(inputPath, dimensions);
|
||||
const outputExt = getOutputExtension(type);
|
||||
const newFilename = path.basename(outputPath, path.extname(outputPath)) + outputExt;
|
||||
const finalOutputPath = path.join(path.dirname(outputPath), newFilename);
|
||||
|
||||
if (fs.existsSync(finalOutputPath)) {
|
||||
console.log(`⏭️ 跳过已存在的文件: ${newFilename}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log(`\n📸 处理: ${filename}`);
|
||||
console.log(` 类型: ${IMAGE_TYPE_LABELS[type]} (${reason})`);
|
||||
console.log(` 尺寸: ${dimensions.width}x${dimensions.height}`);
|
||||
|
||||
try {
|
||||
if (type === IMAGE_TYPES.SCREENSHOT) {
|
||||
execSync(
|
||||
`magick "${inputPath}" -gravity center -quality 80 "${finalOutputPath}"`,
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
} else {
|
||||
const newSize = getTargetSize(type, dimensions.width, dimensions.height);
|
||||
console.log(` 目标尺寸: ${newSize}`);
|
||||
execSync(
|
||||
`magick "${inputPath}" -gravity center -resize "${newSize}^" -extent "${newSize}" -quality 80 "${finalOutputPath}"`,
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
}
|
||||
console.log(` ✅ 图片转换完成 → ${outputExt}`);
|
||||
} catch (error) {
|
||||
console.error(` ❌ 图片转换失败`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
try {
|
||||
execSync(
|
||||
`exiftool -overwrite_original -gps:all= -artist="奇趣保罗" -CreatorTool= "${finalOutputPath}"`,
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
console.log(` ✅ 元数据处理完成`);
|
||||
} catch (error) {
|
||||
console.error(` ❌ 元数据处理失败`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 格式转换主函数
|
||||
async function runFormatTool(rl) {
|
||||
console.log('\n🎨 图片格式转换工具');
|
||||
console.log('='.repeat(50));
|
||||
console.log('支持类型: iPhone (4:3 JPG) · 索尼相机 (3:2 JPG) · 截图 (WebP)');
|
||||
|
||||
const inputDir = await question(rl, '\n📁 请输入图片所在目录 (留空使用当前目录): ');
|
||||
const sourceDir = inputDir.trim() || process.cwd();
|
||||
|
||||
if (!fs.existsSync(sourceDir)) {
|
||||
console.error(`❌ 目录不存在: ${sourceDir}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const outputDir = await question(rl, '📁 请输入输出目录 (留空使用 output): ');
|
||||
const targetDir = outputDir.trim() || path.join(sourceDir, 'output');
|
||||
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
fs.mkdirSync(targetDir, { recursive: true });
|
||||
console.log(`✅ 已创建输出目录: ${targetDir}`);
|
||||
}
|
||||
|
||||
const extensions = ['.heic', '.heif', '.png', '.jpg', '.jpeg', '.HEIC', '.HEIF', '.PNG', '.JPG', '.JPEG'];
|
||||
const files = fs.readdirSync(sourceDir).filter(file => {
|
||||
const ext = path.extname(file);
|
||||
const fullPath = path.join(sourceDir, file);
|
||||
const isFile = fs.statSync(fullPath).isFile();
|
||||
const hasValidExt = extensions.includes(ext);
|
||||
const notModified = !file.includes('MODIFIED') && !file.includes('MERGED');
|
||||
return isFile && hasValidExt && notModified;
|
||||
});
|
||||
|
||||
if (files.length === 0) {
|
||||
console.log('\n⚠️ 未找到符合条件的图片文件');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`\n📋 找到 ${files.length} 个文件:`);
|
||||
files.forEach((file, index) => {
|
||||
const inputPath = path.join(sourceDir, file);
|
||||
const dimensions = getImageDimensions(inputPath);
|
||||
const { type, reason } = detectImageType(inputPath, dimensions);
|
||||
console.log(` ${index + 1}. ${file} → ${IMAGE_TYPE_LABELS[type]} (${reason})`);
|
||||
});
|
||||
|
||||
const confirm = await question(rl, '\n❓ 是否开始处理? (y/n): ');
|
||||
if (confirm.toLowerCase() !== 'y' && confirm.toLowerCase() !== 'yes') {
|
||||
console.log('❌ 已取消');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('\n🚀 开始处理...\n');
|
||||
let processed = 0;
|
||||
let skipped = 0;
|
||||
let failed = 0;
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
const inputPath = path.join(sourceDir, file);
|
||||
const outputPath = path.join(targetDir, file);
|
||||
|
||||
console.log(`\n[${i + 1}/${files.length}]`);
|
||||
try {
|
||||
const result = processImage(inputPath, outputPath);
|
||||
if (result) {
|
||||
processed++;
|
||||
} else {
|
||||
skipped++;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ 处理失败: ${file}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(50));
|
||||
console.log('📊 处理完成!');
|
||||
console.log(` ✅ 成功: ${processed} 个`);
|
||||
console.log(` ⏭️ 跳过: ${skipped} 个`);
|
||||
console.log(` ❌ 失败: ${failed} 个`);
|
||||
console.log(` 📁 输出目录: ${targetDir}`);
|
||||
}
|
||||
|
||||
module.exports = { runFormatTool };
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const { createInterface, showMenu } = require('./utils');
|
||||
const { runFormatTool } = require('./format');
|
||||
const { runRecoverExifTool } = require('./recover-exif');
|
||||
|
||||
async function main() {
|
||||
const rl = createInterface();
|
||||
|
||||
console.log('\n🎨 图片工具箱');
|
||||
console.log('=' .repeat(50));
|
||||
console.log('欢迎使用图片处理工具集合!\n');
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const options = [
|
||||
'📸 图片格式转换 (压缩并调整尺寸)',
|
||||
'🔧 EXIF 信息恢复 (从原始图片恢复元数据)',
|
||||
'❌ 退出'
|
||||
];
|
||||
|
||||
const choice = await showMenu(rl, '\n请选择功能', options);
|
||||
|
||||
if (choice === -1) {
|
||||
console.log('❌ 无效的选择,请重新输入');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (choice === 0) {
|
||||
// 图片格式转换
|
||||
await runFormatTool(rl);
|
||||
} else if (choice === 1) {
|
||||
// EXIF 信息恢复
|
||||
await runRecoverExifTool(rl);
|
||||
} else if (choice === 2) {
|
||||
// 退出
|
||||
console.log('\n👋 再见!');
|
||||
break;
|
||||
}
|
||||
|
||||
// 询问是否继续
|
||||
const continueChoice = await new Promise((resolve) => {
|
||||
rl.question('\n是否继续使用其他功能? (y/n): ', resolve);
|
||||
});
|
||||
|
||||
if (continueChoice.toLowerCase() !== 'y' && continueChoice.toLowerCase() !== 'yes') {
|
||||
console.log('\n👋 再见!');
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('\n❌ 发生错误:', error.message);
|
||||
} finally {
|
||||
rl.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 运行主程序
|
||||
main().catch(error => {
|
||||
console.error('❌ 程序异常:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
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 };
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
const readline = require('readline');
|
||||
|
||||
const IMAGE_TYPES = {
|
||||
IPHONE: 'iphone',
|
||||
SONY: 'sony',
|
||||
SCREENSHOT: 'screenshot',
|
||||
};
|
||||
|
||||
const IMAGE_TYPE_LABELS = {
|
||||
[IMAGE_TYPES.IPHONE]: '📱 iPhone (4:3)',
|
||||
[IMAGE_TYPES.SONY]: '📷 索尼相机 (3:2)',
|
||||
[IMAGE_TYPES.SCREENSHOT]: '🖼️ 截图 → WebP',
|
||||
};
|
||||
|
||||
// 创建 readline 接口
|
||||
function createInterface() {
|
||||
return readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
}
|
||||
|
||||
// 提问函数
|
||||
function question(rl, prompt) {
|
||||
return new Promise((resolve) => {
|
||||
rl.question(prompt, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
// 获取图片尺寸
|
||||
function getImageDimensions(filePath) {
|
||||
try {
|
||||
const width = execSync(`identify -format "%w" "${filePath}"`, { encoding: 'utf-8' }).trim();
|
||||
const height = execSync(`identify -format "%h" "${filePath}"`, { encoding: 'utf-8' }).trim();
|
||||
return { width: parseInt(width), height: parseInt(height) };
|
||||
} catch (error) {
|
||||
console.error(`获取图片尺寸失败: ${filePath}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 读取 EXIF 中的 Make / Model
|
||||
function getExifMakeModel(filePath) {
|
||||
try {
|
||||
const output = execSync(`exiftool -Make -Model -s3 "${filePath}"`, { encoding: 'utf-8' }).trim();
|
||||
const [make = '', model = ''] = output.split('\n');
|
||||
return { make: make.trim(), model: model.trim() };
|
||||
} catch {
|
||||
return { make: '', model: '' };
|
||||
}
|
||||
}
|
||||
|
||||
// 根据宽高比推断 iPhone (4:3) 或索尼 (3:2)
|
||||
function detectTypeFromAspectRatio(width, height) {
|
||||
const ratio = Math.max(width, height) / Math.min(width, height);
|
||||
const diff43 = Math.abs(ratio - 4 / 3);
|
||||
const diff32 = Math.abs(ratio - 3 / 2);
|
||||
return diff43 <= diff32 ? IMAGE_TYPES.IPHONE : IMAGE_TYPES.SONY;
|
||||
}
|
||||
|
||||
// 检测图片类型:iPhone / 索尼相机 / 截图
|
||||
function detectImageType(filePath, dimensions) {
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
|
||||
if (ext === '.png') {
|
||||
return { type: IMAGE_TYPES.SCREENSHOT, reason: 'PNG 格式' };
|
||||
}
|
||||
|
||||
const { make, model } = getExifMakeModel(filePath);
|
||||
const makeUpper = make.toUpperCase();
|
||||
const modelUpper = model.toUpperCase();
|
||||
|
||||
if (makeUpper === 'APPLE' || modelUpper.includes('IPHONE')) {
|
||||
return { type: IMAGE_TYPES.IPHONE, reason: `EXIF: ${make} ${model}`.trim() };
|
||||
}
|
||||
|
||||
if (makeUpper.includes('SONY') || modelUpper.includes('ILCE') || modelUpper.startsWith('DSC-')) {
|
||||
return { type: IMAGE_TYPES.SONY, reason: `EXIF: ${make} ${model}`.trim() };
|
||||
}
|
||||
|
||||
if (['.heic', '.heif'].includes(ext)) {
|
||||
return { type: IMAGE_TYPES.IPHONE, reason: 'HEIC/HEIF 格式' };
|
||||
}
|
||||
|
||||
const { width, height } = dimensions;
|
||||
const type = detectTypeFromAspectRatio(width, height);
|
||||
return { type, reason: `宽高比推断 (${width}x${height})` };
|
||||
}
|
||||
|
||||
// 根据类型和方向获取目标尺寸,截图为 null(不裁剪)
|
||||
function getTargetSize(type, width, height) {
|
||||
const landscape = width >= height;
|
||||
|
||||
switch (type) {
|
||||
case IMAGE_TYPES.IPHONE:
|
||||
return landscape ? '2000x1500' : '1500x2000';
|
||||
case IMAGE_TYPES.SONY:
|
||||
return landscape ? '2250x1500' : '1500x2250';
|
||||
case IMAGE_TYPES.SCREENSHOT:
|
||||
return null;
|
||||
default:
|
||||
return landscape ? '2250x1500' : '1500x2250';
|
||||
}
|
||||
}
|
||||
|
||||
// 获取输出扩展名
|
||||
function getOutputExtension(type) {
|
||||
return type === IMAGE_TYPES.SCREENSHOT ? '.webp' : '.jpg';
|
||||
}
|
||||
|
||||
// 规范化文件名(去掉 -Modified 或 _Modified 后缀)
|
||||
function normalizeBasename(name) {
|
||||
// 匹配 -Modified 或 _Modified(不区分大小写)
|
||||
const match = name.match(/^(.*?)[-_]?modified$/i);
|
||||
if (match && match[1]) {
|
||||
return match[1];
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
// 显示菜单并获取选择
|
||||
async function showMenu(rl, title, options) {
|
||||
console.log(`\n${title}`);
|
||||
console.log('='.repeat(50));
|
||||
options.forEach((option, index) => {
|
||||
console.log(`${index + 1}. ${option}`);
|
||||
});
|
||||
|
||||
const choice = await question(rl, '\n请选择 (输入数字): ');
|
||||
const index = parseInt(choice) - 1;
|
||||
|
||||
if (index >= 0 && index < options.length) {
|
||||
return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createInterface,
|
||||
question,
|
||||
getImageDimensions,
|
||||
getExifMakeModel,
|
||||
detectImageType,
|
||||
detectTypeFromAspectRatio,
|
||||
getTargetSize,
|
||||
getOutputExtension,
|
||||
normalizeBasename,
|
||||
showMenu,
|
||||
IMAGE_TYPES,
|
||||
IMAGE_TYPE_LABELS,
|
||||
};
|
||||
Loading…
Reference in New Issue