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 이후로 애플 카메라는 기본 포맷을 HEIC로 설정했는데, 이는 사진 품질을 저하시키지 않으면서 파일 크기를 크게 줄일 수 있다고 합니다. 设置-相机-格式에서 확인할 수 있듯이, '고효율’은 HEIC 포맷을, '호환성 최적’은 기존 JPEG 포맷을 의미합니다. HEIC 포맷에 대한 더 자세한 내용은 여기에서 볼 수 있습니다.

이로 인해 발생하는 문제는 현재 모든 웹 브라우저가 HEIC 포맷 이미지를 표시하지 못한다는 점입니다. 따라서 이 포맷의 이미지를 어떻게 변환할지가 중요한 문제가 되었습니다.

저는 종종 애플 사진 앱에서 이미지를 직접 드래그하거나 복사해 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 포맷 파일을 처리할 수 없다는 메시지입니다.

문제가 발생하면 가장 먼저 공식 저장소의 이슈에서 해결 방법을 찾아보았습니다. 비슷한 문제를 겪은 사람이 있었습니다:

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

여기서 저는 노드 버전 문제로 인해 자체 컴파일된 libvips를 사용하고 있다는 결론을 내렸습니다. 그래서 nvm을 사용해 기본 노드 버전을 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

이후 저자는 isRosetta 로직을 수정하기 위해 PR을 제출했습니다:

Get real architecture of M1 Mac regardless of Rosetta by Xheldon · Pull Request #3514 · lovell/sharp Rosetta 또는 노드 버전에 관계없이 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