My Eight-Year Quest to Digitize 45 Videotapes (Part Two)

Michael Lynch

45本のビデオテープをデジタル化する8年がかりの挑戦(後編)

前編では、古いホームビデオをデジタル形式で取り込み、シーンごとに分割するまでの苦労の多い道のりについて書きました。すべてのクリップの処理を終えた後、私が目指したのは、YouTubeで動画を探すのと同じくらい手軽に思い出を探せるようにすることでした。ただ、これらの動画は家族のプライベートな記録です。本物のYouTubeでは公開範囲が広すぎます。使いやすく、かつ安全に共有できる方法が必要でした。

ステップ3:共有

ClipBucket――インストールできないオープンソースのYouTubeクローン

最初に試したのはClipBucketでした。セルフホスト可能なオープンソースのYouTubeクローンを謳っています。

GitHub上のClipBucketのリポジトリ

ClipBucketは、ユーザーがセルフホストできる(理論上は)オープンソースのYouTubeクローンです。

不可解なことに、ClipBucketにはインストール手順が一切用意されていませんでした。そこでサードパーティのガイドを参考に、サーバー構成管理ツールであるAnsibleを使ってインストール作業を自動化しました。

難航した理由の一つは、ClipBucketのインストールスクリプト自体が完全に壊れていたことでした。当時私はGoogleの社員だったため、YouTubeクローンにパッチを送ることはできませんでしたが、修正方法が一目で分かるようにバグレポートを提出しました。しかし数か月経っても返答はなく、むしろリリースのたびにさらに深刻な不具合が追加されていきました。

ClipBucketはコンサルティング型のビジネスモデルで、コード自体は無料で公開し、導入支援が必要な顧客から料金を取っていました。やがて、有償の導入サポートで収益を上げている会社が、セルフサービスでの導入を積極的に支援するはずがないことに、徐々に気づきました。

MediaGoblin――よりモダンな代替案

ClipBucketで数か月苦労した後、改めて選択肢を見直してMediaGoblinを見つけました。

MediaGoblinのホームページ

MediaGoblinはセルフホスト型のメディア共有プラットフォームです。

MediaGoblinには気に入った点がたくさんありました。見栄えのよくないClipBucketのPHPとは違い、MediaGoblinは私が使い慣れたPythonで書かれていました。コマンドラインインターフェースが用意されており、動画のアップロードも簡単に自動化できます。何より、MediaGoblinはDockerイメージを提供していたため、インストールで迷う心配がありませんでした。

Dockerは、どこでも動くアプリケーション用の自己完結した環境を構築できる技術です。私は多くのプロジェクトでこれを多用しています。

MediaGoblinを再Docker化する意外な苦労

MediaGoblinのDockerイメージがあればデプロイは簡単だと思っていました。しかし、そうはいきませんでした。

事前に用意されたイメージには、私が必要とする2つの機能が欠けていました。

  • 認証
    • MediaGoblinはデフォルトで公開設定のため、部外者がサイトにアクセスできないようにする必要がありました。
  • トランスコード
    • 動画をアップロードするたびに、MediaGoblinはストリーミングに最適化するため再エンコードを試みます。すでにストリーミングに適した動画では、この処理は画質を劣化させ、処理能力を無駄にします。
    • MediaGoblinにはトランスコードをスキップする設定オプションがありますが、既存のDockerイメージでは設定を変更できませんでした。

問題ありません。Dockerイメージはオープンソースだったので、自分で再ビルドすればよいだけでした。

ところが、Dockerイメージは最新のMediaGoblinリポジトリではビルドできなくなっていました。最後にビルドが成功したバージョンに合わせてみても、やはり失敗します。全く同じコードでビルドしているにもかかわらず、MediaGoblinの外部依存関係が水面下で変わってしまい、ビルドが壊れていたのです。10分以上かかるMediaGoblinのビルドを何度も繰り返し、数十時間を費やした末、ようやく動くようになりました。

