ちょっと話題の記事

Ruby on Rails で動画をアップロード – CarrierWaveとFFmpeg

2014.05.02

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

はじめに

ブラウザよりスマホのカメラを起動して動画を撮影し、その動画をアップロードするアプリを
Ruby on Railsで構築する方法について調べて、サンプルを作成してみました。

以下、今回参考にさせて頂いた記事です。
Railsで動画ファイルを管理する~CarrierWave Flowplayer
Railsで動画を管理する2 動画のサムネイルを作成する~CarrierWave + Flowplayer

上記の記事を参考にし、サンプルでは以下の点について主に変更しています。
・twitter bootstrap の適用。
・アップロードした動画のスクリーンショットを撮るときにサイズを指定。
・ブラウザよりiPhoneのビデオが起動するように、HTMLタグを変更。
・FlowPlayerの使用を止め、リンクをクリックした場合に、動画の再生はデバイスに任せることにした。

尚、今回は動画をアップロードしていますが、カメラ等で撮影した静的な画像も
同じ方法でアップロードできます。(後述します)

動作環境は、以下の通りです。
・iPhone5、ブラウザはSafari
・Ruby 2.1
・Rails 4.1

実装について

サンプルの主な作成手順について書いて行きたいと思います。
$ rails new アプリ名、等でアプリの雛形はできていることが前提です。

1.twitter bootstrapの適用

twitter bootstrapのインストール

Gemfileに以下を記述し、bundle installします。

gem "twitter-bootstrap-rails"
gem "less-rails"
gem 'therubyracer'

twitter bootstrapの適用

以下のコマンドを実行します。

$ rails g bootstrap:install
$ rails g bootstrap:layout application fixed

application.html.erbを上書きしますか(超訳)、的なことを聞かれるので、「Y」を押下します。

ヘッダー部のバーとコンテンツが重なるのを防ぐ設定をする

app\assets\stylesheets\bootstrap_and_overrides.css.less に以下を追加します。

body { padding-top: 60px; }

(気になる人は・・・)sidebarを消す

app/views/layouts/application.html.erb のsidebarを消去するため

<div class="span3">・・・</div>

を消去します。

2.CarrierWaveによる動画のアップロード

CarrierWaveというgemを使用すると、ファイルのアップロードが簡単にできます。
以下に手順を記載しますが、「ブラウザよりビデオを起動するようにする」以外は、上記の参考サイトと手順はほぼ同じです。

CarrierWaveのインストール

Gemfileに以下を記述し、bundle installします。

gem 'carrierwave'

アップローダーの作成

generatorを使い、アップローダーとなるクラスを作成します。

$ rails g uploader video

app/uploaders/video_uploader.rb
が作成されます。

スキャフォールド

今回は「article」というModelを作成し、「video」カラムに
動画の情報を登録することにします。

$ rails g scaffold article title video
$ rake db:migrate

モデルとアップローダーの紐付け

モデルクラスのapp/models/article.rb に以下を追加します。

mount_uploader :video, VideoUploader

アップロードを許可する拡張子を設定

上記で作成したアップローダーである、app/uploaders/video_uploader.rbを編集し
def extension_white_list~end のコメントアウトは外し、MOV wmv を追加しました。
(iPhoneで撮影した動画、Windows Media Playerの動画をアップロードするため)

  # Add a white list of extensions which are allowed to be uploaded.
  # For images you might use something like this:
  def extension_white_list
     %w(jpg jpeg gif png MOV wmv)
  end

Modelに入力必須属性を追加

モデルクラスのapp/models/article.rb に以下を追加します。

validates :title, :video, :presence => true

root画面を定義

config/routes.rb に以下を追加します。

root 'articles#index'

ブラウザよりビデオを起動するようにする

iPhoneのブラウザ(Safariで動作を確認)にてボタン押下時にビデオが起動するよう
入力フィールドを編集します。
(PCのブラウザでは、アップロードするファイルの種類が動画になるようです。)

具体的には、/app/views/articles/_form.html.erbの
<%= f.text_field :video %> を
<%= f.file_field :video, :accept => 'video/*' %>
に変更し、以下のようにしました。

  <div class="field">
    <%= f.label :video %><br>
    <%= f.file_field :video, :accept => 'video/*' %>
  </div>

ここで「:accept => 'video/*」を「:accept => ‘image/*」にすると
カメラが起動するようになります。

3.FFmpeg

FFmpegを使用し、アップロードする動画のスクリーンショットを作成します。
スクリーンショットは、下記「4.スクリーンショットの表示」にて詳細画面にリンクとして表示し
クリック時に動画が再生できるようにします。

インストール

