UNPKG

photo-watermark-cli

Version:

A modern TypeScript CLI tool to add timestamp watermarks to photos with intelligent size scaling

510 lines 20.7 kB
#!/usr/bin/env node "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const commander_1 = require("commander"); const inquirer_1 = __importDefault(require("inquirer")); const path_1 = require("path"); const fs_1 = require("fs"); const path_2 = require("path"); const os_1 = require("os"); const watermark_1 = require("../lib/watermark"); const config_1 = require("../lib/config"); const scanner_1 = require("../lib/scanner"); const chalk_1 = __importDefault(require("chalk")); const fs_2 = require("fs"); // 读取 package.json 版本号 function getPackageVersion() { try { // 尝试多个可能的路径 const possiblePaths = [ (0, path_2.join)(__dirname, '../../package.json'), // 从 dist/bin 到根目录 (0, path_2.join)(__dirname, '../../../package.json'), // 如果有额外的嵌套 (0, path_2.join)(process.cwd(), 'package.json') // 当前工作目录 ]; for (const packagePath of possiblePaths) { try { const packageJson = JSON.parse((0, fs_2.readFileSync)(packagePath, 'utf8')); if (packageJson.version) { return packageJson.version; } } catch { // 继续尝试下一个路径 continue; } } return '1.0.0'; // 默认版本号 } catch (error) { return '1.0.0'; // 默认版本号 } } // 文件夹选择器函数 async function selectDirectory(initialPath) { let currentPath = initialPath || process.cwd(); while (true) { console.clear(); console.log(chalk_1.default.blue('📁 文件夹选择器')); console.log(chalk_1.default.gray('当前位置:'), chalk_1.default.cyan(currentPath)); console.log(chalk_1.default.gray('使用方向键选择,Enter确认\n')); const items = []; try { // 添加返回上级目录选项(除非已经在根目录) const parentPath = (0, path_2.dirname)(currentPath); if (parentPath !== currentPath) { items.push({ name: '📁 .. (返回上级目录)', value: { path: parentPath, isDirectory: true } }); } // 添加快捷目录选项 if (currentPath !== (0, os_1.homedir)()) { items.push({ name: '🏠 用户主目录', value: { path: (0, os_1.homedir)(), isDirectory: true } }); } if (currentPath !== process.cwd()) { items.push({ name: '📂 当前工作目录', value: { path: process.cwd(), isDirectory: true } }); } // 添加当前目录下的所有文件夹 const entries = (0, fs_1.readdirSync)(currentPath); const directories = entries .filter(entry => { try { const fullPath = (0, path_2.join)(currentPath, entry); return (0, fs_1.statSync)(fullPath).isDirectory() && !entry.startsWith('.'); } catch { return false; } }) .sort() .map(entry => ({ name: `📁 ${entry}`, value: { path: (0, path_2.join)(currentPath, entry), isDirectory: true } })); items.push(...directories); // 添加选择当前目录的选项 items.push({ name: `✅ 选择当前目录: ${currentPath}`, value: { path: currentPath, isDirectory: false } }); } catch (error) { console.error(chalk_1.default.red('读取目录失败:', error.message)); return currentPath; } if (items.length === 0) { console.log(chalk_1.default.yellow('当前目录没有可访问的子文件夹')); return currentPath; } const { selectedItem } = await inquirer_1.default.prompt([ { type: 'list', name: 'selectedItem', message: '请选择一个选项:', choices: items, pageSize: Math.min(15, items.length) } ]); // 如果选择的是目录,继续浏览 if (selectedItem.isDirectory) { currentPath = (0, path_1.resolve)(selectedItem.path); } else { // 选择当前目录,返回结果 return currentPath; } } } commander_1.program .name('watermark') .description('为目录下的所有照片添加时间水印') .version(getPackageVersion()); commander_1.program .command('add') .description('为指定目录下的照片添加时间水印') .option('-d, --directory <path>', '指定要处理的目录路径') .option('-o, --output <path>', '指定输出目录路径(可选)') .option('-f, --format <format>', '时间格式(默认:YYYY-MM-DD HH:mm:ss)') .option('-b, --brightness <value>', '照片亮度调整(0.1-3.0,1.0为原始亮度)', parseFloat) .option('--overwrite', '覆盖已存在的文件') .option('-i, --interactive', '使用交互式模式') .action(async (options) => { try { let targetDir = options.directory; // 加载用户配置 const savedConfig = await (0, config_1.loadConfig)(); // 如果没有指定目录或启用交互式模式,通过文件夹选择器选择 if (!targetDir || options.interactive) { console.log(chalk_1.default.blue('请选择要处理的目录:')); targetDir = await selectDirectory(options.directory || process.cwd()); console.log(chalk_1.default.green(`✅ 已选择目录: ${targetDir}\n`)); } if (!targetDir) { console.error(chalk_1.default.red('❌ 错误: 必须指定目录路径')); process.exit(1); } // 确认处理选项,使用保存的配置作为默认值 const config = await inquirer_1.default.prompt([ { type: 'list', name: 'outputMode', message: '选择输出方式:', choices: [ { name: '在原目录下创建 "watermarked" 文件夹(推荐)', value: 'subfolder' }, { name: '覆盖原文件(请先备份!)', value: 'overwrite' } ], default: 'subfolder' }, { type: 'input', name: 'timeFormat', message: '时间格式:', default: options.format || savedConfig.timeFormat, validate: (input) => { if (!input.trim()) { return '时间格式不能为空'; } return true; } }, { type: 'list', name: 'position', message: '水印位置:', choices: [ { name: '左下角', value: 'bottom-left' }, { name: '右下角', value: 'bottom-right' }, { name: '左上角', value: 'top-left' }, { name: '右上角', value: 'top-right' } ], default: savedConfig.position }, { type: 'number', name: 'fontSize', message: '字体大小(像素):', default: savedConfig.fontSize, validate: (input) => { if (input < 12 || input > 200) { return '字体大小必须在 12-200 像素之间'; } return true; } }, { type: 'list', name: 'fontColor', message: '字体颜色:', choices: [ { name: '白色', value: 'white' }, { name: '黑色', value: 'black' }, { name: '红色', value: 'red' }, { name: '蓝色', value: 'blue' }, { name: '绿色', value: 'green' }, { name: '黄色', value: 'yellow' } ], default: savedConfig.fontColor }, { type: 'confirm', name: 'addShadow', message: '添加文字阴影以提高可读性?', default: savedConfig.addShadow }, { type: 'number', name: 'quality', message: '图片质量 (1-100):', default: savedConfig.quality, validate: (input) => { if (input < 1 || input > 100) { return '图片质量必须在 1-100 之间'; } return true; } }, { type: 'number', name: 'brightness', message: '照片亮度调整 (0.5-3.0,1.0为原始亮度,大于1.0增亮):', default: options.brightness || savedConfig.brightness, validate: (input) => { if (input < 0.1 || input > 3.0) { return '亮度值必须在 0.1-3.0 之间'; } return true; } }, { type: 'confirm', name: 'saveAsDefault', message: '保存这些设置为默认配置?', default: false }, { type: 'confirm', name: 'proceed', message: '确认开始处理?', default: true } ]); if (!config.proceed) { console.log(chalk_1.default.yellow('操作已取消')); return; } // 如果用户选择保存配置 if (config.saveAsDefault) { const configToSave = { timeFormat: config.timeFormat, position: config.position, fontSize: config.fontSize, fontColor: config.fontColor, addShadow: config.addShadow, quality: config.quality, brightness: config.brightness }; await (0, config_1.saveConfig)(configToSave); console.log(chalk_1.default.green('✅ 配置已保存')); } // 根据用户选择确定输出目录 let outputDir; if (config.outputMode === 'subfolder') { outputDir = (0, path_1.resolve)(targetDir, 'watermarked'); } else if (options.output) { // 如果通过命令行参数指定了输出目录,仍然支持 outputDir = (0, path_1.resolve)(options.output); } const watermarkOptions = { inputDir: (0, path_1.resolve)(targetDir), outputDir: outputDir, config: { timeFormat: config.timeFormat, position: config.position, fontSize: config.fontSize, fontColor: config.fontColor, addShadow: config.addShadow, quality: config.quality, brightness: config.brightness }, overwrite: options.overwrite || config.outputMode === 'overwrite' }; console.log(chalk_1.default.blue('开始处理照片...')); const result = await (0, watermark_1.processDirectory)(watermarkOptions); if (result.success) { console.log(chalk_1.default.green('✅ 处理完成!')); } else { console.log(chalk_1.default.yellow(`⚠️ 处理完成,但有 ${result.errors.length} 个错误`)); result.errors.forEach(error => { console.log(chalk_1.default.red(` • ${error}`)); }); } } catch (error) { console.error(chalk_1.default.red('❌ 错误:', error.message)); process.exit(1); } }); commander_1.program .command('list') .description('列出指定目录下支持的图片文件') .argument('[directory]', '要扫描的目录路径(可选,不提供则使用文件夹选择器)') .action(async (directory) => { try { let targetDir = directory; // 如果没有提供目录参数,使用文件夹选择器 if (!targetDir) { console.log(chalk_1.default.blue('请选择要扫描的目录:')); targetDir = await selectDirectory(process.cwd()); console.log(chalk_1.default.green(`✅ 已选择目录: ${targetDir}\n`)); } const photos = await (0, scanner_1.scanPhotos)(targetDir); if (photos.length === 0) { console.log(chalk_1.default.yellow('未找到支持的图片文件')); return; } console.log(chalk_1.default.blue(`找到 ${photos.length} 个图片文件:`)); photos.forEach((photo, index) => { console.log(chalk_1.default.gray(`${index + 1}. ${(0, path_1.relative)(targetDir, photo)}`)); }); } catch (error) { console.error(chalk_1.default.red('❌ 错误:', error.message)); process.exit(1); } }); commander_1.program .command('config') .description('管理配置设置') .option('-s, --show', '显示当前配置') .option('-r, --reset', '重置为默认配置') .option('--path', '显示配置文件路径') .action(async (options) => { try { if (options.path) { console.log(chalk_1.default.blue('配置文件路径:'), (0, config_1.getConfigPath)()); return; } if (options.reset) { const confirm = await inquirer_1.default.prompt([ { type: 'confirm', name: 'confirmed', message: '确认重置所有配置为默认值?', default: false } ]); if (confirm.confirmed) { await (0, config_1.resetConfig)(); console.log(chalk_1.default.green('✅ 配置已重置为默认值')); } else { console.log(chalk_1.default.yellow('操作已取消')); } return; } // 显示当前配置 const config = await (0, config_1.loadConfig)(); console.log(chalk_1.default.blue('当前配置:')); console.log(chalk_1.default.gray('时间格式:'), config.timeFormat); console.log(chalk_1.default.gray('水印位置:'), config.position); console.log(chalk_1.default.gray('字体大小:'), config.fontSize + 'px'); console.log(chalk_1.default.gray('字体颜色:'), config.fontColor); console.log(chalk_1.default.gray('文字阴影:'), config.addShadow ? '是' : '否'); console.log(chalk_1.default.gray('图片质量:'), config.quality + '%'); console.log(chalk_1.default.gray('照片亮度:'), config.brightness + 'x'); console.log(chalk_1.default.gray('配置文件:'), (0, config_1.getConfigPath)()); } catch (error) { console.error(chalk_1.default.red('❌ 错误:', error.message)); process.exit(1); } }); commander_1.program .command('brighten') .description('调整照片亮度(专为打印优化)') .option('-d, --directory <path>', '指定要处理的目录路径') .option('-o, --output <path>', '指定输出目录路径(可选)') .option('-b, --brightness <value>', '亮度调整值(0.1-3.0,1.0为原始亮度,建议1.2-1.5)', parseFloat) .option('--overwrite', '覆盖已存在的文件') .option('-i, --interactive', '使用交互式模式') .action(async (options) => { try { let targetDir = options.directory; // 如果没有指定目录或启用交互式模式,通过文件夹选择器选择 if (!targetDir || options.interactive) { console.log(chalk_1.default.blue('请选择要处理的目录:')); targetDir = await selectDirectory(options.directory || process.cwd()); console.log(chalk_1.default.green(`✅ 已选择目录: ${targetDir}\n`)); } if (!targetDir) { console.error(chalk_1.default.red('❌ 错误: 必须指定目录路径')); process.exit(1); } // 亮度调整配置 const brightnessConfig = await inquirer_1.default.prompt([ { type: 'list', name: 'outputMode', message: '选择输出方式:', choices: [ { name: '在原目录下创建 "brightened" 文件夹(推荐)', value: 'subfolder' }, { name: '覆盖原文件(请先备份!)', value: 'overwrite' } ], default: 'subfolder' }, { type: 'number', name: 'brightness', message: '亮度调整值 (打印建议1.2-1.5,屏幕显示偏暗建议1.3-1.6):', default: options.brightness || 1.3, validate: (input) => { if (input < 0.1 || input > 3.0) { return '亮度值必须在 0.1-3.0 之间'; } return true; } }, { type: 'number', name: 'quality', message: '图片质量 (1-100):', default: 95, validate: (input) => { if (input < 1 || input > 100) { return '图片质量必须在 1-100 之间'; } return true; } }, { type: 'confirm', name: 'proceed', message: '确认开始调整亮度?', default: true } ]); if (!brightnessConfig.proceed) { console.log(chalk_1.default.yellow('操作已取消')); return; } // 根据用户选择确定输出目录 let outputDir; if (brightnessConfig.outputMode === 'subfolder') { outputDir = (0, path_1.resolve)(targetDir, 'brightened'); } else if (options.output) { outputDir = (0, path_1.resolve)(options.output); } // 使用专门的亮度调整函数 const watermarkOptions = { inputDir: (0, path_1.resolve)(targetDir), outputDir: outputDir, config: { timeFormat: '', // 不添加水印时时间格式不重要 position: 'bottom-left', fontSize: 0, fontColor: 'white', addShadow: false, quality: brightnessConfig.quality, brightness: brightnessConfig.brightness }, overwrite: options.overwrite || brightnessConfig.outputMode === 'overwrite' }; console.log(chalk_1.default.blue('开始调整照片亮度...')); const result = await (0, watermark_1.processBrightnessOnly)(watermarkOptions); if (result.success) { console.log(chalk_1.default.green('✅ 亮度调整完成!')); console.log(chalk_1.default.blue(`亮度已调整为原始亮度的 ${brightnessConfig.brightness}x`)); if (outputDir) { console.log(chalk_1.default.blue(`输出目录: ${outputDir}`)); } } else { console.log(chalk_1.default.yellow(`⚠️ 处理完成,但有 ${result.errors.length} 个错误`)); result.errors.forEach(error => { console.log(chalk_1.default.red(` • ${error}`)); }); } } catch (error) { console.error(chalk_1.default.red('❌ 错误:', error.message)); process.exit(1); } }); // 如果没有参数,显示帮助 if (process.argv.length === 2) { commander_1.program.help(); } commander_1.program.parse(); //# sourceMappingURL=watermark.js.map