使用 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:片段起始帧的编号。- 在示例的 Texas Farm 素材中,最开始的几帧是空白的,因此第一个片段从第 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 文件,该文件会指示其它脚本如何切分视频并导入到 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 实例 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,你可以观看该单个视频,但无法通过浏览存储桶来找到其它视频。
源代码
| 仓库 | 描述 |
|---|---|
| MediaGoblin | MediaGoblin 核心仓库的镜像 + CircleCI 配置。 分支 mtlynch-custom 包含了针对我个人实例的自定义修复(替换了旧的视频播放器并精简了 UI 中不需要的部分)。 |
| mediagoblin-docker | 为 MediaGoblin 构建 Docker 镜像。 分支 mtlynch-custom 使用了我的自定义配置进行构建,包括指向 Google Cloud Storage 存储桶以及添加 HTTP Basic Authentication。 |
| process-home-videos | 用于将原始视频文件切成片段并发布到 MediaGoblin 的 Python 脚本。 |
随机一篇博客


评论
登录后参与讨论