FFmpegがマシンにインストールされていない場合、gemでインストールする前に
マシンにインストールする必要があります。

Ubuntu
$ sudo apt-get install ffmpeg
Mac
$ brew install ffmpeg

Gemfileに以下を記述し、bundle installします。

gem 'streamio-ffmpeg'

スクリーンショットの作成

アップローダーの app/uploaders/video_uploader.rbを編集し、FFmpegを使ってスクリーンショットを作成するようにします。
尚、この処理も、スクリーンショットを作成するscreenshot()メソッドがアップローダーの中に書かれている以外は
上記の参考サイトと処理の流れが大体同じです。

require 'streamio-ffmpeg'

を追加し

# Create different versions of your uploaded files:
の下に、スクリーンショットを作成する処理を実装します。

  # Create different versions of your uploaded files:
  (中略)
  version :screenshot do
    process :screenshot
    def full_filename (for_file = model.logo.file)
      "screenshot.jpg"
    end
  end

  def screenshot
    tmpfile = File.join(File.dirname(current_path), "tmpfile")

    File.rename(current_path, tmpfile)

    movie = FFMPEG::Movie.new(tmpfile)
    movie.screenshot(current_path + ".jpg", {resolution: '512x312' }, preserve_aspect_ratio: :width)
preserve_aspect_ratio: :width)
    File.rename(current_path + ".jpg", current_path)

    File.delete(tmpfile)
  end

「# Create different versions of your uploaded files: 」とあるように
アップロードするファイルの別バージョンを作成する処理を、「version・・・」で
記述することができます。

4行目で、スクリーンショットを作成するプロセスとして「screenshot」を指定しています。
この「screenshot」の処理の実態は、10行目以降のscreenshot()メソッドです。

5行目のfull_filename()メソッド内で、実際に作成するスクリーンショットのファイル名を指定しています。

10行目以降のscreenshot()について説明する前に、CarrierWaveがファイルをアップロードする際の
ファイル操作について説明しておきます。

CarrierWaveはファイルをアップロードする前に、tmpフォルダにファイルを一旦格納します。
その後、最終的な保存先(デフォルトではpublic/uploads/モデル名/videoフォルダ)にファイルをアップロードします。

screenshot()では、tmpフォルダ内で以下の手順でスクリーンショットを作成しています。
・CarrierWaveによってtmpフォルダ内に作られる「screenshot・・・」という動画ファイルのファイル名を
  「tmpfile」に変える。
・「tmpfile」よりスクリーンショットを作成する。この時にサイズを指定する。
・スクリーンショットのファイル名を、アップロードするファイル名 + ".jpg"にする。
・「tmpfile」を削除する。

これらの処理をtmpフォルダ内で行うと、CarrierWaveが最終的な保存先に動画ファイル、スクリーンショットとも
移動してくれます。

更新時にスクリーンショットを再作成するよう、モデルを変更

Controllerの app/controllers/articles_controller.rb 内に
「@article.video.recreate_versions!」を追加し、update時にrecreate_versions!を呼び出します。(3行目)

  def update
    respond_to do |format|
      if @article.update(article_params) && @article.video.recreate_versions!
        format.html { redirect_to @article, notice: 'Article was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: 'edit' }
        format.json { render json: @article.errors, status: :unprocessable_entity }
      end
    end
  end

上記の処理を行わないと、上記で作成したスクリーンショットが、最終的な保存先にコピーされない
事象が発生しました。

参考記事
CarrierWave with custom processor not registering

4.スクリーンショットの表示

アップロードした動画のスクリーンショットを画面に表示し、クリック時に再生するようにします。
先に書いた通り、再生はデバイス(PCやiPhoneなど)に任せることしました。

app/views/articles/show.html.erb

<p>
  <strong>Video:</strong>
</p>
<p>
  <%= link_to @article.video_url.to_s do %>
      <%= image_tag(@article.video_url(:screenshot).to_s, id: "video", :alt => "screenshot") %>
  <% end %>
</p>

スキャフォールドによって作成されたViewを編集しています。
「<%= link_to @article.video_url.to_s do %>」で動画のURLのリンクを作成し
「image_tag・・・」でスクリーンショットを表示するように指定しています。

5.完成した画面

完成したアプリは、以下のような画面となります。

登録画面(iPhoneのSafariで見た場合)

insert1 insert2

表示画面

show1

※横向きになっていますが、そういう風に撮影した動画です。(こんなのしか手元になかった)

まとめ

Railsで動画や画像をアップロードする機能を作成する際の
参考になれば幸いです。

今回作成したアプリは、以下のGitHubに上げてあります。
carrierwave_ffmpeg_sample