使用 MediaGoblin 編輯與分享家庭影片
原文由 Michael Lynch 于 發布,訂閱此部落格
目標
本教學將示範如何把數位化的影片擷取檔剪輯成較小的片段,並發布到你自行架設、受密碼保護的 MediaGoblin 伺服器上。你將使用免費的 Heroku dyno,因此執行這個私人媒體伺服器唯一的持續性成本,就是 Google Cloud Storage 的儲存費用,每 GB 只要 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 編輯器。技術上任何影片播放器都能用,但如果你選擇支援逐格播放和可縮放時間軸的播放器,會節省很多時間。
接著,建立一份試算表。你可以從我的試算表開始。我會在下面說明各個欄位:

請用與我的試算表相同格式的試算表來整理所有詮釋資料。
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標籤。 - 這些欄位的用途是,如果你有想在影片中標記的家人或寵物,可以為他們新增 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_ 前綴代表容器內路徑的命名慣例。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 核心儲存庫的鏡像,加上 Circle CI 設定。 分支 mtlynch-custom 包含了針對我個人實例的自訂修正(替換了舊的影片播放器,並精簡了部分我不需要的介面)。 |
| mediagoblin-docker | 為 MediaGoblin 建置 Docker 映像檔。 分支 mtlynch-custom 以我的客製化設定進行建置,包含指向 Google Cloud Storage 儲存空間以及加入 HTTP Basic Authentication。 |
| process-home-videos | 用於將原始影片檔分割成片段,並將這些片段發布至 MediaGoblin 的 Python 指令碼。 |
隨機一篇部落格


留言
登入後參與討論