数か月後、同じことが再び起きました。この2年ほどの間に、MediaGoblinの依存関係の入れ替わりでビルドが壊れたのは数回にのぼり、この記事を書いている最中にも一度発生しました。そこで私はついにMediaGoblinを独自にフォークし、すべての依存関係を明示的なバージョンに固定しました。つまり、MediaGoblinがceleryのバージョン3.0以上なら何でも動くといった曖昧な指定をやめ、私が動作確認した4.2.1リリースに依存するようにしたのです。MediaGoblinには再現可能なビルドの仕組みが必要なように思いますが、まだそこまでは手が回っていません。

ともあれ、何時間もの格闘の末、MediaGoblinをDocker内でビルドし、調整できる状態にまで持っていけました。そこから先は、不要な動画のトランスコードをスキップし、認証のためにNginxを追加するのは簡単でした。

ステップ4:ホスティング

MediaGoblinがローカルマシン上のDockerで動くようになったので、次は家族が動画にアクセスできるよう、クラウドサーバーにデプロイする段階でした。

MediaGoblinと動画保存の問題

アプリケーションのDockerイメージを受け取り、公開URLでホストしてくれるプラットフォームはたくさんあります。厄介だったのは、MediaGoblin本体に加えて、共有すべき33GBの動画ファイルがあったことです。Dockerイメージに直接埋め込むことも可能でしたが、煩雑で見栄えもよくありません。設定ファイルを1行変更するだけで、33GBものデータを再デプロイしなければならなくなります。

ClipBucketを使っていたときは、gcsfuseを使ってこの問題を解決しました。これはGoogle Cloud Storage上のディレクトリを通常のファイルシステムのパスとしてマウントできるユーティリティです。動画ファイルをGoogle Cloud Storageに置き、gcsfuseでClipBucketからはローカルファイルのように見えるようにしたのです。

違いは、ClipBucketが完全な仮想マシンで動いていたのに対し、MediaGoblinはDockerコンテナで動いていたことです。Docker環境でクラウドストレージをマウントするのは、はるかに複雑でした。あらゆる落とし穴の解決に数十時間を費やし、その過程をブログ記事にまとめました

MediaGoblin + Docker + gcsfuseのアーキテクチャ図

MediaGoblinとGoogle Cloud Storageを連携させる初期アーキテクチャ。私の2018年のブログ記事で解説しています。

数週間かけてすべてのコンポーネントをなんとか協調させることで、ようやく動作しました。MediaGoblinのコードを一切変更することなく、メディアファイルをGoogle Cloud Storageに対して読み書きしているかのように錯覚させることができたのです。

ただ一つ問題がありました。MediaGoblinが使い物にならないほど遅くなってしまったのです。ホームページの動画サムネイルの読み込みに丸20秒もかかり、動画再生中にシークすると、再生が再開するまで10秒もの長い間停止してしまいました。

根本的な原因は、動画や画像ファイルがユーザーに届くまでの経路が長く、遠回りになっていたことでした。Google Cloud Storageからgcsfuse、MediaGoblin、Nginxを経由して、ようやくユーザーのブラウザに届くという流れです。gcsfuseは速度に最適化されていないため、大きなボトルネックになっていました。レイテンシの高さについては、プロジェクトのホームページでもはっきりと警告されています。

gcsfuseのGitHubリポジトリにあるレイテンシに関する警告

gcsfuseのドキュメントに記載されたパフォーマンス低下に関する警告

理想を言えば、ブラウザが中間層をすべて迂回して、Google Cloud Storageから直接ファイルを取得できればよいのですが。MediaGoblinのコードを深く掘り下げ、Google Cloud Storageとの複雑な連携ロジックを追加することなく、どうすればそれを実現できるでしょうか。

Nginxのsub_filterを使ったトリック

幸い、多少見栄えは悪いものの、シンプルな解決策を見つけました。Nginxのdefault.confファイルにこのフィルターを追加したのです。

sub_filter "/mgoblin_media/media_entries/" "https://storage.googleapis.com/MY-GCS-BUCKET/media_entries/";
sub_filter_once off;

