MediaGoblinでホームビデオを編集・共有する
目的
このチュートリアルでは、デジタル化したビデオを取り込んで短いクリップに編集し、パスワードで保護した自前のMediaGoblinサーバーで公開する方法を解説します。無料のHeroku dynoを利用するため、プライベートなメディアサーバー運用にかかる継続的なコストはGoogle Cloud Storageのストレージ料金のみで、1GBあたり2.3セントです。
私はこのワークフローを使って家族のホームビデオを編集・共有しており、月額わずか0.77ドルで運用しています。詳しい経緯については、ブログ記事「My Eight-Year Quest to Digitize 45 Videotapes」をご覧ください。このワークフローは、切り出して共有したい多数のサブクリップを含むあらゆる種類の動画ファイルに利用できます。
前提条件
- Python 3
- virtualenv
- Git
- Docker
- ffmpeg
- 動画プレーヤー
- 理想的にはAdobe Premiere ElementsやOpenShotのような編集タイムラインを備えたもの
- 無料のHerokuアカウント
- およびHeroku CLI
- Google Cloud Platformアカウント(請求が有効なもの)
デモ動画
例として、このチュートリアル全体で使用する動画は、1950年代のテキサス州のある家族のパブリックドメインのホームビデオです。
このチュートリアルで編集・共有用の素材として使用するパブリックドメインの動画(なお、この動画には音声は含まれていません)
この動画をダウンロードするには、以下のコマンドを実行します。
VIDEO_URL="https://github.com/mtlynch/free-usage-videos/blob/master/texas-farm-family-1952/TexasFar1952.mp4?raw=true"RAW_VIDEOS_DIR="${HOME}/videos-raw"RAW_VIDEO="${RAW_VIDEOS_DIR}/TexasFar1952.mp4"mkdir -p "$RAW_VIDEOS_DIR"wget "$VIDEO_URL" -O "$RAW_VIDEO"アノテーションの付与
動画ファイルは連続したフレームで構成されています。動画ファイル内のクリップの開始位置と終了位置を特定するには、再生位置に対応するフレーム番号を識別する方法が必要です。そのために、ffmpegを使って元の動画にフレーム番号を付与した作業用のコピーを作成できます。
ANNOTATED_VIDEO=TexasFar1952-annotated.mp4ffmpeg \ -i "$RAW_VIDEO" \ -vf "drawtext=fontfile=Arial.ttf: text='%{frame_num}': start_number=1: x=(w-tw)/2: y=h-(2*lh): fontcolor=black: fontsize=20: box=1: boxcolor=white: boxborderw=5" \ -c:a copy \ "$ANNOTATED_VIDEO"これにより、以下のような動画のコピーが作成されます。
カタログ化
次は、テープに収録された映像をカタログ化します。動画を確認しながら、個々のシーンを特定し、誰が登場して何が起こっているのかを整理します。
お好みの動画プレーヤーを開いてください。私はAdobe Premiere Elementsが最適だと思いますが、無料のOpenShot editorも試せます。技術的にはどの動画プレーヤーでも構いませんが、フレームごとのコマ送りや拡大可能なタイムラインに対応したものを選ぶと時間を節約できます。
次に、スプレッドシートを作成します。私のものから始めることもできます。各フィールドについて以下で説明します。

