すでにs3上にアップロードされているファイルをffmpegで処理したい場面があったのでまとめます。
aws上で処理する場合はs3からlambdaでffmpegを動かしたり、elastic transcoderを使う方法があるかと思いますが、今回はただメタデータを付与したいだけの軽い処理なので、rails上で処理する方法にしました。
次のようなモデルを想定しています。
class Post < ActiveRecord
has_one_attached :movie
end
gem
- sidekiq
- streamio-ffmpeg
実装
class TranscodeWorker
include Sidekiq::Worker
def perform(post_id)
post = Post.find(post_id)
# Rails内での一時保存ファイルの作成
local = Tempfile.open
local.binmode
# s3からダウンロード
post.movie.download do |data|
local.write(data)
end
# FFMPEGのインスタンスで処理
movie = FFMPEG::Movie.new(local.path)
output_path = "#{Rails.root}/path/to/output.mp4"
movie.transcode(output_path, %w(-metadata:s:v rotate=180 -c copy))
# 再アップロード
post.movie.attach(
io: File.open(output_path),
filename: "rotated_#{Time.current.strftime("%F_%T")}.mp4",
content_type: "video/mp4"
)
post.save!
local.close
end
ActiveStorage::Blob#download
はバイナリーを返すので、tempfileはbinmode
でバイナリモードにしておく必要があります。ちなみにrewind
でポインタをファイルの先頭に戻せるので、ダウンロードしたファイルを先頭から読み込む場合はコールしておくと良いと思います。
streamio-ffmpegはffmpegとほぼ同じことができます。transcode
は第2引数に配列で、ffmpegのオプションを指定できます。第1引数のpathに出力してくれます。%w()
を%W()
にすれば文字列の時と同様に変数展開できます。
再アップロードはActiveStorage::Attached::One#attach
をコールすればOKです。HTTPリクエスト経由ではないファイルをアタッチする場合は、ioとfilenameの指定が必須です。content_typeは必須ではないですが、指定した方が無難かと思います。
動画処理に関する機能なので、非同期での実装例でした。