私の構成では、NginxがエンドユーザーとMediaGoblinの間でプロキシとして動作します。上記のディレクティブは、MediaGoblinのHTMLレスポンスをエンドユーザーに渡す前に、Nginxに検索・置換を行わせるものです。NginxがMediaGoblin上のメディアファイルへの相対パスをすべてGoogle Cloud StorageのURLに置き換えます。

たとえば、MediaGoblinは次のようなHTMLを生成します。

<video width="720" height="480" controls autoplay>
  <source
    src="/mgoblin_media/media_entries/16/Michael-riding-a-bike.mp4"
    type="video/mp4"
  />
</video>

Nginxはレスポンスを次のように書き換えます。

<video width="720" height="480" controls autoplay>
  <source
    src="https://storage.googleapis.com/MY-GCS-BUCKET/media_entries/16/Michael-riding-a-bike.mp4"
    type="video/mp4"
  />
</video>

全体像は次のようになります。

MediaGoblin + Docker + nginxによるGCSへのレスポンス書き換えのアーキテクチャ図

NginxがMediaGoblinからのレスポンスを書き換えることで、クライアントはメディアファイルをGoogle Cloud Storageから直接取得できます。

この解決策の優れた点は、MediaGoblinのコードを一切変更する必要がなかったことです。たった2行のNginxディレクティブで、互いを全く認識していないMediaGoblinとGoogle Cloud Storageをシームレスに連携させることができました。

注意:この方法では、Google Cloud Storage上のファイルを誰でも読み取れる状態にする必要があります。不正アクセスのリスクを抑えるため、私は長くランダムなバケット名(例:mediagoblin-39dpduhfz1wstbprmyk5ak29)を使用し、バケットのアクセス制御ポリシーで権限のないユーザーによるディレクトリ内容の列挙を防ぐようにしています。

完成した成果物

ここで、完全に動作するソリューションが完成しました。MediaGoblinはGoogle Cloud Platform上の独自のコンテナで安定して動作し、頻繁にパッチを当てたりアップグレードしたりする必要がなくなりました。すべての工程が自動化され再現可能になったため、変更の反映や以前のバージョンへのロールバックも簡単でした。

家族は、動画を簡単に閲覧できる点をとても気に入ってくれました。Nginxによるパフォーマンス改善のおかげで、YouTubeを閲覧するのと同じくらいキビキビと動作しました。

閲覧画面は次のように表示されます。

MediaGoblinの閲覧画面

家族のホームビデオ共有サーバーの閲覧画面

サムネイルをクリックすると、次のような画面が表示されます。

MediaGoblinで動画を表示したスクリーンショット

メディアサーバーで個別のクリップを視聴している様子

何年もの作業を経て、当初思い描いていたYouTubeのような体験を家族に提供できたことは、この上ない喜びでした。

おまけ:月1ドル未満までコストを下げる

ホームビデオは、数か月に一度しか見ないものです。家族全員合わせてもサイトへのアクセスは年間約20時間ほどでしたが、サーバーは24時間365日稼働していました。99.7%の時間はアイドル状態のサーバーに、月15ドルを支払っていたのです。

2018年末、GoogleはCloud Runをリリースしました。その目玉機能は、HTTPリクエストに応答できるほど高速にDockerコンテナを起動できることでした。これにより、サーバーはスタンバイ状態で待機し、誰かがURLにアクセスしたときだけ稼働すればよくなりました。私のようにアクセス頻度の低いアプリでは、コストを月15ドルから年間数セントまで削減できます。

理由はもう覚えていませんが、Cloud Runは私のMediaGoblinイメージではうまく動きませんでした。しかしCloud Runの存在が、Herokuが同様のサービスを無料で提供しており、そのツールの方がGoogleのものよりはるかに使いやすいことを思い出させてくれました。

無料のアプリサーバーを使えば、かかるコストはデータの保存料だけです。Googleの標準リージョナルストレージは1GBあたり2.3セントで、動画コレクションは33GBなので、支払いは月0.77ドルで済みます。

