Railsで動画を分析する

September 12, 2021

ひとくちにファイルのアップロードを扱うgemにもShrineCarrierwaveなどがありますが、今回は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を追記すればよいので とはいえこの情報だけでは圧倒的に足りないのでここではもう少し踏み込んでみようと思います。

https://github.com/rails/rails/blob/6-1-stable/activestorage/lib/active_storage/analyzer/video_analyzer.rb

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を使用しましたが、サポートしているライブラリであればどれでもよいと思います。

SampleVideo_1280x720_1mb

これで生成された画像がご覧の通り。 こちらもサムネイルの生成は時間がかかるので、Seedであらかじめファイルのりサイズを行いたいときはvideo.content.preview.processedを使うとよさそうです。

video = Video.last
video.content.preview(resize_to_limit: [348, 225]).processed

previewable?というメソッドもあったのですが、動画やPDFのときにtrueを返すだけなので確実にファイルを作っておきたいときはモデル作成時に上記のコードを用意しておくとよいと思います。 ただしこのサムネイルはもともと問題なく表示できているのですが、白黒の画面からフェードインだったりする場合はうまくサムネイルが表示できないことがあるかもしれません。

https://github.com/rails/rails/blob/6-1-stable/activestorage/lib/active_storage/previewer/video_previewer.rb

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"

SampleVideo_1280x720_1mb_2

先ほどとは違ったシーンの画像が生成できました。 理想としてはffprobeで動画ごとの長さを取得して半分くらいの長さのサムネイルを取得したいですが、やりたかったことはできました。 解説でいくつかコードは載せましたが、今回は実質2~3行でできているのがRailsのすごいところですね。

https://gorails.com/episodes/how-to-create-an-active-storage-previewer

今回はあくまで簡易的な方法にこだわりましたが、Previewerを自分で定義してLibreOfficeのPowerPointのファイルから画像生成するという方法も紹介されていました。

他にもYouTubeのようにプレビューにGIF動画載せたりとかもしてみたいですが、長くなりそうなので今回はこれくらいで良しとしておこうかなと。


Profile picture

Personal blog by Seiichi Yonezawa.