Editing and Sharing Home Videos with MediaGoblin

Michael Lynch

使用 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 帳號
  • 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 編輯器。理論上任何影片播放器都能使用,但若選擇支援逐格步進與可縮放時間軸的播放器,將能節省許多時間。

接下來,請建立一份試算表。您可以從我的範本開始。我將在下方說明各欄位的意義:

我的 Google Sheets 試算表螢幕截圖

請使用與我的範本相同格式的試算表來編目所有詮釋資料。

  • 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 時,就會為其加上 adamarchie 標籤。
    • 這些欄位的用途是,若您想在影片中標記家人或寵物,可以為他們新增 hashtag 欄位,並在他們出現的每個片段對應欄位中填入 y
  • date:此片段的錄製日期。
    • 可使用下列其中一種格式:
      • YYYY-MM-DD
      • YYYY-MM
      • YYYY
  • 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
這算是我當初一個有點蠢的設計選擇。其實其他指令碼大可直接讀取 CSV,但我當時就是這樣做的。

切割片段

準備好所有詮釋資料後,就可以將大型影片檔案切割成一系列較小的片段了:

# 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 中。

注意:為了區分主機上的路徑與 MediaGoblin Docker 容器內的路徑,我採用了以下命名慣例: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 的網頁介面上看到全部八個片段的縮圖。

如果您點擊某部影片,將無法在一般的影片播放器中觀看,因為 MediaGoblin 使用的網頁播放器過於老舊,不支援現代、針對串流最佳化的影片格式。不過,您仍可點擊「Original file」連結來觀看。

本機 MediaGoblin 實例中單一影片的檢視畫面

本機 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 值區。我建立了一個將所有物件設為公開的值區,但仰賴隨機值區名稱難以被猜中來確保安全性。

24 個字元的隨機後綴代表共有 3624 = 2.24 x 1037 種可能性。換句話說,攻擊者需要猜測約 1037 個值區名稱才能找到您的值區。這相當於猜測一組 128 位元私鑰的難度。
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/
注意:由於您使用的是 Heroku 的免費方案,Heroku 會在您於瀏覽器中發出請求時才按需啟動您的 MediaGoblin 實例。這表示首次載入頁面需要 30 至 60 秒,且您可能會看到 HTTP 502 Gateway 逾時錯誤,這是正常現象。一旦應用程式開始運行,在閒置數小時之前,都會保持快速且靈敏。

額外技巧:分享個別影片

如果您使用此流程來存放家庭影片,您大概會希望家人能夠瀏覽並存取所有影片,但有時您可能想與他人分享個別影片,而不讓他們存取整個收藏。

若要這麼做,請在 MediaGoblin 介面中點擊「Original file」連結:

指向「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

如您所見,透過上述網址,您可以觀看該單一影片,但無法透過探索值區來找到其他影片。


原始碼

儲存庫說明
MediaGoblinMediaGoblin 核心儲存庫的鏡像,另含 CircleCI 設定。

mtlynch-custom 分支包含針對我個人實例的自訂修正(替換了舊的影片播放器,並精簡了部分不需要的 UI)。
mediagoblin-docker為 MediaGoblin 建置 Docker 映像檔。

mtlynch-custom 分支包含我的自訂內容,其中包括指向 Google Cloud Storage 值區以及加入 HTTP Basic Authentication
process-home-videos用於將原始影片檔案切割成片段,然後將這些片段發布至 MediaGoblin 的 Python 指令碼。

原文由 Michael Lynch 發布

本文章由 muse-spark-1.2-contributor 進行翻譯