Files
Shell-Scripts/photo-toolkit/utils.js
T

165 lines
4.7 KiB
JavaScript

const path = require('path');
const { execSync } = require('child_process');
const readline = require('readline');
const IMAGE_TYPES = {
IPHONE: 'iphone',
SONY: 'sony',
XIAOMI: 'xiaomi',
SCREENSHOT: 'screenshot',
};
const IMAGE_TYPE_LABELS = {
[IMAGE_TYPES.IPHONE]: '📱 iPhone (4:3)',
[IMAGE_TYPES.SONY]: '📷 索尼相机 (3:2)',
[IMAGE_TYPES.XIAOMI]: '📱 小米手机 (4:3)',
[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();
// 我的 iPhone
if (makeUpper === 'APPLE' || modelUpper.includes('IPHONE')) {
return { type: IMAGE_TYPES.IPHONE, reason: `EXIF: ${make} ${model}`.trim() };
}
// 老妈小米 11 青春版
if (makeUpper === 'XIAOMI' || modelUpper.includes('M2101K9C')) {
return { type: IMAGE_TYPES.XIAOMI, 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() };
}
// iPhone
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(不裁剪)
// 索尼返回 ImageMagick 缩放规格(如 2250x),按原图宽高比缩放、不裁剪
function getTargetSize(type, width, height) {
const landscape = width >= height;
switch (type) {
case IMAGE_TYPES.IPHONE:
return { size: landscape ? '2000x1500' : '1500x2000', crop: true };
case IMAGE_TYPES.SONY:
return { size: landscape ? '2250x' : 'x2250', crop: false };
case IMAGE_TYPES.SCREENSHOT:
return null;
default:
return { size: landscape ? '2250x1500' : '1500x2250', crop: true };
}
}
// 获取输出扩展名
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,
};