@semba-ryuichiro/webpify 1.1.0 → 1.2.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/README.md CHANGED
@@ -26,6 +26,7 @@ webpify は画像ファイル(PNG/JPEG/GIF)を WebP 形式に変換する CL
26
26
  - 単一ファイルまたはディレクトリ一括変換
27
27
  - 再帰的なディレクトリ走査
28
28
  - 品質パラメータの指定(1-100)
29
+ - lossless(可逆圧縮)モード対応
29
30
  - 出力先ディレクトリの指定
30
31
  - 既存ファイルの上書き制御
31
32
  - WebP ファイル一覧表示
@@ -53,17 +54,18 @@ webpify ./images -r
53
54
 
54
55
  ### オプション
55
56
 
56
- | オプション | 説明 | デフォルト |
57
- | -------------------- | ------------- | ----- |
58
- | `-o, --output <dir>` | 出力先ディレクトリ | 入力と同じ |
59
- | `-q, --quality <n>` | 品質(1-100) | 100 |
60
- | `-r, --recursive` | 再帰的に処理 | false |
61
- | `-f, --force` | 既存ファイルを上書き | false |
62
- | `--list` | WebP ファイル一覧表示 | - |
63
- | `--absolute` | 一覧表示時に絶対パスで表示 | false |
64
- | `--quiet` | 統計情報を非表示 | false |
65
- | `-v, --version` | バージョン表示 | - |
66
- | `-h, --help` | ヘルプ表示 | - |
57
+ | オプション | 説明 | デフォルト |
58
+ | -------------------- | --------------- | ----- |
59
+ | `-o, --output <dir>` | 出力先ディレクトリ | 入力と同じ |
60
+ | `-q, --quality <n>` | 品質(1-100) | 100 |
61
+ | `-r, --recursive` | 再帰的に処理 | false |
62
+ | `-f, --force` | 既存ファイルを上書き | false |
63
+ | `--lossless` | 可逆圧縮モードで変換 | false |
64
+ | `--list` | WebP ファイル一覧表示 | - |
65
+ | `--absolute` | 一覧表示時に絶対パスで表示 | false |
66
+ | `--quiet` | 統計情報を非表示 | false |
67
+ | `-v, --version` | バージョン表示 | - |
68
+ | `-h, --help` | ヘルプ表示 | - |
67
69
 
68
70
  ### 例
69
71
 
@@ -74,6 +76,9 @@ webpify image.png -q 90
74
76
  # 別ディレクトリに出力
75
77
  webpify ./images -o ./webp-images
76
78
 
79
+ # lossless(可逆圧縮)モードで変換
80
+ webpify image.png --lossless
81
+
77
82
  # 強制上書き + 再帰 + 静音
78
83
  webpify ./images -r -f --quiet
