この記事ではAWS
のサービスの1つである、AWS Lambda
内でFFmpeg
を使用して、S3内の動画ファイルを加工する方法に関してまとめていきます。
実際に手を動かして実装していただきやすいように、1つ1つ手順をスクショや実際のコードを交えながら説明していきます。
Contents
この記事の対象者
この記事では、AWS Lambda
とFFmpeg
の使い方はある程度わかった上で説明していきます。
もしあまり良くわかっていない人は、別記事でそれぞれまとめていますので、参考にしてみてください。
AWS Lambdaに関して
AWS Lambda
に関してはこちらの記事でまとめています。
コンソール画面の使い方、関数の作成方法、S3
のトリガーの設定、テストの実行方法、ログの見方などの基本的な使い方は、わかっている前提で書いていきますので、もしわからない場合は以下の記事で説明しています。
FFmpegに関して
FFmpeg
に関しては、以下の記事で使い方や各OSごとのインストール方法などまとめています。
環境
macOS Mojave バージョン10.14.3
Node.js 10.x
画面はMac
で説明していきますが、ほぼAWS
のコンソール画面内でできるので、Windows
でも問題なくできるかと思います。
FFmpegのダウンロード
FFmpeg
の公式ページを開きます。
↓「Download」をクリックしましょう。
↓「Linuxのアイコン」→「32-bit and 64-bit for kernel 2.6.32 and above」をクリックしましょう
↓「Static Builds」の一覧になりますが、特に指定がなければどれでも問題ないかと思います。
左上のものを選択しました。
クリックするとダウンロードが開始します。
↓ダウンロードしたものを解凍すると以下のようになるかと思います。
この中にあるFFmpeg
の実行ファイルを使用します。
Lambda関数の作成
ここからはLambda関数を作成していきます。
Node.js 10.x
で作成していきました。
実際のコードはこちらになります。
今回は、S3
に動画ファイルがアップロードされた際に、gifファイル
に変換したものをS3の同じ階層にアップロードするというようなものになっています。
console.log('Loading function');
const aws = require('aws-sdk');
const s3 = new aws.S3({ apiVersion: '2006-03-01' });
const fs = require('fs');
const execSync = require('child_process').execSync;
process.env.PATH += ':/var/task/bin';
exports.handler = async (event, context) => {
console.log('Received event:', JSON.stringify(event,));
const bucket = event.Records[0].s3.bucket.name;
const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));
const extension = key.split('/')[key.split('/').length - 1].split('.');
const filename = key.split('/')[key.split('/').length - 1].split('.')[0];
const params = {
Bucket: bucket,
Key: key,
};
const uploaded_data = await s3.getObject(params)
.promise()
.catch((err) => {
console.log(err);
const message = `Error getting object ${key} from bucket ${bucket}. Make sure they exist and your bucket is in the same region as this function.`;
console.log(message);
throw new Error(message);
});
fs.writeFileSync('/tmp/' + filename + '.' + extension, uploaded_data.Body);
execSync('ffmpeg -i /tmp/' + filename + '.' + extension + ' /tmp/' + filename + '.gif -y');
const fileStream = fs.createReadStream('/tmp/' + filename + '.gif');
fileStream.on('error', function(error) {
console.log(error);
throw new Error(error);
});
await s3.putObject({
Bucket: bucket,
Key: 'output/' + key.replace(extension, 'gif'),
Body: fileStream,
ContentType: uploaded_data.ContentType,
})
.promise()
.catch((err) => {
console.log(err);
const message = `Error putting object ${key} from bucket ${bucket}. Make sure they exist and your bucket is in the same region as this function.`;
console.log(message);
throw new Error(message);
});
return `Success getting and putting object ${key} from bucket ${bucket}.`;
};
コードの部分ごとの説明は後ほど書いていきます。
こちらの記事で扱った内容ではコードはコンソール画面内で編集できましたが、今回はFFmpeg
の実行ファイルもアップロードしないといけないので、「コードエントリタイプ」は「.zipファイルダウンロード」で行います。
なので、先程のNode.js
のコードは別で編集しておきます。
アップロードの手順
実行ファイルを含めたアップロードの手順を説明していきます。
↓適当な場所に以下のような構成のフォルダを作成します。
先程ダウンロードしたFFmpeg
の実行ファイルはbin
フォルダ以下に設置します。
index.js
の内容は先程のコードになっています。
↓その階層のファイルを全て選択した状態で圧縮をします。
↓「アーカイブ.zip」が作成されました。
この作成されたzipファイル
をアップロードします。
よくあるミスとして、これらのファイルを束ねている上の階層のフォルダを圧縮してしまうとエラーになってしまうので注意です。
その他の設定
S3
にトリガーを設定し、S3
へのアクセス権限のあるロールを設定しましょう。
タイムアウトの時間はデフォルトでは3秒ですが、おそらく足りないので適宜十分な時間を設定しておきましょう。
コードの説明
さて、先程のコードを1つずつ説明していきます。
関数が呼び出されたらexports.handler
の中身が呼ばれるので上から順番に見ていきます。
アップロードされたファイル情報を整理
今回はS3へのファイルのアップロードをトリガーとしているので、event
にイベントの情報が格納されています。
ここからファイルがアップロードされた、バケット名
とキー名
(ファイルパスとファイル名を組み合わせたもの)を取得し、それをもとにファイルの拡張子
とファイル名
を整理しています。
const bucket = event.Records[0].s3.bucket.name;
const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));
const extension = key.split('/')[key.split('/').length - 1].split('.');
const filename = key.split('/')[key.split('/').length - 1].split('.')[0];
const params = {
Bucket: bucket,
Key: key,
};
例えば、s3-sample
バケットにmovies
フォルダ以下にsample.mp4
をアップロードした場合には、
bucket: 's3-sample'
key: 'movies/sample.mp4'
extension: mp4
filename: sample
となるようにしています。
拡張子やファイル名は、あとでFFmpeg
のコマンドで使うために整理しています。
S3のファイルのダウンロード
取得したバケット名
とキー名
をパラメータに渡すことでそのファイルをダウンロードします。
const uploaded_data = await s3.getObject(params)
.promise()
.catch((err) => {
console.log(err);
const message = `Error getting object ${key} from bucket ${bucket}. Make sure they exist and your bucket is in the same region as this function.`;
console.log(message);
throw new Error(message);
});
ダウンロードしたファイルをtmp以下に一時的に保存
この後の流れとして、ダウンロードしたファイルをFFmpeg
のコマンドで変換しますが、その際にuploaded_data
のままでは実行しづらいので、tmp
フォルダ以下に設置します。
Node.js
公式のモジュールのfs
を使用していきます。
fs
を使用できるようにこちらで予め読み込んでいます。
const fs = require('fs');
以下のようにして、tmp
以下にダウンロードされたファイル名と同じ名前で一時的に保存します。
fs.writeFileSync('/tmp/' + filename + '.' + extension, uploaded_data.Body);
FFmpegのコマンドを実行
実際にFFmpeg
のコマンドを実行していきます。
以下のようにすることで、ターミナルで叩くのと同様に実行できるようにexecSync
を使えるようにしていきます。
const execSync = require('child_process').execSync;
次に、アップロードしたFFmpeg
の実行ファイルを実際に使えるようにパスを通す必要があります。
以下がそのためのコードです。
process.env.PATH += ':/var/task/bin';
上記を記述することで、execSync
関数でコマンドを実行することが出来ます。
以下のようにしました。
execSync('ffmpeg -i /tmp/' + filename + '.' + extension + ' /tmp/' + filename + '.gif -y');
ターミナルで実行するのと同様のコマンドになるように引数に渡します。
今回は先程tmp
以下に保存したファイルを、gif
に変換したものを同様にtmp
以下に出力するようにしています。
サンプルなので簡単な例にしていますが、この部分を置き換えれば様々な変換にも対応できるかと思います。
Node.js
でFFmpeg
を使うライブラリはありますが、やりたいことに対して書き方がわからない時があったので、execSync
で行うことにしています。
出力ファイルをS3にアップロード
FFmpeg
コマンドで出力されたgif
ファイルをS3
にアップロードできるように、tmp
以下に出力したものを取り出せるようにしていきます。
先ほどと同様にfs
を使っています。
const fileStream = fs.createReadStream('/tmp/' + filename + '.gif');
fileStream.on('error', function(error) {
console.log(error);
throw new Error(error);
});
これを利用して以下のようにして、S3
にアップロードをしています。
await s3.putObject({
Bucket: bucket,
Key: 'output/' + key.replace(extension, 'gif'),
Body: fileStream,
ContentType: uploaded_data.ContentType,
})
.promise()
.catch((err) => {
console.log(err);
const message = `Error putting object ${key} from bucket ${bucket}. Make sure they exist and your bucket is in the same region as this function.`;
console.log(message);
throw new Error(message);
});
アップロード先はKey
で指定することができます。
変換した結果は、output
フォルダ以下にアップロードするようにしています。
このような流れで実装しました。
まとめ
今回は、AWS Lambda
でFFmpeg
を使用する流れをまとめていきました。
今回のように実行ファイルも含めてアップロードして実行することもできるので、やれることの幅がとても広いと感じました。
今後もAWS Lambda
を他のパターンでも使用していけるようにしていきたいです。