본문 바로가기

개발

Lambda에서 Sharp.js 라이브러리로 HEIC, HEIF 이미지 포맷 지원하게 하기

Sharp.js는 Node.js 기반 고속 이미지 처리 라이브러리이다.
리사이징시 ImageMagick과 GraphicsMagick 같은 라이브러리 대비해서 4~5배 정도 빠르다고 하는데, 이는 내부적으로 libvips라는 라이브러리를 사용하기 때문이다.

 

문제

Sharp.js는 좋은 라이브러리인 것 같다. 레퍼런스가 많고, libvips를 사용하기 때문에 낮은 메모리에서 상당한 속도로 이미지를 처리할 수 있다.
하지만 문제는 사진이 .heic .heif 확장자인 경우이다. 특히 iPhone, Mac 등 애플 기기에서 찍은 사진들은 .heic인 경우들이 많다.
하지만 Sharp.js는 라이센스 문제로 HEIC, HEIF 확장자를 지원하지도, 지원할 계획도 없다고 한다.


Sharp.js는 사용하는 libvips를 따로 Github repository로 관리하고 있고, 결과물을 보면 HEIC, HEIF 관련 기능이 빠져있다.

 

해결

해결방법은 크게 아래 순서로 나눌 수 있다.

  1. libvips가 설치된 환경에서 Sharp.js를 설치한다.
  2. Lambda 계층에서 사용할 libvips를 빌드한다.
  3. Lambda에 계층과 환경변수를 설정한다.

1. libvips가 설치된 환경에서 Sharp.js를 설치한다.

Sharp.js는 libvips가 설치되어 있는 환경에서는 Sharp.js용 libvips를 설치하지 않는다.
나는 로컬 Mac 환경에서 진행했다.

$ brew install libvips

위 명령어를 입력하면 libvips가 설치되는데, 이 상태로 Sharp.js 라이브러리를 설치하면 된다. 나는 Lambda에 사용할 것이기 때문에 아키텍처를 선택하여 설치했다.

$ npm install --os=linux --cpu=x64 sharp@0.33.5

추가로. 아키텍처를 선택해서 설치할경우 버그가 있는지 node_modules/@img 가 설치가 안되었는데, 아래 명령어를 이용해서 강제로 설치해주었다.

$ npm install @img/sharp-darwin-arm64 @img/sharp-libvips-darwin-arm64 @img/sharp-libvips-linux-x64 @img/sharp-libvips-linuxmusl-x64 @img/sharp-linux-x64 @img/sharp-linuxmusl-x64 --force
  • 설치 후에는 node_modules/sharp/vendor 폴더가 없어야 한다. (있으면 로컬에 libvips가 잘 설치되었는지 확인)

2. Lambda 계층에서 사용할 libvips를 빌드한다.

나는 이 작업을 Github workflow를 사용했다.

  • workflow는 빌드 과정을 코드로 기록할 수 있고 다시 빌드 할 수도 있으며, container를 사용해서 빌드환경을 조절할 수도 있다.

작성한 코드에 대해서 간단히만 설명하자면

  container:
    image: amazonlinux:2023

빌드 환경을 Lambda 런타임과 똑같이 맞춘다. C, C++ 컴파일을 하기 때문에 동일해야 한다.

  • 리눅스 배포판만 동일하면 안된다. 배포판 버전도 동일해야 한다. CLIB 버전이 달라지기 때문에

 

steps:
- name: Update package list
...
- name: Build additional libraries
...

libvips 컴파일에 필요한 의존성들을 다운로드 하는 과정이다. dnf에 없는 것들은 curl로 다운로드 받았다.

  - name: Meson build

libvips를 컴파일한다.

 

 

<Workflow 전체>

(코드가 깔끔하지는 않지만 도움이 되길 바라며 올립니다.)

name: Build libvips

on:
  workflow_dispatch:

jobs:
  build-libvips:
    runs-on: ubuntu-latest

    # GLIBC 버전 호환을 위해 Amazon Linux 2023 사용 (Lambda Node.js 20.x Linux version)
    container:
      image: amazonlinux:2023

    steps:
      - name: Update package list
        run: |
          dnf groupinstall -y "Development Tools" && \
          dnf install -y \
            glib2-devel \
            expat-devel \
            libjpeg-turbo-devel \
            libpng-devel \
            giflib-devel \
            libexif-devel \
            librsvg2-devel \
            libtiff-devel \
            lcms2-devel \
            gobject-introspection-devel \
            cmake \
            nasm \
            pkg-config \
            meson \
            ninja-build

      # dnf 에 없는 라이브러리만 빌드
      - name: Build additional libraries
        run: |
          PREFIX_PATH=/usr/local
          LIB_PATH=$PREFIX_PATH/lib
          export PKG_CONFIG_PATH=$LIB_PATH/pkgconfig:$PKG_CONFIG_PATH
          export LD_LIBRARY_PATH=$LIB_PATH:$LD_LIBRARY_PATH

          # Build libwebp (WebP 이미지 지원)  
          echo "Building libwebp..."
          curl -L https://github.com/webmproject/libwebp/archive/v1.5.0.tar.gz | tar zx
          cd libwebp-1.5.0 && ./autogen.sh && ./configure --enable-libwebpmux --prefix=$PREFIX_PATH
          make -j$(nproc) V=0 && make install


          # libde265 (HEVC/H.265 비디오 디코딩 지원), 불필요한 바이너리는 제외하고 빌드 (--disable-dec265 --disable-sherlock265)
          echo "Building libde265..."
          curl -L https://github.com/strukturag/libde265/releases/download/v1.0.15/libde265-1.0.15.tar.gz | tar zx
          cd libde265-1.0.15

          # libtool 버전 불일치 문제 해결 (This is libtool 2.4.7, but the definition of this LT_INIT comes from libtool 2.4.6.)
          libtoolize --force --copy # libtool 관련 파일 복사.
          aclocal # m4 디렉토리에 있는 매크로들을 모아서 aclocal.m4 파일을 재생성
          autoheader # config.h.in 파일을 생성 (매크로, 환경 변수를 위한 템플릿 파일이라고함)
          automake --add-missing # Makefile.in 파일 재생성 (Makefile 템플릿 파일?)
          autoconf # configure 파일 재생성

          ./autogen.sh && ./configure --disable-dec265 --disable-sherlock265 --prefix=$PREFIX_PATH
          make -j$(nproc) V=0 && make install


          # Build x265 (HEVC/H.265 비디오 인코딩 지원) with CMake
          echo "Building x265..."
          curl -L https://bitbucket.org/multicoreware/x265_git/downloads/x265_4.1.tar.gz | tar zx
          cd x265_4.1/build/linux
          cmake -DCMAKE_INSTALL_PREFIX=$PREFIX_PATH -DCMAKE_BUILD_TYPE=Release ../../source
          make -j$(nproc) && make install
          cd ../../..


          # Build libaom (AV1 코덱 지원) with CMake
          echo "Building libaom..."
          curl -L https://storage.googleapis.com/aom-releases/libaom-3.9.1.tar.gz | tar zx
          cd libaom-3.9.1
          mkdir build-libaom && cd build-libaom
          cmake -DCONFIG_AV1_ENCODER=1 -DBUILD_SHARED_LIBS=1 -DENABLE_TESTS=0 -DENABLE_DOCS=0 -DCMAKE_INSTALL_PREFIX=$PREFIX_PATH ..
          make -j$(nproc) V=1 && make install
          cd ../..


          # Build libheif (HEIC 이미지 지원) with CMake
          echo "Building libheif..."
          curl -L https://github.com/strukturag/libheif/releases/download/v1.19.7/libheif-1.19.7.tar.gz | tar zx
          cd libheif-1.19.7
          mkdir build-libheif && cd build-libheif
          cmake -DCMAKE_INSTALL_PREFIX=/usr/local ..
          make -j$(nproc) && make install
          cd ../..

      - name: Download libvips v8.16.0
        run: |
          curl -LO https://github.com/libvips/libvips/releases/download/v8.16.0/vips-8.16.0.tar.xz
          tar -xJf vips-8.16.0.tar.xz

      - name: Meson build
        run: |
          cd vips-8.16.0
          export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH

          meson setup build \
            -Ddefault_library=shared \
            -Dheif=enabled \
            -Dheif-module=disabled \
            -Dlcms=enabled \
            --buildtype=release \
            --libdir=lib \
            --prefix=$GITHUB_WORKSPACE/libvips
          meson compile -C build
          meson install -C build

      - name: Copy shared libraries
        run: |
          mkdir -p $GITHUB_WORKSPACE/libvips/lib

          # libwebp
          cp /usr/local/lib/libwebp.so* $GITHUB_WORKSPACE/libvips/lib/

          # libde265
          cp /usr/local/lib/libde265.so* $GITHUB_WORKSPACE/libvips/lib/

          # x265
          cp /usr/local/lib/libx265.so* $GITHUB_WORKSPACE/libvips/lib/

          # libaom
          cp /usr/local/lib64/libaom.so* $GITHUB_WORKSPACE/libvips/lib/

          # libheif
          cp /usr/local/lib64/libheif.so* $GITHUB_WORKSPACE/libvips/lib/

          # -----------------------------------------

          # libvips (의존성 복사)
          ldd $GITHUB_WORKSPACE/libvips/bin/vips | grep "/usr/lib" | awk '{print $3}' | xargs -I '{}' cp '{}' $GITHUB_WORKSPACE/libvips/lib/

          # 복사된 모든 .so 파일에 대해 ldd 적용 (의존성 복사)
          for lib in $GITHUB_WORKSPACE/libvips/lib/*.so*; do
            echo "Processing dependencies for $lib"
            ldd "$lib" | grep "=> /" | awk '{print $3}' | xargs -I '{}' cp '{}' $GITHUB_WORKSPACE/libvips/lib/ || true
          done

          # GLIBC 버전문제 해결 (런타임 오류)
          rm -rf $GITHUB_WORKSPACE/libvips/lib/libc.so*
          rm -rf $GITHUB_WORKSPACE/libvips/lib/libstdc++.so*
      - name: Package libvips
        run: |
          cd $GITHUB_WORKSPACE
          tar -czf libvips-8.16.0-linux-x86_64.tar.gz libvips

      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: libvips-8.16.0-linux-x86_64
          path: libvips-8.16.0-linux-x86_64.tar.gz

 

3. Lambda에 계층과 환경변수를 설정한다.

이제 위 workflow의 결과물로 나온 파일을 zip으로 압축해서 Lambda 계층으로 업로드하고 Lambda 함수에 연결하면 된다.
추가로 설정이 필요한데, Sharp.js에서 libvip 라이브러리 경로를 찾지 못할 것이다.

  • 환경변수를 아래와 같이 추가설정 해주자.
LD_LIBRARY_PATH /usr/local/lib:/opt/libvips/lib

 


P.S
HEIC는 HEIF에 HEVC 압축코덱을 적용한 것이라고 한다.