79
84
  ```
@@ -6,7 +6,10 @@ import sharp from 'sharp';
6
6
  export function createSharpAdapter() {
7
7
  return {
8
8
  async convertToWebP(inputPath, outputPath, options) {
9
- const result = await sharp(inputPath).webp({ quality: options.quality }).toFile(outputPath);
9
+ // lossless モードの場合は lossless: true を設定(quality は無視される)
10
+ // lossy モードの場合は quality を設定
11
+ const webpOptions = options.lossless ? { lossless: true } : { quality: options.quality };
12
+ const result = await sharp(inputPath).webp(webpOptions).toFile(outputPath);
10
13
  return { size: result.size };
11
14
  },
12
15
  async getMetadata(filePath) {
@@ -44,6 +44,7 @@ export function createArgumentParser() {
44
44
  .option('-q, --quality <number>', '品質レベル (1-100)', parseQuality, DEFAULT_QUALITY)
45
45
  .option('-r, --recursive', 'ディレクトリを再帰的に処理', false)
46
46
  .option('-f, --force', '既存ファイルを上書き', false)
47
+ .option('--lossless', 'lossless(可逆圧縮)モードで変換', false)
47
48
  .option('--quiet', 'サイレントモード(出力なし)', false)
48
49
  .option('--list', 'WebP ファイルをサイズ情報付きで一覧表示', false)
49
50
  .option('--absolute', '--list オプション使用時に絶対パスで表示', false)
@@ -62,6 +63,7 @@ export function createArgumentParser() {
62
63
  force: false,
63
64
  input: '',
64
65
  list: false,
66
+ lossless: false,
65
67
  quality: DEFAULT_QUALITY,
66
68
  quiet: false,
67
69
  recursive: false,
@@ -81,6 +83,7 @@ export function createArgumentParser() {
81
83
  force: false,
82
84
  input: '',
83
85
  list: false,
86
+ lossless: false,
84
87
  quality: DEFAULT_QUALITY,
85
88
  quiet: false,
86
89
  recursive: false,
@@ -96,6 +99,7 @@ export function createArgumentParser() {
96
99
  force: options['force'],
97
100
  input: args[0] || '',
98
101
  list: options['list'],
102
+ lossless: options['lossless'],
99
103
  output: options['output'],
100
104
  quality: options['quality'],
101
105
  quiet: options['quiet'],
@@ -37,9 +37,10 @@ export function createMain(deps) {
37
37
  /**
38
38
  * 単一ファイル変換モードを実行する
39
39
  */
40
- async function executeSingleFileMode(inputPath, quality, force, output, quiet) {
40
+ async function executeSingleFileMode(inputPath, quality, force, output, quiet, lossless) {
41
41
  const result = await converter.convert(inputPath, {
42
42
  force,
43
+ lossless,
43
44
  output,
44
45
  quality,
45
46
  });
@@ -49,7 +50,7 @@ export function createMain(deps) {
49
50
  /**
50
51
  * ディレクトリ変換モードを実行する
51
52
  */
52
- async function executeDirectoryMode(inputPath, quality, force, output, recursive, quiet) {
53
+ async function executeDirectoryMode(inputPath, quality, force, output, recursive, quiet, lossless) {
53
54
  const files = await fileScanner.scan(inputPath, {
54
55
  extensions: CONVERT_EXTENSIONS,
55
56
  recursive,
@@ -66,7 +67,7 @@ export function createMain(deps) {
66
67
  reporter.reportConversion(result, quiet);
67
68
  }
68
69
  };
69
- const stats = await converter.convertBatch(files, { force, output, quality }, onProgress);
70
+ const stats = await converter.convertBatch(files, { force, lossless, output, quality }, onProgress);
70
71
  if (!quiet) {
71
72
  reporter.reportStats(stats);
72
73
  }
@@ -86,6 +87,10 @@ export function createMain(deps) {
86
87
  process.stderr.write(`Error: File not found: ${options.input}\n`);
87
88
  return 1;
88
89
  }
90
+ // lossless と quality の同時指定時に警告を出す(quality がデフォルト値でない場合)
91
+ if (options.lossless && options.quality !== 100 && !options.quiet) {
92
+ process.stderr.write('警告: --lossless と --quality が同時に指定されています。lossless モードでは quality は無視されます。\n');
93
+ }
89
94
  // 一覧表示モード(--list オプション)
90
95
  if (options.list) {
91
96
  const isDir = await fileScanner.isDirectory(options.input);
@@ -109,10 +114,10 @@ export function createMain(deps) {
109
114
  const isDirectory = await fileScanner.isDirectory(options.input);
110
115
  if (isDirectory) {
111
116
  // ディレクトリ変換モード
112
- return executeDirectoryMode(options.input, options.quality, options.force, options.output, options.recursive, options.quiet);
117
+ return executeDirectoryMode(options.input, options.quality, options.force, options.output, options.recursive, options.quiet, options.lossless);
113
118
  }
114
119
  // 単一ファイル変換モード
115
- return executeSingleFileMode(options.input, options.quality, options.force, options.output, options.quiet);
120
+ return executeSingleFileMode(options.input, options.quality, options.force, options.output, options.quiet, options.lossless);
116
121
  },
117
122
  };
118
123
  }
@@ -79,6 +79,7 @@ export function createConverter(deps) {
79
79
  // WebP に変換
80
80
  try {
81
81
  const result = await imageProcessor.convertToWebP(inputPath, outputPath, {
82
+ lossless: options.lossless,
82
83
  quality: options.quality,
83
84
  });
84
85
  return {
@@ -12,6 +12,7 @@ export interface ImageProcessorPort {
12
12
  */
13
13
  convertToWebP(inputPath: string, outputPath: string, options: {
14
14
  quality: number;
15
+ lossless: boolean;
15
16
  }): Promise<{
16
17
  size: number;
17
18
  }>;
@@ -8,10 +8,12 @@ export type SupportedFormat = 'png' | 'jpeg' | 'jpg' | 'gif';
8
8
  export interface ConvertOptions {
9
9
  /** 出力先パス(省略時は入力ファイルと同じディレクトリ) */
10
10
  output?: string | undefined;
11
- /** 変換品質(1-100、デフォルト: 80) */
11
+ /** 変換品質(1-100、デフォルト: 100) */
12
12
  quality: number;
13
13
  /** 既存ファイルを上書きするかどうか */
14
14
  force: boolean;
15
+ /** lossless(可逆圧縮)モードで変換するかどうか */
16
+ lossless: boolean;
15
17
  }
16
18
  /**
17
19
  * 単一ファイルの変換結果
@@ -91,6 +93,8 @@ export interface ParsedOptions {
91
93
  list: boolean;
92
94
  /** 一覧表示時に絶対パスで表示するか */
93
95
  absolutePath: boolean;
96
+ /** lossless(可逆圧縮)モードで変換するか */
97
+ lossless: boolean;
94
98
  }
95
99
  /**
96
100
  * 一覧表示用の画像アイテム
package/package.json CHANGED
@@ -80,7 +80,7 @@
80
80
  },
81
81
  "type": "module",
82
82
  "types": "./dist/index.d.ts",
83
- "version": "1.1.0",
83
+ "version": "1.2.0",
84
84
  "scripts": {
85
85
  "build": "tsc",
86
86
  "deps": "dependency-cruiser src --config .dependency-cruiser.cjs",