AWS LambdaのPython 3.8ランタイムで不足する共有ライブラリと格闘しながらMediaInfoを動かしてみた

MediaInfoをAWS LambdaのPython 3.8ランタイムで動作させ、Lambda関数内で動画ファイルのメタ情報を取得してみました。Python 3.7ランタイムの場合と異なり、Python 3.8ランタイムの場合にはMediaInfoの実行ファイルに加えて、Lambda実行環境で不足している共有ライブラリファイルをLayerに含める必要がありました。
2020.03.29

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

清水です。先日、AWS Lambda(Python)から動画やオーディオファイルの各種情報を取得できるツールMediaInfoを使い、Lambda関数内で動画ファイルのメタ情報を取得してみました。以下のエントリにまとめています。

上記エントリではLambdaのランタイムはPython 3.7を選択していました。しかし2020年3月現在、サポートされているLambdaランタイムのうちPythonの最新版はPython 3.8となります。本エントリではこのPython 3.8のランタイムでMediaInfoを動作させてみた記録を、Python 3.7ランタイムとの違いを中心にまとめてみます。具体的には、Python 3.8ではオペレーティングシステムがAmazon Linux 2となりますので該当環境でのMediaInfoのコンパイル作業を行いました。(Python 3.7ではオペレーティングシステムはAmazon Linux (いわゆる1のほう)でした。)またPython 3.7ランタイムの環境ではMediaInfoの実行ファイルのみをLayerに含めればよかったのですが、Python 3.8ランタイムの環境ではこのままだとMediaInfo実行時に共有ライブラリ関連のファイルが見つからずエラーとなってしまいます。不足する共有ライブラリを確認し、これらもMediaInfoの実行ファイルと一緒にLayerとしてLambda関数に設定を行いました。

Amazon Linux 2でのMediaInfo実行ファイルのコンパイル

まずはLambda関数内で使用するMediaInfoの実行ファイルを準備します。以前のPython 3.7で動作させたときは、オペレーティングシステムがAmazon Linux(Amazon Linux 2ではなく、1のほう)でした。AWS Lambda 開発者ガイド「AWS Lambda ランタイム」の項目を確認してみると、Python 3.8ランタイムではオペレーティングシステムはAmazon Linux 2とのことです。イメージについては「カスタム」という表記のみでした *1ので、東京リージョン(ap-northeast-1)で利用できる現段階で最新のAMIを利用しました。

  • AMI Name
    • amzn2-ami-hvm-2.0.20200304.0-x86_64-gp2
  • AMI ID
    • ami-052652af12b58691f
  • Description
    • Amazon Linux 2 AMI 2.0.20200304.0 x86_64 HVM gp2

コンパイルはこちらの手順そのままで行うことができました。つまり、Amazon Linux(Python 3.7ランタイム想定)のときと変わりはありません。詳細は以前のPython 3.7のエントリを参照ください。概要のみまとめます。また今回EC2のインスタンスタイプはt2.mediumを使用しました。

'Development Tools'libcurl-develをyumでインストールします。(前者はgroupinstallします。)

$ sudo yum groupinstall 'Development tools'
$ sudo yum install libcurl-devel

MediaInfoのソースファイルをダウンロードして付属のスクリプトでコンパイルを行います。MediaInfoのバージョンは19.09です。

$ wget https://mediaarea.net/download/binary/mediainfo/19.09/MediaInfo_CLI_19.09_GNU_FromSource.tar.xz
$ tar xvf MediaInfo_CLI_19.09_GNU_FromSource.tar.xz
$ cd MediaInfo_CLI_GNU_FromSource
$ ./CLI_Compile.sh --with-libcurl 2>&1 | tee CLI_Compile.log

コンパイルが完了すると以下のメッセージが表示されます。

MediaInfo (CLI) compiled
MediaInfo executable is MediaInfo/Project/GNU/CLI/mediainfo
For installing, cd MediaInfo/Project/GNU/CLI && make install

MediaInfo実行ファイルのみをLambda Layerに設定して動作確認

LambdaのLayer機能を使うため、MediaInfoの実行ファイルをzipにまとめます。

