使用 MediaGoblin 編輯與分享家庭影片
目標
本教學將示範如何將數位化的影片擷取內容編輯成較小的片段,並發布到您自有的、受密碼保護的 MediaGoblin 伺服器上。您將使用免費的 Heroku dyno,因此運行這個私人媒體伺服器唯一的經常性成本,就是 Google Cloud Storage 的儲存費用,每 GB 僅 2.3 美分。
我使用這個工作流程來編輯並分享家人的家庭影片,每月成本僅 0.77 美元。若想了解詳細的背景故事,請參閱部落格文章「My Eight-Year Quest to Digitize 45 Videotapes(《我花八年將 45 捲錄影帶數位化的歷程》)」。這個工作流程適用於任何包含大量子片段、您想剪輯出來分享的影片檔案。
前置需求
- 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 編輯器。理論上任何影片播放器都能使用,但若選擇支援逐格步進與可縮放時間軸的播放器,將能節省許多時間。
接下來,請建立一份試算表。您可以從我的範本開始。我將在下方說明各欄位的意義:

請使用與我的範本相同格式的試算表來編目所有詮釋資料。
tape_id:這是不含副檔名的影片檔案名稱。tape_shortname:這是用來識別來自此原始檔案的檔案所使用的簡稱,可以與tape_id相同。tape_friendly_name:MediaGoblin 顯示此片段來源錄影帶時,會使用此欄位。如果有比檔案名稱更具描述性的名稱,請填在此欄位。scene_start_frame:片段開始的第一個影格編號。- 以德州農場範例影片來說,最前面的幾個影格是空白的,因此第一個片段從第 28 格開始。
title:此場景的標題,將用於輸出檔案名稱與 MediaGoblin 的縮圖。- 若要讓
render_scenes忽略某段素材,請將其標題命名為junk。
- 若要讓
description:(選填)描述此場景中發生的內容,將顯示在 MediaGoblin 觀看影片頁面中片段的下方。- hashtags:在
description欄之後,您可以建立以井字號為前綴的標籤欄位。只要在某個 hashtag 欄位中填入y(或任何值),該標籤就會被加入到 MediaGoblin 中的該片段。- 例如:我的試算表中第 9 列在
#adam和#archie欄位下填有y,當publish_to_mediagoblin將該片段加入 MediaGoblin 時,就會為其加上adam和archie標籤。 - 這些欄位的用途是,若您想在影片中標記家人或寵物,可以為他們新增 hashtag 欄位,並在他們出現的每個片段對應欄位中填入
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
建立 scenes YAML 檔案
完成片段編目後,您可以使用我的指令碼將 CSV 轉換為 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:6543您可以造訪 http://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 的網頁介面上看到全部八個片段的縮圖。
如果您點擊某部影片,將無法在一般的影片播放器中觀看,因為 MediaGoblin 使用的網頁播放器過於老舊,不支援現代、針對串流最佳化的影片格式。不過,您仍可點擊「Original file」連結來觀看。

本機 MediaGoblin 實例中單一影片的檢視畫面
別擔心,我們會在下一個步驟中修正這個問題,讓您能在內嵌播放器中正常觀看所有影片。
本機容器的任務已完成,請將其移除。
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.bin設定 Google 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 實例網址:
Your app is live at https://mediagoblin-v5lmqis51k.herokuapp.com/額外技巧:分享個別影片
如果您使用此流程來存放家庭影片,您大概會希望家人能夠瀏覽並存取所有影片,但有時您可能想與他人分享個別影片,而不讓他們存取整個收藏。
若要這麼做,請在 MediaGoblin 介面中點擊「Original file」連結:
這會讓訪客取得該單一檔案的存取權限,但由於您設定 Google Cloud Storage 值區權限的方式,他們將無法瀏覽媒體庫或存取其他影片。
「Original file」連結會提供一個如下所示的網址:
https://storage.googleapis.com/mediagoblin-39dpduhfz1wstbprmyk5ak29/media_entries/4/Texas_Family_-_1952_-_04_-_Trudy_Working_at_the_Loom.mp4
如您所見,透過上述網址,您可以觀看該單一影片,但無法透過探索值區來找到其他影片。
原始碼
| 儲存庫 | 說明 |
|---|---|
| MediaGoblin | MediaGoblin 核心儲存庫的鏡像,另含 CircleCI 設定。 mtlynch-custom 分支包含針對我個人實例的自訂修正(替換了舊的影片播放器,並精簡了部分不需要的 UI)。 |
| mediagoblin-docker | 為 MediaGoblin 建置 Docker 映像檔。 mtlynch-custom 分支包含我的自訂內容,其中包括指向 Google Cloud Storage 值區以及加入 HTTP Basic Authentication。 |
| process-home-videos | 用於將原始影片檔案切割成片段,然後將這些片段發布至 MediaGoblin 的 Python 指令碼。 |
隨機一篇部落格

