🐈

Squoosh CLIでJPEG/PNGをWebPに一括変換

2022/07/13に公開

Googleが提供する画像最適化ツールSquooshは、JPEG/PNG/GIFなどの従来形式の画像をWebPやAVIFなどの次世代画像形式に変換してくれるツールです。
本記事では、一括変換が可能なSquooshのCLI版を使ってJPEG/PNGをWebPに一括変換する方法についてまとめました。
エラーによりはまった点もあったので、エラー内容と解消した方法も紹介していきます。

参考

以下を参考にしました。(※サポート終了に伴い削除されています)

squoosh/cli at dev · GoogleChromeLabs/squoosh - GitHub
https://github.com/GoogleChromeLabs/squoosh/blob/dev/libsquoosh/src/codecs.ts

環境

  • macOS Monterey 12.3

Squoosh CLIの準備

本記事では、npmでSquoosh CLIをインストールして使う方法を紹介します。
GoogleChromeLabsのGitHubにはnpxを用いてインストールせずに利用する方法も書かれていました。

インストールします。

$ npm i -g @squoosh/cli

JPEG/PNGをWebPに変換

うまくいった条件&コマンド

結論から先に。
以下の条件とコマンドで、カレントディレクトリ配下のJPEGファイルをWebPファイルに一括変換することができました。

  • Node.js v17.6.0
  • @squoosh/cli@0.7.2
$ squoosh-cli --webp '{}' *.jpg

PNGファイルも同様に変換できます。

$ squoosh-cli --webp '{}' *.png

画像の品質はqualityで指定します。デフォルトでは75%に設定されています。
(品質はPNGやJPGの圧縮率に近いものですが、完全に同じものではありません。値が大きいほど視覚的品質が高くなり、小さいほど品質が低くなります。)

$ squoosh-cli --webp '{quality: 100}' *.jpg  # 品質100%

オプションの設定の種類とデフォルト値はcodecs.tsdefaultEncoderOptionsプロパティで確認できます。
--webpのデフォルト設定は以下のようになっていました。

    defaultEncoderOptions: {
      quality: 75,
      target_size: 0,
      target_PSNR: 0,
      method: 4,
      sns_strength: 50,
      filter_strength: 60,
      filter_sharpness: 0,
      filter_type: 1,
      partitions: 0,
      segments: 4,
      pass: 1,
      show_compressed: 0,
      preprocessing: 0,
      autofilter: 0,
      partition_limit: 0,
      alpha_compression: 1,
      alpha_filtering: 1,
      alpha_quality: 100,
      lossless: 0,
      exact: 0,
      image_hint: 0,
      emulate_jpeg_size: 0,
      thread_level: 0,
      low_memory: 0,
      near_lossless: 100,
      use_delta_palette: 0,
      use_sharp_yuv: 0,
    }

はまったエラー1: TypeError: Failed to parse URL from /Users/*/.nodebrew/node/v18.4.0/lib/node_modules/@squoosh/cli/node_modules/@squoosh/lib/build/imagequant_node-a4aafbae.wasm

エラーが起こった条件

  • Node.js v18.4.0
  • @squoosh/cli@0.7.2

以下を実行

$ squoosh-cli --webp '{}' *.jpg
エラー文
TypeError: Failed to parse URL from /Users/*/.nodebrew/node/v18.4.0/lib/node_modules/@squoosh/cli/node_modules/@squoosh/lib/build/imagequant_node-a4aafbae.wasm
    at new Request (node:internal/deps/undici/undici:4832:19)
    at Agent.fetch2 (node:internal/deps/undici/undici:5524:29)
    ... 4 lines matching cause stack trace ...
    at /Users/*/.nodebrew/node/v18.4.0/lib/node_modules/@squoosh/cli/node_modules/@squoosh/lib/build/index.js:28:35216
    at instantiateEmscriptenWasm (/Users/*/.nodebrew/node/v18.4.0/lib/node_modules/@squoosh/cli/node_modules/@squoosh/lib/build/index.js:1:466)
    at Object.<anonymous> (/Users/*/.nodebrew/node/v18.4.0/lib/node_modules/@squoosh/cli/node_modules/@squoosh/lib/build/index.js:30:323)
    at Module._compile (node:internal/modules/cjs/loader:1112:14) {
  [cause]: TypeError [ERR_INVALID_URL]: Invalid URL
      at new NodeError (node:internal/errors:388:5)
      at URL.onParseError (node:internal/url:564:9)
      at new URL (node:internal/url:644:5)
      at new Request (node:internal/deps/undici/undici:4830:25)
      at Agent.fetch2 (node:internal/deps/undici/undici:5524:29)
      at Object.fetch (node:internal/deps/undici/undici:6351:20)
      at fetch (node:internal/bootstrap/pre_execution:197:25)
      at instantiateAsync (/Users/*/.nodebrew/node/v18.4.0/lib/node_modules/@squoosh/cli/node_modules/@squoosh/lib/build/index.js:28:11469)
      at createWasm (/Users/*/.nodebrew/node/v18.4.0/lib/node_modules/@squoosh/cli/node_modules/@squoosh/lib/build/index.js:28:12079)
      at /Users/*/.nodebrew/node/v18.4.0/lib/node_modules/@squoosh/cli/node_modules/@squoosh/lib/build/index.js:28:35216 {
    input: '/Users/*/.nodebrew/node/v18.4.0/lib/node_modules/@squoosh/cli/node_modules/@squoosh/lib/build/imagequant_node-a4aafbae.wasm',
    code: 'ERR_INVALID_URL'
  }
}

