본문 바로가기

개발/AWS

[AWS] NodeJS Lambda에서 ffmpeg 사용하기

반응형

사이드 프로젝트를 람다로 구현하는 중에 하나의 허들이 있었으니 바로 ffmpeg기능을 활용하는 부분이었습니다.

 

람다 용량 제한이 25MB정도인데 ffmpeg 패키지 사이즈만 30 MB정도 됩니다. 번들링은 할 수 없다는 의미.. 

람다 자체를 dockernize해서 올리려고도 해봤으나 AWS에서 제공하는 기본 도커 이미지에 ffmpeg 를 추가하는게 쉽지 않았습니다. 반나절 정도 이걸로 삽질하다가 다른 방법을 찾아 나섰죠.

 

결국 최종적으로 선택한 방법은 Layers로 ffmpeg를 따로 구성하고 람다에 추가하여 활용하는 방식입니다.

이걸로 시도한지 두어시간만에 성공해서 하루동안의 삽질이 아름답고 허무하게 끝났네요.. 

 

1. FFMPEG 빌드 파일을 .zip으로 압축

우분투 EC2 인스턴스를 하나 띄워서, ffmpeg 를 설치하고 압축 후 로컬로 다시 scp 해옵니다.

zip으로 압축된 파일이 있어야만 layer로 추가할 수 있습니다.

자세한 방법은 제가 참고한 아래 유투브 영상을 따라하시면 됩니다.

FFMPEG 빌드 파일을 .zip으로 압축 튜토리얼: https://www.youtube.com/watch?v=3d9eXZ5s_MA  

 

2. Lambda Layer 생성

이 zip으로 lambda layer를 생성합니다. Runtime은 꼭 Node가 아니어도 상관 없습니다. 

 

3. Lambda 생성 + ffmpeg 레이어 추가

ffmpegtest라는 이름의 새 람다를 만들고 방금 생성한 Layer를 추가해주었습니다.

Lambda 생성

 

Layer추가 1

 

직접 업로드한 layer는 Custum layers에서 선택할 수 있습니다.

Layer추가 2

 

이제 ffmpeg를 사용할 준비는 다 되었으니 잘 올라와있는지 테스트를 해볼까요?

layer에 추가한 내용은 /opt 경로에서 접근하여 사용할 수 있습니다. 

노드에서 커맨드로 ffmpeg -help를 직접 수행하여 출력하도록 해보겠습니다.

import fs from "fs";
import { promisify } from "util";
import child_process from "child_process";
const exec = promisify(child_process.exec);

export const handler = async(event) => {
    console.log(await exec(`/opt/ffmpeg -help`));
    
    // TODO implement
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    return response;
};

디플로이 후 테스트해보면!

로그에 ffmpeg -help의 출력 내용을 확인해볼 수 있습니다. ffmpeg 로드는 성공했네요!

 

4. S3에 있는 파일을 ffmpeg로 분석하기 

다만 한가지 문제가 있었습니다.. Mac 로컬에서 ffmpeg를 수행할 때는 input 미디어를  remote url로 줘도 아주 잘 동작했고, lambda에 올려서도 동일하게 활용하려고 했는데요,

lambda에서는 input을 remote url로 주니 Segmentation Error 가 나오더라구요..

ffmpeg 는 미디어에 접근하여 사용할 수 없으면 아무짝에도 쓸모가 없는데...

 

어차피 처리할 미디어는 모두 S3에 올려둘 생각이었어서, 람다에서 s3에 있는 파일을 다운받아 처리하도록 구성을 수정하였습니다. 

S3에서 getObject해서 writeStream에 연결해 람다 안의 저장 경로 (/tmp/[saveFileName])에 저장하고, 해당 경로를 읽어서 ffmpeg에서 활용하는 방식입니다.

 

아래 코드를 읽어보면 아시겠지만 제가 하려던 작업은 바로 묵음구간 인식.. 

이제 아웃풋을 읽어서 배열로 구성해 반환만 하면 됩니다.

import * as AWS from "aws-sdk";
const s3 = new AWS.S3();
import fs from "fs";
import { promisify } from "util";
import child_process from "child_process";
const exec = promisify(child_process.exec);

export const run = async (mediaFileName: string) => {
  console.log(await exec(`echo hello!`));
  console.log(await exec(`/opt/ffmpeg -help`));

  const localPath = "/tmp/[saveFileName]"; // ./는 read only이므로 /tmp 경로에 저장해주어야 함
  var downParams = {
    downloadParams: {
      Bucket: [bucketName],
      Key: [fileName]
    },
    savePath: localPath,
  };

  const pm = new Promise((resolve, reject) => {
    let readStream = s3.getObject(downParams.downloadParams).createReadStream();
    let writeStream = fs.createWriteStream(downParams.savePath);
    var stream = readStream.pipe(writeStream);
    stream.on("finish", async function () {
      console.log("file downloaded successfully");
      if (fs.existsSync(localPath)) {
        console.log("file found ");
      }

      const output = await exec(
        `/opt/ffmpeg -i ${localPath} -af silencedetect=n=-30dB:d=3 -f null - 2>&1`
      );
      console.log("output", output);
      
    });
  });
  const result = await pm;
  return result;
};

 

5. Lambda Configuration

ffmpeg task는 3초 이상 필요하니 기본 타임아웃 시간은 3분정도로 늘려줬습니다.

파일을 저장해야 하니 Ephemeral Storage도 파일 용량 감당 가능한 수준으로 늘려줍니다. 

메모리도 한 번 부족하다고 떠서 1024로 늘려주었습니다.

 

결론

주말 하루를 바친 삽질이었지만 오늘 내 결론이 나서 행복하다.. 라고 생각해봅니다.

 

반응형