入库 4 个验收脚本(play-verify/play-smoke/play-daily-check/img-process), play-verify 新增 markdown 报告生成和论坛发布功能。 同时完成但不在 git 跟踪范围的变更: - ~/bin/session-end(会话收尾脚本) - ~/bin/play-add-plugin(交互式添加插件) - staging-watcher systemd user service - cron 自动清理 test-results Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
78 lines
2.6 KiB
JavaScript
Executable file
78 lines
2.6 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
||
// img-process — 截图后处理工具
|
||
// 用法: img-process <input> [options]
|
||
// --resize <width> 缩放到指定宽度(保持比例)
|
||
// --quality <1-100> 压缩质量(默认 85)
|
||
// --watermark <text> 添加水印文字
|
||
// --trim 自动裁剪空白边
|
||
// --output <path> 输出路径(默认覆盖原文件)
|
||
// --webp 转为 WebP 格式
|
||
|
||
const sharp = require('sharp');
|
||
const path = require('path');
|
||
|
||
const args = process.argv.slice(2);
|
||
const input = args.find(a => !a.startsWith('--') && !args[args.indexOf(a) - 1]?.startsWith('--'));
|
||
|
||
function getArg(name) {
|
||
const idx = args.indexOf(`--${name}`);
|
||
return idx >= 0 ? args[idx + 1] : null;
|
||
}
|
||
const hasFlag = (name) => args.includes(`--${name}`);
|
||
|
||
if (!input) {
|
||
console.log('用法: img-process <input.png> [--resize 1280] [--quality 85] [--trim] [--webp] [--output out.png]');
|
||
process.exit(0);
|
||
}
|
||
|
||
(async () => {
|
||
let img = sharp(input);
|
||
const meta = await img.metadata();
|
||
console.log(`输入: ${input} (${meta.width}x${meta.height}, ${meta.format})`);
|
||
|
||
if (hasFlag('trim')) {
|
||
img = img.trim();
|
||
console.log(' 裁剪空白边');
|
||
}
|
||
|
||
const resizeW = getArg('resize');
|
||
if (resizeW) {
|
||
img = img.resize(parseInt(resizeW));
|
||
console.log(` 缩放到宽度 ${resizeW}`);
|
||
}
|
||
|
||
const quality = parseInt(getArg('quality') || '85');
|
||
|
||
const watermark = getArg('watermark');
|
||
if (watermark) {
|
||
const { width, height } = await img.metadata();
|
||
const w = width || meta.width;
|
||
const h = height || meta.height;
|
||
const fontSize = Math.max(16, Math.floor(w / 40));
|
||
const svg = `<svg width="${w}" height="${h}">
|
||
<text x="${w - 10}" y="${h - 10}" font-size="${fontSize}" fill="rgba(255,255,255,0.5)"
|
||
text-anchor="end" font-family="sans-serif">${watermark}</text>
|
||
</svg>`;
|
||
img = img.composite([{ input: Buffer.from(svg), gravity: 'southeast' }]);
|
||
console.log(` 水印: ${watermark}`);
|
||
}
|
||
|
||
let output = getArg('output') || input;
|
||
if (hasFlag('webp')) {
|
||
img = img.webp({ quality });
|
||
if (!getArg('output')) output = input.replace(/\.\w+$/, '.webp');
|
||
console.log(` 转 WebP (quality: ${quality})`);
|
||
} else {
|
||
img = img.png({ quality: Math.min(quality, 100) });
|
||
}
|
||
|
||
await img.toFile(output === input ? output + '.tmp' : output);
|
||
if (output === input) {
|
||
const fs = require('fs');
|
||
fs.renameSync(output + '.tmp', output);
|
||
}
|
||
|
||
const outMeta = await sharp(output).metadata();
|
||
const stats = require('fs').statSync(output);
|
||
console.log(`输出: ${output} (${outMeta.width}x${outMeta.height}, ${(stats.size / 1024).toFixed(0)}KB)`);
|
||
})();
|