私のものと同じ形式のスプレッドシートに、すべてのメタデータをまとめます。
tape_id: 動画ファイルのファイル名(拡張子なし)です。tape_shortname: この素材ファイルに由来するファイルを識別するための短縮名です。tape_idと同じでも構いません。tape_friendly_name: MediaGoblinでこのクリップがどのテープに由来するかが表示される際に使われるフィールドです。ファイル名よりも分かりやすい名前がある場合は、ここに入力します。scene_start_frame: クリップが始まる最初のフレーム番号です。- テキサスの農場のサンプル映像では、最初の数フレームは真っ黒なので、最初のクリップはフレーム28から始まります。
title: このシーンのタイトルです。出力されるファイル名やMediaGoblinのサムネイルで使われます。render_scenesに特定区間を無視させたい場合は、タイトルをjunkにします。
description: (任意)シーンで何が起こっているかの説明です。MediaGoblinの動画視聴画面でクリップの下に表示されます。- hashtags:
description列の後に、ハッシュ記号で始まるタグ列を作成できます。ハッシュタグ列にy(または何らかの値)が入っているクリップには、そのタグがMediaGoblin上で追加されます。- 例:私のスプレッドシートの9行目は
#adam列と#archie列にyが入っています。publish_to_mediagoblinがクリップをMediaGoblinに追加する際に、adamとarchieのタグがクリップに付与されます。 - これらの列の目的は、動画にタグ付けしたい家族やペットがいる場合に、そのハッシュタグの列を追加し、登場するクリップごとに
yを入力できるようにすることです。
- 例:私のスプレッドシートの9行目は
date: クリップが録画された日付です。- 次のいずれかの形式で記述できます。
YYYY-MM-DDYYYY-MMYYYY
- 次のいずれかの形式で記述できます。
other_tags: クリップに追加したいその他のタグを、カンマ区切りで記述します。
後述するrender_scenesを実行すると、この命名規則で動画が短いクリップに分割されます。
[tape_shortname] - [clip index] - [title].mp4
例:
Texas Family - 1952 - 05 - Archie in the Corn Fields.mp4
シーン用YAMLファイルの作成
クリップのカタログ化ができたら、私のスクリプトを使ってCSVをYAMLファイルに変換できます。このYAMLファイルは、他のスクリプトが動画を分割しMediaGoblinにインポートする方法を指示します。
git clone https://github.com/mtlynch/process-home-videos.gitcd process-home-videosmkdir -p ./venvvirtualenv --python python3 ./venv. venv/bin/activatepip install --requirement requirements.txt次に、設定ファイルを作成する必要があります。process-home-videosにあるconfig.example.yamlをconfig.yamlにコピーし、ファイル内の指示に従ってクリップ用に編集してください。
cp config.example.yaml config.yaml次に、クリップのメタデータのCSVとYAMLファイルのパスを作成します。
MEDIAGOBLIN_METADATA="${HOME}/mediagoblin-meta"mkdir -p "$MEDIAGOBLIN_METADATA"# Path to the CSV you created.SCENES_CSV="${MEDIAGOBLIN_METADATA}/scenes.csv"# Path to output file to create.SCENES_YAML="${MEDIAGOBLIN_METADATA}/scenes.yaml"私のスプレッドシートの例に沿って進める場合は、CSVとしてダウンロードできます。
CSV_URL="https://docs.google.com/spreadsheets/d/1kuamVFEYBrOI097IWBQ8sB0q37ZRACYe2o389Ag92zI/export?format=csv&id=1kuamVFEYBrOI097IWBQ8sB0q37ZRACYe2o389Ag92zI&gid=401061703"wget "$CSV_URL" -O "$SCENES_CSV"以下のスクリプトを実行して、CSVをYAMLファイルに変換します。
app/csv_to_yaml.py \ --config config.yaml \ "$SCENES_CSV" \ > "$SCENES_YAML"これにより、以下のようなYAMLファイルが作成されます。
$ head -n 19 "$SCENES_YAML"- description: 'Abigail and Adam stand outside with Abigail primping Adam''s coat. Adam is 12. Recorded between June 1, 1952 and June 30, 1952. Came from tape "Texas Farm Family - 1952," scene #01.' duration_frames: 457 raw_source_filename: TexasFar1952.mp4 rendered_filename: Texas Family - 1952 - 01 - Abigail Primping Adam.mp4 tags: - abigail - adam - best of timecode_start: '0:00:00.934267' title: Abigail Primping Adamクリップの分割
すべてのメタデータの準備ができたら、大きな動画ファイルを一連の短いクリップに分割します。
# Specify the directory where the script should write out the processed clips.PROCESSED_CLIPS_DIR="${HOME}/videos-processed"mkdir -p "$RAW_VIDEOS_DIR"app/render_scenes.py \ --metadata "$SCENES_YAML" \ --raw_videos_dir "$RAW_VIDEOS_DIR" \ --output_clips_dir "$PROCESSED_CLIPS_DIR"スクリプトが完了すると、以下のようなフォルダができあがります。
$ ls "$PROCESSED_CLIPS_DIR"'Texas Family - 1952 - 01 - Abigail Primping Adam.mp4''Texas Family - 1952 - 02 - Abigail Standing on the Stairs.mp4''Texas Family - 1952 - 03 - Adam Plays with Cars on the Sidewalk.mp4''Texas Family - 1952 - 04 - Trudy Working at the Loom.mp4''Texas Family - 1952 - 05 - Archie in the Corn Fields.mp4''Texas Family - 1952 - 06 - Tending to the Cows, Bessie Nursing.mp4''Texas Family - 1952 - 07 - Giving Pigs Belly-Rubs.mp4''Texas Family - 1952 - 08 - Checking on the Chickens and Hens.mp4'MediaGoblin用ファイルの作成
クリップの処理が終わったら、MediaGoblinにファイルをアップロードする準備が整いました。まずはローカルマシンのMediaGoblinインスタンスに動画とメタデータをインポートします。これにより、MediaGoblinがファイルを内部データベースとファイル構造に変換し、クラウドでMediaGoblinインスタンスを実行する際に再利用できるようになります。
まず、MediaGoblinの設定ファイルであるmediagoblin.iniに環境情報を入力します。
MG_という接頭辞はホストマシン、MGC_はDockerコンテナ内のパスを指すという命名規則に従っています。MG_CONFIG=$(mktemp)wget https://mtlynch.io/digitizing-home-videos-walkthrough/mediagoblin.ini \ -O "$MG_CONFIG"# Path to MediaGoblin's home directory within the container.MGC_HOME="/var/lib/mediagoblin"MGC_DB_PATH="${MGC_HOME}/mediagoblin.db"# Update the relevant lines in the config file.sed \ --in-place \ "s@.*sql_engine = .*@sql_engine = sqlite:///${MGC_DB_PATH}@" \ "$MG_CONFIG"次に、コンテナを起動します。
# This is a path on the host machine that will receive the files the MediaGoblin# container generates.MG_SERVING_DIR=$(mktemp --directory)# These are paths within the MediaGoblin container.MGC_INPUT_VOLUME="/opt/input-videos"MGC_APP_DIR="/srv/mediagoblin.example.org/mediagoblin"# Name for MediaGoblin Docker container.MGC_NAME=mediaglindocker run \ --tty \ --detach \ --publish 6543:6543 \ --volume "${PROCESSED_CLIPS_DIR}:${MGC_INPUT_VOLUME}" \ --volume "${MG_CONFIG}:${MGC_APP_DIR}/mediagoblin_host.ini" \ --volume "${MG_SERVING_DIR}:${MGC_HOME}" \ --name "$MGC_NAME" \ mtlynch/mediagoblin上記のコマンドは、ホストマシン上のいくつかのパスをMediaGoblinコンテナにマウントし、ホストとMediaGoblin間でファイルを共有できるようにします。
PROCESSED_CLIPS_DIRにより、MediaGoblinは上記で作成したクリップを読み取れるようになります。MG_CONFIGはMediaGoblinが使用する設定ファイルを指定します。MG_SERVING_DIRはMediaGoblinがローカルファイルシステム上に配置した動画やサムネイルファイルを受け取ります。
コンテナを実行すると、MediaGoblinの起動に数秒かかります。以下のコマンドで進行状況を確認できます。
docker logs "$MGC_NAME" -fサーバーの準備ができると、以下のような行が表示されます。
Starting server in PID 26.Serving on http://0.0.0.0:6543http://localhost:6543にアクセスして、MediaGoblinサーバーが正常に起動していることを確認できます。
admin、パスワードadminでログインできますが、ログインは必須ではありません。以下のスクリプトを実行して、クリップとメタデータをMediaGoblinにインポートします。
MEDIAGOBLIN_PUBLISH_HISTORY=$(mktemp)app/publish_to_mediagoblin.py \ "$MGC_INPUT_VOLUME" \ --metadata "$SCENES_YAML" \ --publish_history "$MEDIAGOBLIN_PUBLISH_HISTORY" \ --container_name "$MGC_NAME"スクリプトが完了すると、http://localhost:6543のMediaGoblinウェブインターフェースにすべてのクリップが表示されるはずです。

