ひとくちにファイルのアップロードを扱うgemにもShrineやCarrierwaveなどがありますが、今回はActiveStorageで動画の長さと動画のサムネイル画像を生成してみることにします。
ActiveStorageも出始めの頃は情報も少なくできることが少ないものだと思っていましたが、プロトタイプを作る意味では最も簡単に使えると思います。 Active Storage Overviewを読んでいると:
Video analysis provides these, as well as duration, angle, and display_aspect_ratio.
動画ファイルでは縦横のサイズに加えて、長さや縦横比が取得できるようです。
test:
service: Disk
root: <%= Rails.root.join("tmp/storage") %>
analyzed: true
local:
service: Disk
root: <%= Rails.root.join("storage") %>
analyzed: true
ガイドによればanalyzed
を追記すればよいので
とはいえこの情報だけでは圧倒的に足りないのでここではもう少し踏み込んでみようと思います。
module ActiveStorage
class Analyzer::VideoAnalyzer < Analyzer
def ffprobe_path
ActiveStorage.paths[:ffprobe] || "ffprobe"
end
end
end
探してみるとActiveStorage::Analyzer::VideoAnalyzer
というクラスが見つかりました。
このファイルによるとFFmpegのffprobe
を使うようです。
Alpine Linuxだとapk add ffmpeg
でコマンドが使えるようになりました。
続いて今回定義したモデルのVideo
クラスです。
class Video < ApplicationRecord
has_one_attached :content
end
では実際にSample Videosのファイルを借りて試してみましょう。
[ActiveJob] Enqueued ActiveStorage::AnalyzeJob (Job ID: 2d53f9b3-dc4b-4883-963d-0910e1cf606f) to Async(default) with arguments: #<GlobalID:0x00007f0b3e034080 @uri=#<URI::GID gid://myapp/ActiveStorage::Blob/1>>
[ActiveJob] [ActiveStorage::AnalyzeJob] [2d53f9b3-dc4b-4883-963d-0910e1cf606f] ActiveStorage::Blob Update (0.7ms) UPDATE "active_storage_blobs" SET "metadata" = $1 WHERE "active_storage_blobs"."id" = $2 [["metadata", "{\"identified\":true,\"width\":1280.0,\"height\":720.0,\"duration\":5.28,\"display_aspect_ratio\":[16,9],\"analyzed\":true}"], ["id", 1]]
ログを見るとActiveStorage::AnalyzeJob
というジョブが実行されていました。
縦横1280x720px(16:9)で長さが5.28秒として取得できていました。
注意としてはSeedで作成したファイルはActiveStorage::AnalyzeJob
が実行されなかったので、個別にanalyze
を実行してあげる必要がありそうです。
video = Video.last
video.content.analyze
続いてこの動画のサムネイルを表示してみます。
<%= image_tag video.content.preview(resize_to_limit: [348, 225]) %>
Viewに関してはこの通りです。
初回表示に時間がかかってしまうのですが、イメージとしてはffmpeg
を実行してimage_processing
というgemでリサイズをします。
今回はGraphicsMagickを使用しましたが、サポートしているライブラリであればどれでもよいと思います。
これで生成された画像がご覧の通り。
こちらもサムネイルの生成は時間がかかるので、Seedであらかじめファイルのりサイズを行いたいときはvideo.content.preview.processed
を使うとよさそうです。
video = Video.last
video.content.preview(resize_to_limit: [348, 225]).processed
previewable?
というメソッドもあったのですが、動画やPDFのときにtrue
を返すだけなので確実にファイルを作っておきたいときはモデル作成時に上記のコードを用意しておくとよいと思います。
ただしこのサムネイルはもともと問題なく表示できているのですが、白黒の画面からフェードインだったりする場合はうまくサムネイルが表示できないことがあるかもしれません。
module ActiveStorage
class Previewer::VideoPreviewer < Previewer
private
def draw_relevant_frame_from(file, &block)
draw self.class.ffmpeg_path, "-i", file.path, *Shellwords.split(ActiveStorage.video_preview_arguments), "-", &block
end
end
end
今度はActiveStorage::Previewer::VideoPreviewer
を見ていると、実際にコマンドを実行していると思しき箇所が見つかりました。
ちょうどActiveStorage.video_preview_arguments
というものがあり、調べてみるとさらに次のファイルを見つけました。
config.active_storage.video_preview_arguments
can be used to alter the way ffmpeg generates video preview images. The default is"-y -vframes 1 -f image2"
Rails Consoleでも同様に確認できました。
Loading development environment (Rails 6.1.4.1)
irb(main):001:0> ActiveStorage.video_preview_arguments
=> "-y -vframes 1 -f image2"
もとの動画が5秒なので、今回は3秒後で指定してみます。
config.active_storage.video_preview_arguments = "-y -vframes 1 -f image2 -ss 3"
先ほどとは違ったシーンの画像が生成できました。
理想としてはffprobe
で動画ごとの長さを取得して半分くらいの長さのサムネイルを取得したいですが、やりたかったことはできました。
解説でいくつかコードは載せましたが、今回は実質2~3行でできているのがRailsのすごいところですね。
https://gorails.com/episodes/how-to-create-an-active-storage-previewer
今回はあくまで簡易的な方法にこだわりましたが、Previewerを自分で定義してLibreOfficeのPowerPointのファイルから画像生成するという方法も紹介されていました。
他にもYouTubeのようにプレビューにGIF動画載せたりとかもしてみたいですが、長くなりそうなので今回はこれくらいで良しとしておこうかなと。