※ユーザー名は*で置き換えています

GitHubのissues[LibSquoosh] URL invalid #1033にて同様の事例を見つけ、以下の回答がありました。

The only thing that has changed for me was node 18 being upgraded to a newer version, so perhaps that's the root cause.

For anyone running into the same problem: Temporarily downgrading to node >=v16 when using @squoosh/lib@0.4.0 is a successful workaround.

Node.jsのバージョンとsquoosh-cliのバージョンの組み合わせによってこのエラーが出る場合があるようです。私はNode.jsのバージョンを17.6.0にダウングレードすることによってこのエラーが出なくなりました。

はまったエラー2: SyntaxError: JSON5: invalid character 'c' at 1:1

エラーが起こった条件

  • Node.js v17.6.0
  • @squoosh/cli@0.7.2

以下を実行

$ squoosh-cli --webp *.jpg
エラー文
/Users/*/.nodebrew/node/v17.6.0/lib/node_modules/@squoosh/cli/node_modules/json5/lib/parse.js:1083
    const err = new SyntaxError(message)
                ^

SyntaxError: JSON5: invalid character 'c' at 1:1
    at syntaxError (/Users/*/.nodebrew/node/v17.6.0/lib/node_modules/@squoosh/cli/node_modules/json5/lib/parse.js:1083:17)
    at invalidChar (/Users/*/.nodebrew/node/v17.6.0/lib/node_modules/@squoosh/cli/node_modules/json5/lib/parse.js:1028:12)
    at Object.value (/Users/*/.nodebrew/node/v17.6.0/lib/node_modules/@squoosh/cli/node_modules/json5/lib/parse.js:287:15)
    at lex (/Users/*/.nodebrew/node/v17.6.0/lib/node_modules/@squoosh/cli/node_modules/json5/lib/parse.js:78:42)
    at Object.parse (/Users/*/.nodebrew/node/v17.6.0/lib/node_modules/@squoosh/cli/node_modules/json5/lib/parse.js:25:17)
    at Command.processFiles (file:///Users/*/.nodebrew/node/v17.6.0/lib/node_modules/@squoosh/cli/src/index.js:172:60) {
  lineNumber: 1,
  columnNumber: 1
}

※ユーザー名は*で置き換えています

このエラーについてもGitHubのissuesに情報がありました。

--webp takes a JSON parameter to configure the encoder, that’s why it’s trying to decode the first file name using JSON5. If you use --webp '{}', it should work.
RuntimeError: abort(SyntaxError: JSON5: invalid character 'w' at 1:1) #956

$ squoosh-cli --webp '{}' *.jpgとすることで解決しました。

(余談)JPEGをWebPに変換したらファイルサイズが大きくなった!?

圧縮率80%で書き出したJPEGを、Squooshでquality:100%でWebPに変換したところ、ファイルサイズ(重さ)が元画像より大きくなってしましました。

WebPは複数の圧縮アルゴリズムを組み合わせて、設定された品質(quality)になるように画像を変換します。JPEGからWebPに変換するアルゴリズムでは、qualityの値は元の画像に対して相対的に100%の品質というわけではなく、絶対的な評価値になります。(他のアルゴリズムでは相対的な値であることもあるそうです)

そのため、圧縮率80%のJPEGをquality:100%でWebPに変換した場合、元のJPGEGより多くの情報を保持するために必要なビット数が増加し、ファイルサイズが大きくなってしまうことがあります。

Discussion