Image by Ryland Dean / Unsplash

Sharp を使用した HEIC/HEIF 画像変換における問題点

✍🏼 作成日 2023年01月05日    💡 更新日 2023年01月09日
❗️ 注意:この記事が作成されてから既に 日が経過しています。情報の鮮度にご注意ください
🖥  説明:Sharpを使用してHEIC形式の画像を圧縮する際に遭遇した問題と解決過程の記録
📚  Craft にも公開: https://www.craft.do/s/mHmLa16eR5YWPi

iOS 11以降、AppleのカメラはデフォルトのフォーマットとしてHEICを採用しています。このフォーマットは画質を損なうことなく写真のファイルサイズを大幅に削減できると言われています。设置-相机-格式で確認できますが、「高効率」がHEICフォーマット、「互換性最優先」が従来のJPEGフォーマットを指します。HEICフォーマットの詳細についてはこちらをご覧ください。

これに伴う問題点として、現在すべてのWebブラウザがHEICフォーマットの画像表示に対応していないため、このフォーマットの画像を変換する方法が課題となっています。

私はよくAppleの写真アプリから画像を直接Craftにドラッグ&ドロップまたはコピー&ペーストするので、このフォーマットの画像を個人ブログにアップロードしても表示されません。そのため、PNGやJPEGなどの汎用フォーマットに変換する必要があります。ここではSharpライブラリを使用してWebPに変換しています(HEICからPNGへの変換はファイルサイズが大きくなりすぎるため)。

GitHub - lovell/sharp: High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images. Uses the libvips library. 高性能なNode.js画像処理ライブラリ。JPEG、PNG、WebP、AVIF、TIFF画像のリサイズが最速で可能。libvipsライブラリを使用。- GitHub - lovell/sharp: High performance Node.js image proc... https://github.com/lovell/sharp

通常のフォーマット(JPEGからPNGなど)の変換であれば問題ありませんが、HEICから他のフォーマットへの変換では問題が発生します。この処理においてSharpはlibvipsというプラットフォームネイティブのツールライブラリに依存しているためです:

GitHub - libvips/libvips: A fast image processing library with low memory needs. 低メモリ要件で高速な画像処理ライブラリ。- GitHub - libvips/libvips: A fast image processing library with low memory needs. https://github.com/libvips/libvips

このツールをグローバルにインストールしていない場合、HEICフォーマットの画像処理時に次のエラーが発生します:

1
2
3
4
5
(node:11469) UnhandledPromiseRejectionWarning: Error: source: bad seek to 807962
heif: Unsupported feature: Unsupported codec (4.3000)

(Use `node --trace-warnings ...` to show where the warning was created)
(node:11469) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)

HEICフォーマットのファイルを処理できないという内容です。

問題が発生したらまず公式リポジトリのissueで解決策を探します。やはり同様の問題に遭遇した人がいました:

heif: unsupported feature · Issue #2924 · lovell/sharp 最新バージョンを使用していますか?npm ls sharpで報告される現在のバージョンと、npm view sharp dist-tags.latestで報告される最新バージョンは同じですか?2.9.1 再現手順... https://github.com/lovell/sharp/issues/2924

そこで彼のアドバイスに従い、homebrewを使用してlibvipsをインストールしました:

libvips "低メモリ要件で高速な画像処理ライブラリ" https://www.libvips.org/install.html

このプロセスには時間がかかり、ログが継続的に表示されます。エラーなく完了するまで待機してください。

その後、期待を込めて再度Sharp npm install sharpをインストールし、次のメッセージが表示されることを期待しました:

1
sharp: Detected globally-installed libvips v8.13.3

このメッセージはSharpのインストールスクリプトがローカルにlibvipsがインストールされていることを検知したことを示します。しかし再度Sharpをインストールすると、依然として既にダウンロード済みのキャッシュが使用されていました:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> sharp@0.31.3 install /Users/x/Code/test2/node_modules/sharp
> (node install/libvips && node install/dll-copy && prebuild-install) || (node install/can-compile && node-gyp rebuild && node install/dll-copy)

sharp: Using cached /Users/x/.npm/_libvips/libvips-8.13.3-darwin-x64.tar.br
sharp: Integrity check passed for darwin-x64
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN test2@1.0.0 No description
npm WARN test2@1.0.0 No repository field.