$ mkdir bin
$ cp -p MediaInfo_CLI_GNU_FromSource/MediaInfo/Project/GNU/CLI/mediainfo ./bin/
$ zip mediainfo_v19.09.zip ./bin/*

zipファイルを展開すると以下のような構成になっています。

$ unzip mediainfo_v19.09.zip
Archive:  mediainfo_v19.09.zip
  inflating: bin/mediainfo
$ ls -R
.:
bin  mediainfo_v19.09.zip

./bin:
mediainfo

この、Amazon Linux 2環境でコンパイルしたMediaInfoの実行ファイルのみをLayerに含んだ状態で動作するか、確認してみます。以前のエントリとは実行ファイルをコンパイルしたオペレーティングシステム環境、ランタイムのみが異なる状態ですね。手順詳細は以前のエントリをご参照ください。AWS CLIコマンドのみまとめていきます。

まずはLayerを作成します。Layer名はMediaInfo-Python38としました。

$ aws lambda publish-layer-version \
    --layer-name MediaInfo-Python38 \
    --description "MediaInfo v19.09 (Amazon Linux 2)" \
    --zip-file fileb://mediainfo_v19.09.zip \
    --compatible-runtimes python3.8

続いてLambda関数を作成します。Lambda関数に設定するPythonのコードも以前のエントリと変わりはありません。(本エントリ最後にもまとめておきます。)コードはlambda_function.pyというファイルにまとめ、MediaInfo-on-lambda.zipとしてzipファイルにまとめている状態です。

$ zip MediaInfo-on-Lambda.zip lambda_function.py
  adding: lambda_function.py (deflated 63%)
$ aws lambda create-function \
    --function-name MediaInfo-on-Lambda-Python38 \
    --runtime python3.8 \
    --role arn:aws:iam::123456789012:role/MediaInfo-on-Lambda-Role \
    --handler lambda_function.lambda_handler \
    --zip-file fileb://MediaInfo-on-Lambda.zip \
    --layers arn:aws:lambda:ap-northeast-1:123456789012:layer:MediaInfo-Python38:1 \
    --memory-size 256 \
    --timeout 60

S3からLambdaをトリガするように設定を行います。まずはLambda側にInvokeFunctionの実行を許可します。

$ aws lambda add-permission \
    --function-name MediaInfo-on-Lambda-Python38 \
    --statement-id "s3-put-event" \
    --action "lambda:InvokeFunction" \
    --principal "s3.amazonaws.com" \
    --source-arn "arn:aws:s3:::my-s3-bucket"

そしてS3バケット側に設定を行います。

$ aws s3api put-bucket-notification-configuration \
    --bucket my-s3-bucket \
    --notification-configuration '{"LambdaFunctionConfigurations": [{"LambdaFunctionArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:MediaInfo-on-Lambda-Python38", "Id": "Execute-MediaInfo-on-Lambda-Python38", "Events": ["s3:ObjectCreated:*"]}]}'

以上の状態、繰り返しになりますがつまり、Python3.7ランタイムと比べて (1) MediaInfoの実行ファイルコンパイル環境をAmazon Linux 2に変更し(2) LambdaのランタイムをPython3.8にした状態で動作を確認してみます。この変更だけですんなり動けば良いのですが……、実際にS3に動画ファイルをアップロードしLambda関数をキック、実行結果をCloudWatch Logsを確認すると以下のエラーが発生しており、MediaInfo自体が実行できていないことが確認できます。

mediainfo: error while loading shared libraries: libcurl.so.4: cannot open shared object file: No such file or directory

MediaInfo実行に不足しているライブラリと格闘

libcurl.so.4がNo such file or directory

実行エラーは共有ライブラリのlibcurl.so.4が見つからず開けない、というものでした。確認したところ該当のファイルlibcurl.so.4はMediaInfoをコンパイルしたAmazon Linux 2上の/lib64ディレクトリ配下に存在しています。

$ ls -l /lib64/libcurl.so*
lrwxrwxrwx 1 root root     16  3月 26 11:32 /lib64/libcurl.so -> libcurl.so.4.5.0
lrwxrwxrwx 1 root root     16  3月  7 03:42 /lib64/libcurl.so.4 -> libcurl.so.4.5.0
-rwxr-xr-x 1 root root 553840 10月  3 23:37 /lib64/libcurl.so.4.5.0

Lambda実行環境下ではこれが存在していない、ということのようですので、このlibcurl.so.4についてもMediaInfo実行ファイルmediainfoと一緒にLayerに設定してみます。libディレクトリを作成してその中にlibcurl.so.4のシンボリックリンク、ならびにその実態のlibcurl.so.4.5.0を配置します。(/lib64ディレクトリからのコピー時にはシンボリックリンクでコピーするよう、cpコマンドに-aオプションをつけておきます。)そしてMediaInfoの実行ファイルmediainfoが格納されているbinディレクトリとlibディレクトリを一緒のzipファイルにまとめます。

$ mkdir lib
$ cp -pa /lib64/libcurl.so.4* ./lib/
$ ls -l ./lib/
合計 544
lrwxrwxrwx 1 ec2-user ec2-user     16  3月  7 03:42 libcurl.so.4 -> libcurl.so.4.5.0
-rwxr-xr-x 1 ec2-user ec2-user 553840 10月  3 23:37 libcurl.so.4.5.0
$ zip mediainfo_v19.09_libcurl.so.4.zip ./bin/* ./lib/*
  adding: bin/mediainfo (deflated 64%)
  adding: lib/libcurl.so.4 (deflated 52%)
  adding: lib/libcurl.so.4.5.0 (deflated 52%)

zipファイルを展開すると以下のような構成となっています。

$ unzip mediainfo_v19.09_libcurl.so.4.zip
Archive:  mediainfo_v19.09_libcurl.so.4.zip
  inflating: bin/mediainfo
  inflating: lib/libcurl.so.4
  inflating: lib/libcurl.so.4.5.0
$ ls -R
.:
bin  lib  mediainfo_v19.09_libcurl.so.4.zip

./bin:
mediainfo

./lib:
libcurl.so.4  libcurl.so.4.5.0

作成したzipファイルで、Layerの新規バージョンを作成します。

$ aws lambda publish-layer-version \
    --layer-name MediaInfo-Python38 \
    --description "MediaInfo v19.09 (Amazon Linux 2) and libcurl.so.4" \
    --zip-file fileb://mediainfo_v19.09_libcurl.so.4.zip \
    --compatible-runtimes python3.8

Lambda関数で使用するLayerを変更します。

$ aws lambda update-function-configuration \
    --function-name MediaInfo-on-Lambda-Python38 \
    --layers arn:aws:lambda:ap-northeast-1:123456789012:layer:MediaInfo-Python38:2

このLayerにmediainfoとlibcurl.so.4を含んだ状態で、再度動作確認をしてみます。libcurl.so.4のエラーは出なくなりましたが、またや共有ライブラリ不足のエラーが発生してしまいます。エラー内容は以下です。

mediainfo: error while loading shared libraries: libnghttp2.so.14: cannot open shared object file: No such file or directory

libnghttp2.so.14がNo such file or directory

共有ライブラリのlibnghttp2.so.14が見つからずに開けない、ということで、こちらもlibcurl.so.4のときと同様にLayerに設定してみましょう。libnghttp2.so.14についてもMediaInfoをコンパイルしたAmazon Linux 2上の/lib64ディレクトリに存在していました。

$ ls -l /lib64/libnghttp2.so*
lrwxrwxrwx 1 root root     21  3月  7 03:42 /lib64/libnghttp2.so.14 -> libnghttp2.so.14.18.0
-rwxr-xr-x 1 root root 158280 10月  1 19:24 /lib64/libnghttp2.so.14.18.0

libcurl.so.4を格納したlibディレクトリにlibnghttp2.so.14とその実体のlibnghttp2.so.14.18.0をコピー、zipファイルに纏めていきます。

$ cp -pa /lib64/libnghttp2.so.14* ./lib/
$ ls -l ./lib/
合計 700
lrwxrwxrwx 1 ec2-user ec2-user     16  3月  7 03:42 libcurl.so.4 -> libcurl.so.4.5.0
-rwxr-xr-x 1 ec2-user ec2-user 553840 10月  3 23:37 libcurl.so.4.5.0
lrwxrwxrwx 1 ec2-user ec2-user     21  3月  7 03:42 libnghttp2.so.14 -> libnghttp2.so.14.18.0
-rwxr-xr-x 1 ec2-user ec2-user 158280 10月  1 19:24 libnghttp2.so.14.18.0
$ zip mediainfo_libcurl_libnghttp2.zip ./bin/* ./lib/*
  adding: bin/mediainfo (deflated 64%)
  adding: lib/libcurl.so.4 (deflated 52%)
  adding: lib/libcurl.so.4.5.0 (deflated 52%)
  adding: lib/libnghttp2.so.14 (deflated 52%)
  adding: lib/libnghttp2.so.14.18.0 (deflated 52%)

このzipファイルでLayerの新規バージョンを作成しLambda関数で使用するよう設定します。

$ aws lambda publish-layer-version \
    --layer-name MediaInfo-Python38 \
    --description "MediaInfo v19.09 (Amazon Linux 2) and libcurl.so.4 and libngttp2.so.14" \
    --zip-file fileb://mediainfo_libcurl_libnghttp2.zip \
    --compatible-runtimes python3.8
$ aws lambda update-function-configuration \
    --function-name MediaInfo-on-Lambda-Python38 \
    --layers arn:aws:lambda:ap-northeast-1:123456789012:layer:MediaInfo-Python38:3

このLayerにmediainfoとlibcurl.so.4、さらにlibnghttp2.so.14を含んだ状態で、もういちど動作確認をしてみます。これでエラーが発生しなくなればよいのですが……、残念ながら、libnghttp2.so.14のエラーは発生しなくなりますが、同様の共有ライブラリ不足のエラーが発生します。

mediainfo: error while loading shared libraries: libidn2.so.0: cannot open shared object file: No such file or directory

これまでのエラーと同様、共有ライブラリのlibidn2.so.0が見つからずに開けない、ということです。それならlibcurl.so.4libnghttp2.so.14のときと同じ手順で、/lib64ディレクトリからコピーしてLayerに追加、という手順で対応できそうですね。ただこの共有ライブラリ不足、いつまで続くのでしょうか……。切りがないようにも思えます。実際このlibidn2.so.0を追加しても、libssh2.so.1libldap-2.4.so.2liblber-2.4.so.2と続いていきました。どこまで続くかわからないこともあり、エラーメッセージを逐一確認して対応していく、ではなく別のやり方を検討してみます。

MediaInfoの実行に必要な共有ライブラリファイルの洗い出し

Lambda上でのMediaInfo実行時に、どれほどの共有ライブラリが不足しているかわからないので、 MediaInfo実行に必要な共有ライブラリと、Lambda実行環境に存在している共有ライブラリのそれぞれを確認してみます。

MediaInfo実行時に必要な共有ライブラリ

MediaInfoの実行に必要な共有ライブラリについて、lddコマンドを使って確認します。MediaInfoをコンパイルしたAmazon Linux 2上で確認しました。実行結果は下記です。

[ec2-user@ip-10-82-11-17 ~]$ ldd ./bin/mediainfo
        linux-vdso.so.1 (0x00007ffd8dd56000)
        libz.so.1 => /lib64/libz.so.1 (0x00007f1e9da16000)
        libcurl.so.4 => /lib64/libcurl.so.4 (0x00007f1e9d791000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f1e9d573000)
        libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f1e9d1f1000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f1e9ceb1000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f1e9cc9b000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f1e9c8f0000)
        libnghttp2.so.14 => /lib64/libnghttp2.so.14 (0x00007f1e9c6ca000)
        libidn2.so.0 => /lib64/libidn2.so.0 (0x00007f1e9c47b000)
        libssh2.so.1 => /lib64/libssh2.so.1 (0x00007f1e9c252000)
        libssl.so.10 => /lib64/libssl.so.10 (0x00007f1e9bfe3000)
        libcrypto.so.10 => /lib64/libcrypto.so.10 (0x00007f1e9bb8e000)
        libgssapi_krb5.so.2 => /lib64/libgssapi_krb5.so.2 (0x00007f1e9b942000)
        libkrb5.so.3 => /lib64/libkrb5.so.3 (0x00007f1e9b65e000)
        libk5crypto.so.3 => /lib64/libk5crypto.so.3 (0x00007f1e9b42d000)
        libcom_err.so.2 => /lib64/libcom_err.so.2 (0x00007f1e9b229000)
        libldap-2.4.so.2 => /lib64/libldap-2.4.so.2 (0x00007f1e9afd5000)
        liblber-2.4.so.2 => /lib64/liblber-2.4.so.2 (0x00007f1e9adc6000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f1e9dc2b000)
        libunistring.so.0 => /lib64/libunistring.so.0 (0x00007f1e9aaae000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007f1e9a8aa000)
        libkrb5support.so.0 => /lib64/libkrb5support.so.0 (0x00007f1e9a69b000)
        libkeyutils.so.1 => /lib64/libkeyutils.so.1 (0x00007f1e9a497000)
        libresolv.so.2 => /lib64/libresolv.so.2 (0x00007f1e9a281000)
        libsasl2.so.3 => /lib64/libsasl2.so.3 (0x00007f1e9a064000)
        libssl3.so => /lib64/libssl3.so (0x00007f1e99e0e000)
        libsmime3.so => /lib64/libsmime3.so (0x00007f1e99be8000)
        libnss3.so => /lib64/libnss3.so (0x00007f1e998c6000)
        libnssutil3.so => /lib64/libnssutil3.so (0x00007f1e99697000)
        libplds4.so => /lib64/libplds4.so (0x00007f1e99493000)
        libplc4.so => /lib64/libplc4.so (0x00007f1e9928e000)
        libnspr4.so => /lib64/libnspr4.so (0x00007f1e99052000)
        libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f1e98e2b000)
        libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007f1e98bf4000)
        librt.so.1 => /lib64/librt.so.1 (0x00007f1e989ec000)
        libpcre.so.1 => /lib64/libpcre.so.1 (0x00007f1e98788000)

後ほど比較するため、整形しておきます。

$ ldd ./bin/mediainfo | awk '{print $1;}' | grep -E "^lib" | sort
libc.so.6
libcom_err.so.2
libcrypt.so.1
libcrypto.so.10
libcurl.so.4
libdl.so.2
libgcc_s.so.1
libgssapi_krb5.so.2
libidn2.so.0
libk5crypto.so.3
libkeyutils.so.1
libkrb5.so.3
libkrb5support.so.0
liblber-2.4.so.2
libldap-2.4.so.2
libm.so.6
libnghttp2.so.14
libnspr4.so
libnss3.so
libnssutil3.so
libpcre.so.1
libplc4.so
libplds4.so
libpthread.so.0
libresolv.so.2
librt.so.1
libsasl2.so.3
libselinux.so.1
libsmime3.so
libssh2.so.1
libssl.so.10
libssl3.so
libstdc++.so.6
libunistring.so.0
libz.so.1

Lambda実行環境に存在している共有ライブラリ

続いてLambda実行環境で存在している共有ライブラリを確認します。以下のコードをLambda関数内で実行、CloudWatch Logsに出力される情報を確認していきます。

    text = subprocess.check_output(["ls", "-l", "/usr/lib64"])
    logger.info("lib64: {}".format(text))

なお、ここまでAmazon Linux 2上の操作では/lib64を確認していましたが、このディレクトリ自体はシンボリックリンク、実体は/usr/lib64でした。Lambda関数内ではこの/usr/lib64ディレクトリを確認しています。

$ ls -l /lib64
lrwxrwxrwx 1 root root 9  3月  7 03:42 /lib64 -> usr/lib64

CloudWatch Logsで確認できる該当部分の出力、\nを改行に修正後、libで始まるファイルのみ抜粋すると以下となります。(awk '{print $9;}' | grep -E "^lib" | sortというような処理を経ています。)

libBrokenLocale-2.26.so
libBrokenLocale.so.1
libSegFault.so
libacl.so.1
libacl.so.1.1.0
libanl-2.26.so
libanl.so.1
libattr.so.1
libattr.so.1.1.0
libc-2.26.so
libc.so.6
libcap.so.2
libcap.so.2.22
libcidn-2.26.so
libcidn.so.1
libcom_err.so.2
libcom_err.so.2.1
libcrypto.so.1.0.2k
libcrypto.so.10
libdl-2.26.so
libdl.so.2
libffi.so.6
libffi.so.6.0.1
libform.so.6
libform.so.6.0
libformw.so.6
libformw.so.6.0
libfreebl3.chk
libfreebl3.so
libfreeblpriv3.chk
libfreeblpriv3.so
libgcc_s-7-20180712.so.1
libgcc_s.so.1
libgmp.so.10
libgmp.so.10.2.0
libgmpxx.so.4
libgmpxx.so.4.4.0
libgssapi_krb5.so.2
libgssapi_krb5.so.2.2
libgssrpc.so.4
libgssrpc.so.4.2
libicudata.so.50
libicudata.so.50.1.2
libicui18n.so.50
libicui18n.so.50.1.2
libicuio.so.50
libicuio.so.50.1.2
libicule.so.50
libicule.so.50.1.2
libiculx.so.50
libiculx.so.50.1.2
libicutest.so.50
libicutest.so.50.1.2
libicutu.so.50
libicutu.so.50.1.2
libicuuc.so.50
libicuuc.so.50.1.2
libk5crypto.so.3
libk5crypto.so.3.1
libkdb5.so.8
libkdb5.so.8.0
libkeyutils.so.1
libkeyutils.so.1.5
libkrad.so.0
libkrad.so.0.0
libkrb5.so.3
libkrb5.so.3.3
libkrb5support.so.0
libkrb5support.so.0.1
libm-2.26.so
libm.so.6
libmemusage.so
libmenu.so.6
libmenu.so.6.0
libmenuw.so.6
libmenuw.so.6.0
libmvec-2.26.so
libmvec.so.1
libncurses.so.6
libncurses.so.6.0
libncursesw.so.6
libncursesw.so.6.0
libnsl-2.26.so
libnsl.so.1
libnspr4.so
libnss_compat-2.26.so
libnss_compat.so.2
libnss_dns-2.26.so
libnss_dns.so.2
libnss_files-2.26.so
libnss_files.so.2
libnssckbi.so
libnssutil3.so
libp11-kit.so.0
libp11-kit.so.0.3.0
libpanel.so.6
libpanel.so.6.0
libpanelw.so.6
libpanelw.so.6.0
libpcprofile.so
libpcre.so.1
libpcre.so.1.2.0
libpcre16.so.0
libpcre16.so.0.2.0
libpcre32.so.0
libpcre32.so.0.0.0
libpcrecpp.so.0
libpcrecpp.so.0.0.0
libpcreposix.so.0
libpcreposix.so.0.0.1
libplc4.so
libplds4.so
libpopt.so.0
libpopt.so.0.0.0
libpthread-2.26.so
libpthread.so.0
libresolv-2.26.so
libresolv.so.2
librt-2.26.so
librt.so.1
libselinux.so.1
libsepol.so.1
libssl.so.1.0.2k
libssl.so.10
libstdc++.so.6
libstdc++.so.6.0.24
libtasn1.so.6
libtasn1.so.6.5.3
libthread_db-1.0.so
libthread_db.so.1
libtic.so.6
libtic.so.6.0
libtinfo.so.6
libtinfo.so.6.0
libutil-2.26.so
libutil.so.1
libverto.so.1
libverto.so.1.0.0
libz.so.1
libz.so.1.2.7

不足している共有ライブラリとMediaInfo実行ファイルををまとめてLambda Layerに設定して動作確認

MediaInfo実行に必要な共有ライブラリと、Lambda実行環境に存在している共有ライブラリを比較して、前者にのみ存在し後者に含まれないものをリストアップします。MediaInfo実行に必要な共有ライブラリはmediainfo_library_list.txt、Lambda実行環境に存在している共有ライブラリはlambda_library_list.txtとしてファイルにまとめておき、commコマンドで比較、前者にのみ存在し後者に含まれない共有ライブラリファイルを抜き出してみました。

$ comm -23 mediainfo_library_list.txt lambda_library_list.txt
libcrypt.so.1
libcurl.so.4
libidn2.so.0
liblber-2.4.so.2
libldap-2.4.so.2
libnghttp2.so.14
libnss3.so
libsasl2.so.3
libsmime3.so
libssh2.so.1
libssl3.so
libunistring.so.0

これらをすべてをMediaInfoをコンパイルしたAmazon Linux 2上の/lib64ディレクトリからlibディレクトリにコピーします。先ほどのlibcurl.so.4libnghttp2.so.14の手順と同様ですね。シンボリックリンクとなっているものはその実体のファイルもコピーします。(libcrypt.so.1だけシンボリックリンクと実体ファイルの命名規則が他と異なっているので注意が必要でした。)そしてMediaInfo実行ファイルmediainfoと一緒にzipファイルにします。

$ cp -pa /lib64/libcrypt.so.1 ./lib/
$ cp -pa /lib64/libcrypt-2.26.so ./lib/
$ cp -pa /lib64/libcurl.so.4* ./lib/
$ cp -pa /lib64/libidn2.so.0* ./lib/
$ cp -pa /lib64/liblber-2.4.so.2* ./lib/
$ cp -pa /lib64/libldap-2.4.so.2* ./lib/
$ cp -pa /lib64/libnghttp2.so.14* ./lib/
$ cp -pa /lib64/libnss3.so* ./lib/
$ cp -pa /lib64/libsasl2.so.3* ./lib/
$ cp -pa /lib64/libsmime3.so* ./lib/
$ cp -pa /lib64/libssh2.so.1* ./lib/
$ cp -pa /lib64/libssl3.so* ./lib/
$ cp -pa /lib64/libunistring.so.0* ./lib/
$ 
$ ls -l ./lib/
合計 4572
-rwxr-xr-x 1 ec2-user ec2-user   41032  1月 17 06:44 libcrypt-2.26.so
lrwxrwxrwx 1 ec2-user ec2-user      16  3月  7 03:42 libcrypt.so.1 -> libcrypt-2.26.so
lrwxrwxrwx 1 ec2-user ec2-user      16  3月  7 03:42 libcurl.so.4 -> libcurl.so.4.5.0
-rwxr-xr-x 1 ec2-user ec2-user  553840 10月  3 23:37 libcurl.so.4.5.0
lrwxrwxrwx 1 ec2-user ec2-user      16  3月  7 03:42 libidn2.so.0 -> libidn2.so.0.3.7
-rwxr-xr-x 1 ec2-user ec2-user  324216 11月 21 18:20 libidn2.so.0.3.7
lrwxrwxrwx 1 ec2-user ec2-user      21  3月  7 03:42 liblber-2.4.so.2 -> liblber-2.4.so.2.10.7
-rwxr-xr-x 1 ec2-user ec2-user   61800  7月 27  2018 liblber-2.4.so.2.10.7
lrwxrwxrwx 1 ec2-user ec2-user      21  3月  7 03:42 libldap-2.4.so.2 -> libldap-2.4.so.2.10.7
-rwxr-xr-x 1 ec2-user ec2-user  348336  7月 27  2018 libldap-2.4.so.2.10.7
lrwxrwxrwx 1 ec2-user ec2-user      21  3月  7 03:42 libnghttp2.so.14 -> libnghttp2.so.14.18.0
-rwxr-xr-x 1 ec2-user ec2-user  158280 10月  1 19:24 libnghttp2.so.14.18.0
-rwxr-xr-x 1 ec2-user ec2-user 1204272  1月  9 18:58 libnss3.so
lrwxrwxrwx 1 ec2-user ec2-user      17  3月  7 03:42 libsasl2.so.3 -> libsasl2.so.3.0.0
-rwxr-xr-x 1 ec2-user ec2-user  121240  7月 27  2018 libsasl2.so.3.0.0
-rwxr-xr-x 1 ec2-user ec2-user  160096  1月  9 18:58 libsmime3.so
lrwxrwxrwx 1 ec2-user ec2-user      16  3月  7 03:42 libssh2.so.1 -> libssh2.so.1.0.1
-rwxr-xr-x 1 ec2-user ec2-user  169928  8月 29  2019 libssh2.so.1.0.1
-rwxr-xr-x 1 ec2-user ec2-user  358112  1月  9 18:58 libssl3.so
lrwxrwxrwx 1 ec2-user ec2-user      21  3月  7 03:42 libunistring.so.0 -> libunistring.so.0.1.2
-rwxr-xr-x 1 ec2-user ec2-user 1146512  7月 31  2018 libunistring.so.0.1.2
$ 
$ zip mediainfo_v19.09_include_libraries.zip ./bin/* ./lib/*
  adding: bin/mediainfo (deflated 64%)
  adding: lib/libcrypt-2.26.so (deflated 53%)
  adding: lib/libcrypt.so.1 (deflated 53%)
  adding: lib/libcurl.so.4 (deflated 52%)
  adding: lib/libcurl.so.4.5.0 (deflated 52%)
  adding: lib/libidn2.so.0 (deflated 60%)
  adding: lib/libidn2.so.0.3.7 (deflated 60%)
  adding: lib/liblber-2.4.so.2 (deflated 55%)
  adding: lib/liblber-2.4.so.2.10.7 (deflated 55%)
  adding: lib/libldap-2.4.so.2 (deflated 55%)
  adding: lib/libldap-2.4.so.2.10.7 (deflated 55%)
  adding: lib/libnghttp2.so.14 (deflated 52%)
  adding: lib/libnghttp2.so.14.18.0 (deflated 52%)
  adding: lib/libnss3.so (deflated 60%)
  adding: lib/libsasl2.so.3 (deflated 52%)
  adding: lib/libsasl2.so.3.0.0 (deflated 52%)
  adding: lib/libsmime3.so (deflated 56%)
  adding: lib/libssh2.so.1 (deflated 55%)
  adding: lib/libssh2.so.1.0.1 (deflated 55%)
  adding: lib/libssl3.so (deflated 55%)
  adding: lib/libunistring.so.0 (deflated 62%)
  adding: lib/libunistring.so.0.1.2 (deflated 62%)

zipファイルを展開すると以下のような構成です。

$ unzip mediainfo_v19.09_include_libraries.zip
Archive:  mediainfo_v19.09_include_libraries.zip
  inflating: bin/mediainfo
  inflating: lib/libcrypt-2.26.so
  inflating: lib/libcrypt.so.1
  inflating: lib/libcurl.so.4
  inflating: lib/libcurl.so.4.5.0
  inflating: lib/libidn2.so.0
  inflating: lib/libidn2.so.0.3.7
  inflating: lib/liblber-2.4.so.2
  inflating: lib/liblber-2.4.so.2.10.7
  inflating: lib/libldap-2.4.so.2
  inflating: lib/libldap-2.4.so.2.10.7
  inflating: lib/libnghttp2.so.14
  inflating: lib/libnghttp2.so.14.18.0
  inflating: lib/libnss3.so
  inflating: lib/libsasl2.so.3
  inflating: lib/libsasl2.so.3.0.0
  inflating: lib/libsmime3.so
  inflating: lib/libssh2.so.1
  inflating: lib/libssh2.so.1.0.1
  inflating: lib/libssl3.so
  inflating: lib/libunistring.so.0
  inflating: lib/libunistring.so.0.1.2
$ 
$ ls -R
.:
bin  lib  mediainfo_v19.09_include_libraries.zip

./bin:
mediainfo

./lib:
libcrypt-2.26.so  liblber-2.4.so.2.10.7  libsasl2.so.3.0.0
libcrypt.so.1     libldap-2.4.so.2       libsmime3.so
libcurl.so.4      libldap-2.4.so.2.10.7  libssh2.so.1
libcurl.so.4.5.0  libnghttp2.so.14       libssh2.so.1.0.1
libidn2.so.0      libnghttp2.so.14.18.0  libssl3.so
libidn2.so.0.3.7  libnss3.so             libunistring.so.0
liblber-2.4.so.2  libsasl2.so.3          libunistring.so.0.1.2

このzipファイルで、改めてLayerの新規バージョンを作成します。

$ aws lambda publish-layer-version \
    --layer-name MediaInfo-Python38 \
    --description "MediaInfo v19.09 (Amazon Linux 2) and shared libraries" \
    --zip-file fileb://mediainfo_v19.09_include_libraries.zip \
    --compatible-runtimes python3.8

Layerのバージョン含めたARNは"arn:aws:lambda:ap-northeast-1:123456789012:layer:MediaInfo-Python38:4"でした。この情報をもとにLambda関数で使用するよう設定します。

$ aws lambda update-function-configuration \
    --function-name MediaInfo-on-Lambda-Python38 \
    --layers arn:aws:lambda:ap-northeast-1:123456789012:layer:MediaInfo-Python38:4

このLayerにmediainfo実行ファイルと不足している共有ライブラリのファイルを含んだ状態で、改めて動作確認をしてみます。実際にS3に動画ファイルをアップロードしてLambda関数をキック、CloudWatch Logsから実行結果を確認してみます。今度はエラーなくMediaInfoが実行されました!ファイルサイズ、幅(Width)、高さ(Height)の情報がそれぞれ取得できていることが確認できます。

まとめ

動画やオーディオファイルの各種情報を取得できるツールMediaInfoをAWS LambdaのPython 3.8ランタイムで動作させ、Lambda関数内で動画ファイルのメタ情報を取得してみました。Python 3.7ランタイムの場合と異なり、Python 3.8ランタイムの場合にはMediaInfoの実行ファイルに加えて、Lambda実行環境で不足している共有ライブラリファイルをLayerに含める必要がありました。

補足 Lambda関数で実行したコード

本エントリで動作確認をしたPtyhonのコードが下記になります。内容についてはPtyhon 3.7でMediaInfoを動作させた前回のエントリと代わりありませんので、詳細はそちらを参照ください。1点だけ、Lambda実行環境に存在している共有ライブラリ確認のためls -l /usr/lib64の実行結果をログに書き出す処理を追記しています。

import json
import logging
import os
import subprocess
import urllib.parse

import boto3

SIGNED_URL_EXPIRATION = 300

logger = logging.getLogger('boto3')
logger.setLevel(logging.INFO)

def get_signed_url(expires_in, bucket, obj):
    s3_cli = boto3.client("s3")
    presigned_url = s3_cli.generate_presigned_url(
        'get_object', 
        Params={'Bucket': bucket, 'Key': obj}, 
        ExpiresIn=expires_in)
    return presigned_url

s3 = boto3.client('s3')

logger.info("Loading function")

def lambda_handler(event, context):

    logger.info(json.dumps(event))
    
    text = subprocess.check_output(["ls", "-l", "/usr/lib64"])
    logger.info("lib64: {}".format(text))    

    for s3_record in event['Records']:
        try: 
            logger.info("Working on new s3_record...")

            key = urllib.parse.unquote_plus(s3_record['s3']['object']['key'], 
                                            encoding='utf-8')
            bucket = s3_record['s3']['bucket']['name']
            logger.info("Bucket: {} \t Key: {}".format(bucket, key))

            signed_url = get_signed_url(SIGNED_URL_EXPIRATION, bucket, key)
            logger.info("Signed URL: {}".format(signed_url))

            # MediaInfoを実行
            json_output = subprocess.check_output(
                ["mediainfo", "--full", "--output=JSON", signed_url])
            logger.info("MediaInfo Output: {}".format(json_output))

            # MediaInfoの実行結果からFileSize, Width, Heightを取り出す
            json_data = json.loads(json_output)
            tracks = json_data['media']['track']
            video_info = {}
            for track in tracks:
                if track['@type'] == "General":
                    if 'GeneralFileSize' not in video_info:
                        video_info['GeneralFileSize'] = track.get('FileSize')
                    else:
                        logger.info('MediaInfo num of General > 1')
                elif track['@type'] == "Video":
                    if 'VideoHeight' not in video_info:
                        video_info['VideoHeight'] = track.get('Height')
                    else:
                        logger.info('MediaInfo number of Video > 1')
                    if 'VideoWidth' not in video_info:
                        video_info['VideoWidth'] = track.get('Width')
                    else:
                        logger.info('MediaInfo number of Video Width > 1')
            logger.info("video_info: {}".format(video_info))

            # ファイル名とFileSize, Width, Heightをログ出力
            input_file_name = key.rsplit('/', 1)[-1]
            logger.info("\n{} Infomation =>  FileSize: {} Byte, Width: {} pixel, Height: {} pixel".format(
                input_file_name, video_info['GeneralFileSize'], video_info['VideoWidth'], video_info['VideoHeight']))

        except Exception as e:
            print(e)
            print('Error getting object {} from bucket {}. Make sure they exist and your bucket is in the same region as this function.'.format(key, bucket))
            raise e

脚注

  1. 2020/02下旬ぐらいまではamzn2-ami-hvm-2.0.20190313-xvvv86_64-gp2 (https://console.aws.amazon.com/ec2/v2/home#Images:visibility=public-images;search=amzn2-ami-hvm-2.0.20190313-x86_64-gp2)とAMIの指定があったようです