@zhaoshijun/compress 1.2.5 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/compress.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import { Command } from 'commander';
4
4
  import chalk from 'chalk';
@@ -9,7 +9,7 @@ import fg from 'fast-glob';
9
9
  import prettyBytes from 'pretty-bytes';
10
10
  import { MultiBar, Presets } from 'cli-progress';
11
11
  import { loadConfig } from '../src/config/loader.js';
12
- import { compressImage } from '../src/core/compressor.js';
12
+ import { compressImage, addWatermarkOnly } from '../src/core/compressor.js';
13
13
  import { DEFAULT_EXCLUDES } from '../src/utils/constants.js';
14
14
  import { isSupportedImage as isSupported } from '../src/utils/file-utils.js';
15
15
  import { createRequire } from 'module';
@@ -29,21 +29,8 @@ program
29
29
  .option('--dry-run', '仅列出将处理的文件,不写入磁盘')
30
30
  .option('-q, --quiet', '静默模式,仅输出错误')
31
31
  .option('-w, --watermark', '启用水印')
32
- .option('--watermark-text <text>', '水印文本')
33
- .option('--watermark-opacity <opacity>', '水印透明度 (0-1)', parseFloat)
34
- .option('--watermark-density <density>', '水印密度 (1-10)', parseInt)
35
- .option('--watermark-color <color>', '水印颜色 (hex 或 rgba)')
36
- .option('--watermark-mode <mode>', '水印模式 (text/image/tiled)')
37
- .option('--watermark-image <path>', '水印图片路径')
38
- .option('--watermark-x <x>', '水印 X 坐标', parseInt)
39
- .option('--watermark-y <y>', '水印 Y 坐标', parseInt)
40
- .option('--watermark-width <width>', '水印宽度', parseInt)
41
- .option('--watermark-height <height>', '水印高度', parseInt)
42
- .option('--watermark-position <position>', '水印位置 (top-left/top-right/bottom-left/bottom-right/center)')
43
- .option('--watermark-padding <padding>', '水印边距', parseInt)
44
- .option('--watermark-angle <angle>', '水印倾斜角度', parseInt)
45
- .option('--watermark-spacing-x <spacingX>', '水印水平间隔', parseInt)
46
- .option('--watermark-spacing-y <spacingY>', '水印垂直间隔', parseInt)
32
+ .option('--keep-metadata', '保留图片元数据(拍摄时间、地点等)')
33
+ .option('-o, --output <dir>', '输出目录')
47
34
  .action(async (options) => {
48
35
  try {
49
36
  const startTime = Date.now();
@@ -53,26 +40,8 @@ program
53
40
 
54
41
  // 合并命令行选项
55
42
  if (options.backup !== undefined) config.backup = options.backup;
56
-
57
- // 合并水印选项
58
- if (options.watermark) {
59
- config.watermark = config.watermark || {};
60
- if (options.watermarkText) config.watermark.text = options.watermarkText;
61
- if (options.watermarkOpacity !== undefined) config.watermark.opacity = options.watermarkOpacity;
62
- if (options.watermarkDensity !== undefined) config.watermark.density = options.watermarkDensity;
63
- if (options.watermarkColor) config.watermark.color = options.watermarkColor;
64
- if (options.watermarkMode) config.watermark.mode = options.watermarkMode;
65
- if (options.watermarkImage) config.watermark.imagePath = options.watermarkImage;
66
- if (options.watermarkX !== undefined) config.watermark.x = options.watermarkX;
67
- if (options.watermarkY !== undefined) config.watermark.y = options.watermarkY;
68
- if (options.watermarkWidth !== undefined) config.watermark.width = options.watermarkWidth;
69
- if (options.watermarkHeight !== undefined) config.watermark.height = options.watermarkHeight;
70
- if (options.watermarkPosition) config.watermark.position = options.watermarkPosition;
71
- if (options.watermarkPadding !== undefined) config.watermark.padding = options.watermarkPadding;
72
- if (options.watermarkAngle !== undefined) config.watermark.angle = options.watermarkAngle;
73
- if (options.watermarkSpacingX !== undefined) config.watermark.spacingX = options.watermarkSpacingX;
74
- if (options.watermarkSpacingY !== undefined) config.watermark.spacingY = options.watermarkSpacingY;
75
- }
43
+ if (options.keepMetadata !== undefined) config.keepMetadata = options.keepMetadata;
44
+ if (options.output !== undefined) config.output = options.output;
76
45
 
77
46
  // 确定输入源
78
47
  // 如果 config.input 未定义,则默认为当前目录 '**/*.{jpg,jpeg,png,webp}'
@@ -251,17 +220,25 @@ program
251
220
  console.log(chalk.green('Created compress.config.js'));
252
221
  });
253
222
 
254
- program.parse();
255
-
256
223
  // Watermark command
257
224
  program
258
225
  .command('watermark')
259
226
  .description('给图片添加水印(不压缩)')
260
227
  .option('-c, --config <path>', '配置文件路径')
228
+ .option('--watermark-mode <mode>', '水印模式 (text/image/tiled)', 'text')
261
229
  .option('--watermark-text <text>', '水印文本')
230
+ .option('--watermark-image <path>', '水印图片路径')
262
231
  .option('--watermark-opacity <opacity>', '水印透明度 (0-1)', parseFloat)
263
232
  .option('--watermark-density <density>', '水印密度 (1-10)', parseInt)
264
233
  .option('--watermark-color <color>', '水印颜色 (hex 或 rgba)')
234
+ .option('--watermark-font-size <size>', '水印字体大小', parseInt)
235
+ .option('--watermark-width <width>', '水印图片宽度', parseInt)
236
+ .option('--watermark-height <height>', '水印图片高度', parseInt)
237
+ .option('--watermark-position <position>', '水印位置 (top-left/top-right/bottom-left/bottom-right/center/custom)')
238
+ .option('--watermark-padding <padding>', '水印边距', parseInt)
239
+ .option('--watermark-angle <angle>', '水印倾斜角度', parseInt)
240
+ .option('--watermark-spacing-x <spacing>', '水印水平间隔', parseInt)
241
+ .option('--watermark-spacing-y <spacing>', '水印垂直间隔', parseInt)
265
242
  .option('--output <dir>', '输出目录(默认为 ./watermarked)')
266
243
  .action(async (options) => {
267
244
  try {
@@ -271,13 +248,30 @@ program
271
248
  const config = await loadConfig(options.config);
272
249
 
273
250
  // 合并水印选项
251
+ if (options.watermarkMode) config.watermark.mode = options.watermarkMode;
274
252
  if (options.watermarkText) config.watermark.text = options.watermarkText;
253
+ if (options.watermarkImage) config.watermark.imagePath = options.watermarkImage;
275
254
  if (options.watermarkOpacity !== undefined) config.watermark.opacity = options.watermarkOpacity;
276
255
  if (options.watermarkDensity !== undefined) config.watermark.density = options.watermarkDensity;
277
256
  if (options.watermarkColor) config.watermark.color = options.watermarkColor;
257
+ if (options.watermarkFontSize) config.watermark.fontSize = options.watermarkFontSize;
258
+ if (options.watermarkWidth) config.watermark.width = options.watermarkWidth;
259
+ if (options.watermarkHeight) config.watermark.height = options.watermarkHeight;
260
+ if (options.watermarkPosition) config.watermark.position = options.watermarkPosition;
261
+ if (options.watermarkPadding) config.watermark.padding = options.watermarkPadding;
262
+ if (options.watermarkAngle) config.watermark.angle = options.watermarkAngle;
263
+ if (options.watermarkSpacingX) config.watermark.spacingX = options.watermarkSpacingX;
264
+ if (options.watermarkSpacingY) config.watermark.spacingY = options.watermarkSpacingY;
278
265
 
279
- if (!config.watermark.text) {
280
- console.error(chalk.red('Error: Watermark text is required. Use --watermark-text option or set it in config file.'));
266
+ // 验证水印配置
267
+ const mode = config.watermark.mode || 'text';
268
+ if (mode === 'text' && !config.watermark.text) {
269
+ console.error(chalk.red('Error: Watermark text is required for text mode. Use --watermark-text option or set it in config file.'));
270
+ process.exit(1);
271
+ }
272
+
273
+ if ((mode === 'image' || mode === 'tiled') && !config.watermark.imagePath) {
274
+ console.error(chalk.red('Error: Watermark image path is required for image/tiled mode. Use --watermark-image option or set it in config file.'));
281
275
  process.exit(1);
282
276
  }
283
277
 
@@ -334,7 +328,7 @@ program
334
328
  const outDir = path.join(outputDir, relDir);
335
329
  const outFilePath = path.join(outDir, fileName);
336
330
 
337
- const watermarkedBuffer = await compressImage(file, config, file);
331
+ const watermarkedBuffer = await addWatermarkOnly(file, config, file);
338
332
 
339
333
  // 写入文件
340
334
  await fs.ensureDir(outDir);
@@ -371,3 +365,5 @@ program
371
365
  process.exit(1);
372
366
  }
373
367
  });
368
+
369
+ program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhaoshijun/compress",
3
- "version": "1.2.5",
3
+ "version": "1.4.0",
4
4
  "description": "Image compression CLI and Vite plugin",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,7 @@
1
1
  export const defaultConfig = {
2
2
  output: './compressed',
3
3
  quality: 88,
4
+ keepMetadata: false,
4
5
  jpegOptions: {
5
6
  quality: 88,
6
7
  progressive: false,
@@ -2,6 +2,57 @@ import sharp from 'sharp';
2
2
  import path from 'path';
3
3
  import { addWatermark, addImageWatermark, addTiledImageWatermark } from './watermark.js';
4
4
 
5
+ /**
6
+ * 只添加水印(不压缩)
7
+ * @param {string|Buffer} input - 图片路径或 Buffer
8
+ * @param {Object} options - 水印配置
9
+ * @param {string} [filePath] - 原始文件路径 (用于判断格式)
10
+ * @returns {Promise<Buffer>} 添加水印后的 Buffer
11
+ */
12
+ export async function addWatermarkOnly(input, options, filePath) {
13
+ // 初始化 sharp 实例
14
+ let instance = sharp(input);
15
+ const metadata = await instance.metadata();
16
+
17
+ // 添加水印
18
+ if (options.watermark) {
19
+ // 文本水印
20
+ if (options.watermark.mode === 'text' || (options.watermark.text && options.watermark.text.trim() !== '')) {
21
+ const watermarkedBuffer = await addWatermark(instance, options.watermark, metadata);
22
+ instance = sharp(watermarkedBuffer);
23
+ }
24
+ // 单个图片水印
25
+ else if (options.watermark.mode === 'image' || (options.watermark.imagePath && options.watermark.mode !== 'tiled')) {
26
+ const watermarkedBuffer = await addImageWatermark(instance, options.watermark);
27
+ instance = sharp(watermarkedBuffer);
28
+ }
29
+ // 平铺图片水印
30
+ else if (options.watermark.mode === 'tiled' || (options.watermark.imagePath && options.watermark.mode === 'tiled')) {
31
+ const watermarkedBuffer = await addTiledImageWatermark(instance, options.watermark, metadata);
32
+ instance = sharp(watermarkedBuffer);
33
+ }
34
+ }
35
+
36
+ // 保持原始格式,不进行压缩
37
+ const format = metadata.format;
38
+ switch (format) {
39
+ case 'jpeg':
40
+ case 'jpg':
41
+ instance = instance.jpeg({ quality: 100 });
42
+ break;
43
+ case 'png':
44
+ instance = instance.png({ compressionLevel: 0 });
45
+ break;
46
+ case 'webp':
47
+ instance = instance.webp({ quality: 100 });
48
+ break;
49
+ default:
50
+ throw new Error(`Unsupported format: ${format}`);
51
+ }
52
+
53
+ return instance.toBuffer();
54
+ }
55
+
5
56
  /**
6
57
  * 压缩图片
7
58
  * @param {string|Buffer} input - 图片路径或 Buffer
@@ -76,5 +127,10 @@ export async function compressImage(input, options, filePath) {
76
127
  }
77
128
  }
78
129
 
130
+ // 如果需要保留元数据
131
+ if (options.keepMetadata) {
132
+ instance = instance.withMetadata();
133
+ }
134
+
79
135
  return instance.toBuffer();
80
136
  }