목표
Lambda Function에 python을 통해
영상편집(영상 + 이미지 + 텍스트 이미지화)을 하는 Function을 만드는게 목표.
환경
- python 3.7
- moviepy (ffmpeg + imageMagick)
- Lambda + Api Gateway + CloudWatch + S3
프로세스
- API Gateway를 통한 동영상 합성 요청
- Lambda 에서 Trigger를 통한 동영상 합성 진행
- S3로 바로 결과 파일 생성
2. Lambda 에서 Trigger를 통한 동영상 합성 진행항목을 중점적으로 다뤄본다.
해당 내용을 진행하기 전에 Lambda에 대해서 알아두어야할 점이 있다.
-
OS( Amazone Linux2 )
-
서버의 경로가 존재한다.
-
Lambda는 소스파일을 50M 업로드 제한이 있다.
-
Lambda 기본 Storage를 활용한다. ( /tmp ) 20GB제한
-
Lambda Layer를 활용한다( /opt )
-
해당 내용은 소스코드를 참조하여 진행하면된다.
https://github.com/serverlesspub/imagemagick-aws-lambda-2
해당 코드는 Layer에 ImageMagick이라는 Utilitiy를 배포할수있는 스크립트이다.
내 버전에서는 freetype 을 지원하여하는데 해당버전을 지원하게 하려면 수정이 필요하였다.
https://github.com/serverlesspub/imagemagick-aws-lambda-2/blob/master/Makefile_ImageMagick
위 파일의 내용을 수정해야하는데
https://github.com/serverlesspub/imagemagick-aws-lambda-2/issues/13
위 이슈를 참고하여 수정하였다.
LIBPNG_VERSION ?= 1.6.37
LIBJPG_VERSION ?= 9c
OPENJP2_VERSION ?= 2.3.1
LIBTIFF_VERSION ?= 4.0.9
BZIP2_VERSION ?= 1.0.6
LIBWEBP_VERSION ?= 0.6.1
IMAGEMAGICK_VERSION ?= 7.0.8-45
**FREETYPE_VERSION ?= 2.8.1**
TARGET_DIR ?= /opt/
PROJECT_ROOT = $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
CACHE_DIR=$(PROJECT_ROOT)build/cache
.ONESHELL:
CONFIGURE = PKG_CONFIG_PATH=$(CACHE_DIR)/lib/pkgconfig \
./configure \
CPPFLAGS=-I$(CACHE_DIR)/include \
LDFLAGS=-L$(CACHE_DIR)/lib \
--disable-dependency-tracking \
--disable-shared \
--enable-static \
--prefix=$(CACHE_DIR)
**##freetype
FREETYPE_SOURCE=freetype-$(FREETYPE_VERSION).tar.gz
$(FREETYPE_SOURCE):
curl -LO http://www.imagemagick.org/download/delegates//$(FREETYPE_SOURCE)
$(CACHE_DIR)/lib/freetype.a: $(FREETYPE_SOURCE)
tar xf $<
cd freetype-*
$(CONFIGURE)
make clean
make install**
## libjpg
LIBJPG_SOURCE=jpegsrc.v$(LIBJPG_VERSION).tar.gz
$(LIBJPG_SOURCE):
curl -LO http://ijg.org/files/$(LIBJPG_SOURCE)
$(CACHE_DIR)/lib/libjpeg.a: $(LIBJPG_SOURCE)
tar xf $<
cd jpeg*
$(CONFIGURE)
make
make install
## libpng
LIBPNG_SOURCE=libpng-$(LIBPNG_VERSION).tar.xz
$(LIBPNG_SOURCE):
curl -LO http://prdownloads.sourceforge.net/libpng/$(LIBPNG_SOURCE)
$(CACHE_DIR)/lib/libpng.a: $(LIBPNG_SOURCE)
tar xf $<
cd libpng*
$(CONFIGURE)
make
make install
# libbz2
BZIP2_SOURCE=bzip2-$(BZIP2_VERSION).tar.gz
$(BZIP2_SOURCE):
curl -LO http://prdownloads.sourceforge.net/bzip2/bzip2-$(BZIP2_VERSION).tar.gz
$(CACHE_DIR)/lib/libbz2.a: $(BZIP2_SOURCE)
tar xf $<
cd bzip2-*
make libbz2.a
make install PREFIX=$(CACHE_DIR)
# libtiff
LIBTIFF_SOURCE=tiff-$(LIBTIFF_VERSION).tar.gz
$(LIBTIFF_SOURCE):
curl -LO http://download.osgeo.org/libtiff/$(LIBTIFF_SOURCE)
$(CACHE_DIR)/lib/libtiff.a: $(LIBTIFF_SOURCE) $(CACHE_DIR)/lib/libjpeg.a
tar xf $<
cd tiff-*
$(CONFIGURE)
make
make install
# libwebp
LIBWEBP_SOURCE=libwebp-$(LIBWEBP_VERSION).tar.gz
$(LIBWEBP_SOURCE):
curl -L https://github.com/webmproject/libwebp/archive/v$(LIBWEBP_VERSION).tar.gz -o $(LIBWEBP_SOURCE)
$(CACHE_DIR)/lib/libwebp.a: $(LIBWEBP_SOURCE)
tar xf $<
cd libwebp-*
sh autogen.sh
$(CONFIGURE)
make
make install
## libopenjp2
OPENJP2_SOURCE=openjp2-$(OPENJP2_VERSION).tar.gz
$(OPENJP2_SOURCE):
curl -L https://github.com/uclouvain/openjpeg/archive/v$(OPENJP2_VERSION).tar.gz -o $(OPENJP2_SOURCE)
$(CACHE_DIR)/lib/libopenjp2.a: $(OPENJP2_SOURCE) $(CACHE_DIR)/lib/libpng.a $(CACHE_DIR)/lib/libtiff.a
tar xf $<
cd openjpeg-*
mkdir -p build
cd build
PKG_CONFIG_PATH=$(CACHE_DIR)/lib/pkgconfig cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=$(CACHE_DIR) \
-DBUILD_SHARED_LIBS:bool=off \
-DBUILD_CODEC:bool=off
make clean
make install
## ImageMagick
IMAGE_MAGICK_SOURCE=ImageMagick-$(IMAGEMAGICK_VERSION).tar.gz
$(IMAGE_MAGICK_SOURCE):
curl -L https://github.com/ImageMagick/ImageMagick/archive/$(IMAGEMAGICK_VERSION).tar.gz -o $(IMAGE_MAGICK_SOURCE)
LIBS:=$(CACHE_DIR)/lib/libjpeg.a \
$(CACHE_DIR)/lib/libpng.a \
$(CACHE_DIR)/lib/libopenjp2.a \
$(CACHE_DIR)/lib/libtiff.a \
$(CACHE_DIR)/lib/libbz2.a \
**$(CACHE_DIR)/lib/freetype.a \**
$(CACHE_DIR)/lib/libwebp.a
$(TARGET_DIR)/bin/identify: $(IMAGE_MAGICK_SOURCE) $(LIBS)
tar xf $<
cd ImageMa*
PKG_CONFIG_PATH=$(CACHE_DIR)/lib/pkgconfig \
./configure \
CPPFLAGS=-I$(CACHE_DIR)/include \
LDFLAGS=-L$(CACHE_DIR)/lib \
--disable-dependency-tracking \
--disable-shared \
--enable-static \
--prefix=$(TARGET_DIR) \
--enable-delegate-build \
--without-modules \
--disable-docs \
--without-magick-plus-plus \
--without-perl \
--without-x \
--disable-openmp
make clean
make all
make install
libs: $(LIBS)
all: $(TARGET_DIR)/bin/identify
수정을 하고 깃허브 README.md파일을 참고로 배포를 진행 하면 Layer로 배포가 된다.
Layer에 적용한 모습
위 사진처럼 Layer에 내가 만든파일을 적용하게되면
/opt 경로를 접근할수 있게된다.
위의 상태로 소스코드만 작업해주면 된다.
/tmp의 경로에는 image, video가 떨어질 경로
import json
import os
import stat
import shutil
import boto3
import datetime
lambda_tmp_dir = '/tmp'
image_path = "{0}/{1}".format(lambda_tmp_dir, "images")
video_path = "{0}/{1}".format(lambda_tmp_dir, "video")
video_name = "video.mp4"
video_bucket = "heesun-video"
s3 = boto3.client ('s3')
s3.download_file('heesun','font/malgun.ttf','/tmp/malgun.ttf')
from moviepy.editor import *
from moviepy.config import *
change_settings({"IMAGEMAGICK_BINARY": "/opt/bin/magick"})
print('Loading function')
def prepare_path(target):
if os.path.exists(target):
shutil.rmtree(target)
os.mkdir(target)
def move_video(video_file, bucket, dest_key):
video = open(video_file,"r")
s3.upload_file(
video_file,bucket,dest_key,
ExtraArgs={'ACL':'public-read'}
)
# functions
def funcHeight(startSec, t):
return 50+((startSec+t)*30)
def makeVideoFileClip(path, hasMask):
return (VideoFileClip(path, has_mask=hasMask))
def makeTextClip(msg, fpath, fcolor, fsize):
return (TextClip(msg, font=fpath, color=fcolor, fontsize=fsize))
# 텍스트가 위에서 아래로 내려오는 애니메이션
def applyTextAnimation(tclip, startSec, endSec):
txt_col = tclip.on_color(size=(200,30), color=(0,0,0), pos=(6,'center'), col_opacity=0.6)
txt_mov = (txt_col.set_pos(lambda t: ('center', funcHeight(startSec, t))))
return (txt_mov.set_start(startSec).set_end(endSec))
def makeImageClip(path, startSec, endSec):
return (ImageClip(path).set_start(startSec).set_end(endSec))
def createVideo(tTxt, tColor, tSize, iPath, startSec, endSec):
# Create Image
iclip = (makeImageClip(iPath,startSec,endSec))
# Create Text
txt = (makeTextClip(tTxt, "/tmp/malgun.ttf", tColor, tSize))
tclip = (applyTextAnimation(txt,startSec,endSec))
# txt + image
return (CompositeVideoClip([iclip, tclip]))
def lambda_handler(event, context):
tdatetime = datetime.datetime.now()
prefix = tdatetime.strftime('%Y/%m/%d/')
prepare_path(video_path)
video_file = "{0}/{1}".format(video_path, video_name)
arr = [("등장!!", "http://heesun.s3-ap-northeast-1.amazonaws.com/first.jpg", 2, 3), ("또또또!! 등장!!", "http://heesun.s3-ap-northeast-1.amazonaws.com/two.jpg", 6, 7)]
clip = (makeVideoFileClip('https://heesun.s3-ap-northeast-1.amazonaws.com/background.mp4', True))
arr_final = [clip]
for (txt, img, startsec, endsec) in arr:
arr_final.append(createVideo(txt, 'white', 24, img, startsec, endsec))
final = CompositeVideoClip(arr_final)
final.subclip(0,8).write_videofile(video_file,fps=24,codec='libx264',temp_audiofile="/tmp/test.mp4")
final.close()
ymd = prefix.split('/')
video_key = "{0}/{1}/{2}.mp4".format(ymd[0], ymd[1],"".join(ymd))
move_video(video_file, video_bucket, video_key)
os.system ('cd /tmp && ls -al')
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
소스는 미흡하지만 원하는대로 동작이 되었다.
위 코드 내용에서 필요한것들은
- S3 버킷
- 이미지 2가지 ( 위 버킷에 넣을 두가지 )
두가지 이미지에 텍스트가 합성되어 추출된다.
해결된 사항
audio=True 할 시에 writing 되는 path가 /tmp 를 바라보지 않고 있어서 writing이 되지 않음. → 설정 필요
해당 내용은 임시로 Audio file이 Writing할때 경로를 지정해주어야 하는데 해당 부분이 없어서 시스템경로에 Writing되면서 발생된다. temp_audiofile 라는 parameter를 추가해주면 된다.
final.write_videofile(video_file,fps=24,codec='libx264',temp_audiofile="/tmp/test.mp4")
코어(라이브러리 파일)쪽에 손을 대는 방법이다. 현재 둘다 적용이 되어있다.
# moveipy.video.VideoClip.py
# 306 line
audiofile = (tempDirPath + "/" + name + Clip._TEMP_FILES_PREFIX +
"wvf_snd.%s" % audio_ext)
해결되지 않은 사항
-
Lambda Function Call이 반복될 때 반복적인 다운로드, 프로세스를 제거해야 함.
-
CloudWatch를 통해서 logging을 추가 해야 함.
-
tmp 디렉토리 안에 동영상파일이 계속 남아있는지 로그 체크 해봐야함.
⇒ 남아있진 않음. 그러나 여러차례 호출하는 api로 작업을 진행 및 테스트 해봐야 함.
⇒ 남아있다면 만들고 업로드 후 지워주는 프로세스 추가
Lambda 리소스 성능
램은 어느 일정 수준 이후로는 더 이상 초가 증가 되지 않으나 넉넉한 용량일 때 생성속도가 빨라진다.
이미지, 동영상 용량등이 생성 속도에 영향을 준걸로 확인 어느정도 크기 이후에는 큰 영향이 없음
이전에 기록했던 영상 합성 Lambda 함수를 다시 정리해 보았다.
영상 관련된 부분을 작업하려면 ffmpeg가 필요하였다.
되게 고생하면서 작업했던 기억이 있는데 글을 정리하려고보니 두서없이 정리된거 같다.