photo-watermark-cli
Version:
A modern TypeScript CLI tool to add timestamp watermarks to photos with intelligent size scaling
510 lines • 20.7 kB
JavaScript
#!/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