Google Cloud Platformからの0.77ドルの請求書

このソリューション全体のコストは、月わずか0.77ドルです。

これから挑戦する人へのヒント

この作業には明らかに長い時間がかかりましたが、この記事が、ホームビデオをデジタル化して共有しようとする他の方々の手間を80〜90%削減できれば幸いです。次のセクションでは、私のソリューションの具体的な手順を詳細なウォークスルーで解説していますが、まずはホームビデオのデジタル化と共有に関する一般的なヒントをいくつか紹介します。

  • 取り込みや編集の段階で、できるだけ多くのメタデータを記録しましょう。
    • テープのラベルには貴重な情報が書かれていることがよくあります。
    • どのクリップがどのテープのどの順番に由来するかを記録しておきましょう。
    • クリップの中にある収録日に関する手がかりをメモしておきましょう。
  • 取り込み作業の外注も検討しましょう。
    • あなたが自力で動画デジタル化業者の品質に匹敵するのは、極めて困難でコストもかかります。
    • ただしEverPresentという会社は避けた方がよいでしょう(詳細を知りたい方はメールでお問い合わせください)。
  • 自分で取り込む場合は、十分なディスク容量を用意しましょう。
    • 非圧縮のキャプチャは、標準画質の動画で1分あたり約100〜200MBになります。
    • 私はすべてを10TBのSynology DS412+に保存しました。
  • メタデータは特定のアプリケーションに依存しない形式で記録しましょう。
    • クリップの説明、タイムコード、日付などです。
    • アプリケーション固有の形式で保存したり(さらに悪いことに捨ててしまったり)すると、別のソリューションに切り替えた際に作業を再現できなくなります。
    • 編集時に動画を見ると、多くの有用なメタデータに気づきます。記録しなければ失われてしまいます。
      • 動画の中で何が起きているか
      • 誰が映っているか
      • いつ収録されたか
  • お気に入りに印をつけましょう。
    • 正直なところ、ホームビデオの映像の大部分はかなり退屈なものです。
    • 私はお気に入りのクリップに「best of」タグを付け、楽しい動画が見たくなったときにそれらを巡回するようにしています。
  • エンドツーエンドのソリューションはできるだけ早く構築しましょう。
    • 私はまずすべてのテープを取り込み、次にすべてを編集する、という順番で進めようとしました。
    • 1本のテープから始めて、共有までに必要な作業を一通りやっておけばよかったと思っています。そうすれば、初期段階での判断が最終結果にどう影響するかが分かったはずです。
  • トランスコードは最小限に抑えましょう。
    • クリップを編集したり再エンコードしたりするたびに、画質は劣化します。
    • 元映像はできる限り最高画質で取り込み、その後ブラウザでネイティブに再生できる形式へ一度だけトランスコードしましょう。
  • 動画クリップの共有には、できる限りシンプルなソリューションを使いましょう。
    • 振り返ってみると、MediaGoblinは、変わることのない一連の動画ファイルを表示するウェブページを生成するという、さほど複雑ではない用途には複雑すぎました。
    • もしやり直すなら、HugoJekyllGridsomeのような静的サイトジェネレーターを使ったでしょう。
  • モンタージュを作りましょう。
    • ビデオモンタージュは、複数のホームビデオから最高の瞬間をひとつにまとめる楽しい方法です。
    • モンタージュは音楽が命です。The Nationalの「Slow Show」はモンタージュにぴったりですが、まだ誰もそのことに気づいていないようです。

私の作業工程のエンドツーエンド・ウォークスルー

具体的にどうやったのか詳しく知りたい方のために、最初から最後までの全ワークフローを示したチュートリアルを用意しました。私の手順を再現するためのすべてのソースコードとコマンドが含まれています。


イラスト:Loraine Yow

これらのクリップや静止画の一部を共有することを快諾し、そもそもすべてを記録してくれ、そしてこの長い過程を通じて支え続けてくれた家族に、心から感謝します。

原文は Michael Lynch により に公開されました。

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。