MediaGoblinのウェブインターフェースに8つのクリップすべてのサムネイルが表示されるはずです。
動画をクリックしても、MediaGoblinが現代のストリーミングに最適化された動画形式には古すぎるウェブプレーヤーを使用しているため、通常の動画プレーヤーでは視聴できません。「Original file」リンクをクリックすれば視聴できます。

ローカルのMediaGoblinインスタンスにおける1本の動画の表示
ご安心ください。次のステップで修正するので、埋め込みプレーヤーですべての動画を通常どおり視聴できるようになります。
ローカルコンテナでの作業はこれで完了ですので、削除します。
docker rm --force "$MGC_NAME"Dockerが共有フォルダの所有権を取得してしまったので、取り戻します。
sudo chown "${USER}:${USER}" "$MG_SERVING_DIR" --recursiveそのディレクトリを確認すると、以下のような内容が表示されるはずです。
find "$MG_SERVING_DIR" -type f/tmp/tmp.bq7GAbNNNW/media/public/media_entries/8/Texas_Family_-_1952_-_08_-_Checking_on_the_Chickens_and_Hens.mp4/tmp/tmp.bq7GAbNNNW/media/public/media_entries/8/Texas_Family_-_1952_-_08_-_Checking_on_the_Chickens_and_Hens.thumbnail.jpg/tmp/tmp.bq7GAbNNNW/media/public/media_entries/5/Texas_Family_-_1952_-_05_-_Archie_in_the_Corn_Fields.mp4/tmp/tmp.bq7GAbNNNW/media/public/media_entries/5/Texas_Family_-_1952_-_05_-_Archie_in_the_Corn_Fields.thumbnail.jpg/tmp/tmp.bq7GAbNNNW/media/public/media_entries/1/Texas_Family_-_1952_-_01_-_Abigail_Primping_Adam.thumbnail.jpg/tmp/tmp.bq7GAbNNNW/media/public/media_entries/1/Texas_Family_-_1952_-_01_-_Abigail_Primping_Adam.mp4/tmp/tmp.bq7GAbNNNW/media/public/media_entries/4/Texas_Family_-_1952_-_04_-_Trudy_Working_at_the_Loom.thumbnail.jpg/tmp/tmp.bq7GAbNNNW/media/public/media_entries/4/Texas_Family_-_1952_-_04_-_Trudy_Working_at_the_Loom.mp4/tmp/tmp.bq7GAbNNNW/media/public/media_entries/7/Texas_Family_-_1952_-_07_-_Giving_Pigs_Belly-Rubs.mp4/tmp/tmp.bq7GAbNNNW/media/public/media_entries/7/Texas_Family_-_1952_-_07_-_Giving_Pigs_Belly-Rubs.thumbnail.jpg/tmp/tmp.bq7GAbNNNW/media/public/media_entries/6/Texas_Family_-_1952_-_06_-_Tending_to_the_Cows_Bessie_Nursing.thumbnail.jpg/tmp/tmp.bq7GAbNNNW/media/public/media_entries/6/Texas_Family_-_1952_-_06_-_Tending_to_the_Cows_Bessie_Nursing.mp4/tmp/tmp.bq7GAbNNNW/media/public/media_entries/3/Texas_Family_-_1952_-_03_-_Adam_Plays_with_Cars_on_the_Sidewalk.thumbnail.jpg/tmp/tmp.bq7GAbNNNW/media/public/media_entries/3/Texas_Family_-_1952_-_03_-_Adam_Plays_with_Cars_on_the_Sidewalk.mp4/tmp/tmp.bq7GAbNNNW/media/public/media_entries/2/Texas_Family_-_1952_-_02_-_Abigail_Standing_on_the_Stairs.mp4/tmp/tmp.bq7GAbNNNW/media/public/media_entries/2/Texas_Family_-_1952_-_02_-_Abigail_Standing_on_the_Stairs.thumbnail.jpg/tmp/tmp.bq7GAbNNNW/mediagoblin.db/tmp/tmp.bq7GAbNNNW/.cache/gstreamer-1.0/registry.x86_64.binGoogle Cloud Storageの設定
前のステップの目的は、MediaGoblinにすべてのクリップのデータベース、ファイル構成、動画サムネイルを生成させることでした。次はMediaGoblinをクラウドサーバーにデプロイし、ローカルマシンだけでなくどこからでもアクセスできるようにします。
新しいGoogle Cloud Platformプロジェクトの作成(任意)
MediaGoblinのファイル用にクリーンなGoogle Cloud Platformプロジェクトを用意したい場合は、以下のコマンドで作成できます。既存のプロジェクトを使用する場合は、GCP_PROJECT_IDを使用したいプロジェクトIDに設定し、このセクションをスキップしてください。
GCP_PROJECT_NAME="mediagoblin"RANDOM_SUFFIX=$(head /dev/urandom | tr -dc 'a-z0-9' | head -c 16 ; echo '')GCP_PROJECT_ID="${GCP_PROJECT_NAME}-${RANDOM_SUFFIX}"gcloud projects create \ "$GCP_PROJECT_ID" \ --name "$GCP_PROJECT_NAME" \ --set-as-default新しいプロジェクトを請求先アカウントにリンクする必要があります。請求先アカウントの一覧を表示するには、以下を実行します。
gcloud beta billing accounts list次に、BILLING_ACCOUNTをこのストレージバケットの請求先にしたいアカウントに置き換えます。
BILLING_ACCOUNT="0X0X0X-0X0X0X-0X0X0X" # Replace with account from the listgcloud beta billing projects link \ "$GCP_PROJECT_ID" \ --billing-account "$BILLING_ACCOUNT"ストレージバケットの作成
次に、ファイル用のGoogle Cloud Storageバケットを作成します。私はすべてのオブジェクトを公開するバケットを作成しましたが、ランダムなバケット名を推測することの困難さに依拠しています。
RANDOM_SUFFIX=$(head /dev/urandom | tr -dc 'a-z0-9' | head -c 24 ; echo '')BUCKET_NAME="mediagoblin-${RANDOM_SUFFIX}"# All the videos need to be public for users to access them directly from the# browser, but we're adding enough entropy to the bucket name to make it# infeasible for unauthorized users to access the files by guessing or# enumerating URLs.UNIFORM_ACCESS="on"# Bucket storage properties. You can adjust these depending on your preferences.STORAGE_CLASS="Standard"BUCKET_LOCATION="US-EAST1"# Create the GCS bucket.gsutil mb \ -p "$GCP_PROJECT_ID" \ -c "$STORAGE_CLASS" \ -l "$BUCKET_LOCATION" \ -b "$UNIFORM_ACCESS" \ "gs://${BUCKET_NAME}/"# Set uniform access policy for the bucket.# We use legacyObjectReader because it grants read access to individual files but# prevents clients from exploring the bucket's contents.gsutil iam ch allUsers:roles/storage.legacyObjectReader "gs://${BUCKET_NAME}"ファイルをGoogle Cloud Storageにコピーする
MediaGoblinコンテナが生成したMG_SERVING_DIRディレクトリ内のファイルを覚えていますか?それらを先ほど作成したGoogle Cloud Storageバケットに移動します。
gsutil -m cp -r "${MG_SERVING_DIR}/media/public/*" "gs://${BUCKET_NAME}/"RANDOM_FOLDER=$(head /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 16 ; echo '')gsutil cp "${MG_SERVING_DIR}/mediagoblin.db" "gs://${BUCKET_NAME}/${RANDOM_FOLDER}/"# Save the public URL of the mediagoblin.db file.MEDIAGOBLIN_DB_URL="https://storage.googleapis.com/${BUCKET_NAME}/${RANDOM_FOLDER}/mediagoblin.db"mediagoblin.dbの保存場所にさらにランダム性を加えています。そうしないと、バケット名を知っている誰もがデータベースファイルを発見できてしまうからです。このようにエントロピーを追加することで、コレクション全体を公開することなく、個別の動画を共有できるようになります。Herokuへのデプロイ
メディアファイルがGoogle Cloud Storage上のインターネットからアクセス可能な場所に置かれたので、あとはMediaGoblinサーバーをデプロイするだけです。HerokuはDockerコンテナに対して寛大な無料枠を提供しており、セットアップも簡単なので利用しています。
このマシンでまだHeroku CLIの認証を行っていない場合は、Herokuの認証情報でログインしてください。
heroku login --interactive次に、Herokuのコンテナレジストリに認証し、新しいHerokuアプリを作成します。デフォルトでは、作成されるアプリは無料のHeroku dynoになります。
heroku container:loginRANDOM_SUFFIX=$(head /dev/urandom | tr -dc 'a-z0-9' | head -c 10 ; echo '')HEROKU_APP_NAME="mediagoblin-${RANDOM_SUFFIX}"heroku apps:create "$HEROKU_APP_NAME"最後のステップは、認証設定とタイトルでDockerビルドをカスタマイズし、Herokuにプッシュして公開することです。
pushd $(mktemp -d)REPO="https://github.com/mtlynch/mediagoblin-docker.git"# This is a special branch I created that adds authentication and adds a few# other features.TARGET_BRANCH="mtlynch-custom"git clone --single-branch --branch "$TARGET_BRANCH" "$REPO" .HTTP_AUTH_USER="mediagoblin" # change this to a username you chooseHTTP_AUTH_PASS="goblinmedia" # change this to a password you choose# Title you want for your MediaGoblin server.HTML_TITLE="My Demo MediaGoblin Server"heroku container:push web \ --app "$HEROKU_APP_NAME" \ --arg "HTTP_AUTH_USER=${HTTP_AUTH_USER},HTTP_AUTH_PASS=${HTTP_AUTH_PASS},GCS_BUCKET=${BUCKET_NAME},MEDIAGOBLIN_DB_URL=${MEDIAGOBLIN_DB_URL},HTML_TITLE=${HTML_TITLE}"heroku container:release --app "$HEROKU_APP_NAME" web && \ printf "Your app is live at https://${HEROKU_APP_NAME}.herokuapp.com/\n"すべてがうまくいけば、新しいMediaGoblinインスタンスのURLが以下のように表示されます。
Your app is live at https://mediagoblin-v5lmqis51k.herokuapp.com/おまけ:個別の動画を共有する
この方法で家族の動画を保存する場合、家族にはすべての動画を閲覧・アクセスできるようにしたい一方で、コレクション全体へのアクセス権を与えずに特定の動画だけを他の人と共有したい場合もあるでしょう。
その場合は、MediaGoblinインターフェースの「Original file」リンクをクリックします。
これにより、ゲストはその個別ファイルにアクセスできるようになりますが、Google Cloud Storageバケットの権限を設定した方法により、ライブラリを探索して他の動画にアクセスすることはできません。
「original file」リンクは、以下のようなURLになります。
https://storage.googleapis.com/mediagoblin-39dpduhfz1wstbprmyk5ak29/media_entries/4/Texas_Family_-_1952_-_04_-_Trudy_Working_at_the_Loom.mp4
ご覧のとおり、上記のURLがあればその1本の動画を視聴できますが、バケットを探索して他の動画を見つけることはできません。
ソースコード
| リポジトリ | 説明 |
|---|---|
| MediaGoblin | MediaGoblinコアリポジトリのミラー+Circle CI設定。 ブランチmtlynch-customには、私のインスタンス用のカスタム修正が含まれています(古い動画プレーヤーを置き換え、不要なUIの一部を削除しています)。 |
| mediagoblin-docker | MediaGoblin用のDockerイメージをビルドします。 ブランチmtlynch-customは、Google Cloud Storageバケットへの紐付けやHTTP Basic Authenticationの追加を含む私のカスタマイズでビルドされます。 |
| process-home-videos | 素の動画ファイルをクリップに分割し、それらのクリップをMediaGoblinに公開するためのPythonスクリプトです。 |
記事をランダムに読む