added 45 packages from 206 contributors and audited 45 packages in 4.5s

10 packages are looking for funding
run `npm fund` for details

found 0 vulnerabilities

そこで上記ログに表示されたキャッシュファイルrm -rf /Users/x/.npm/_libvips/ディレクトリを削除し、再度実行しましたが、依然としてシステムバージョンのlibvipsを使用できませんでした:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> sharp@0.31.3 install /Users/x/Code/test2/node_modules/sharp
> (node install/libvips && node install/dll-copy && prebuild-install) || (node install/can-compile && node-gyp rebuild && node install/dll-copy)

sharp: Downloading https://github.com/lovell/sharp-libvips/releases/download/v8.13.3/libvips-8.13.3-darwin-x64.tar.br
sharp: Integrity check passed for darwin-x64
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN test2@1.0.0 No description
npm WARN test2@1.0.0 No repository field.

added 45 packages from 206 contributors and audited 45 packages in 10.727s

10 packages are looking for funding
run `npm fund` for details

found 0 vulnerabilities

そこでこのパッケージのデバッグを決意しました。

まずログから始め、バージョン判定と思われるログUsing cachedをキーワードとして、node_modules/sharpパッケージ内で関連キーワードを検索し、sharp/install/libvips.jsで以下のコードを発見しました:

1
libvips.log(`Using cached ${tarPathCache}`);

さらに調査を進めると、この関数の上部にシステム組み込みのlibvipsを使用するか、HEICフォーマットに対応していない独自コンパイルのバイナリパッケージを使用するかを決定するロジックがありました:

1
const useGlobalLibvips = libvips.useGlobalLibvips();

ここが核心部分です。この関数をデバッグしていると、isRosettaの判定時にtrueが返されていることがわかりました。falseが返されればシステム組み込みのlibvipsが使用され、そうでなければ独自コンパイルのlibvipsが使用される仕組みです:

Image

ここで奇妙な現象が発生しました。デバッグ時にprocess.archの出力がx64で、このコードのログ出力がsysctl.proc_translated: 1でした:

Image

しかし、直接コンソールでこのコードを実行すると、出力はsysctl.proc_translated: 0でした:

Image

ここで私は、nodeのバージョン問題が原因で自身でコンパイルしたlibvipsを使用していると結論づけました。そこでnvmを使用して、デフォルトのnodeバージョンをarm64バージョンに切り替えた後、再度npm i sharpを実行すると問題は解決しました。

以下はこのプロセスで使用する可能性のあるコードです:

1
2
3
4
5
6
7
8
9
10
// 列出 nvm 版本
nvm list
// 输出当前电脑的架构
arch // 如果输出的是 arm64 表示就是 M1 芯片
// 使用最新的稳定版
nvm install stable // 我的机器给我安装了 19.3.0 的 arm64 版本的 node
// 将该 node 作为默认
nvm alias default v19.3.0
// 在当前终端使用 19.3.0 版本的 node
nvm use 19.3.0

その後、作者にPRを提出してisRosettaのロジックを修正しました:

Get real architecture of M1 Mac regardless of Rosetta by Xheldon · Pull Request #3514 · lovell/sharp Rosettaやnodeバージョンに関係なくM1 Macの実際のアーキテクチャを取得する、#3239を参照 https://github.com/lovell/sharp/pull/3514

しかし作者は、この関数がそうなっているのには理由があると説明しました。そのため私は、Sharpをインストールする際にユーザーがグローバルにインストールされたlibvipsパッケージを使用していない理由を通知することを提案し、最終的に作者は私の意見を受け入れました:

Install: log Rosetta detection, improve related docs · lovell/sharp@5be36c2 "高性能なNode.js画像処理、JPEG、PNG、WebP、AVIF、TIFF画像のリサイズに最速のモジュール。libvipsライブラリを使用。- インストール:Rosetta検出をログに記録、関連ドキュメントを改善 · lovell/sharp@5be36c2" https://github.com/lovell/sharp/commit/5be36c2deb735577fc76fa52242836d40df276bd

- EOF -
この記事の初出: Sharp を使用した HEIC/HEIF 画像変換における問題点 - Xheldon Blog