My Eight-Year Quest to Digitize 45 Videotapes (Part Two)

Michael Lynch

我耗時八年將 45 卷錄影帶數位化(下)

上篇中,我描述了將舊家庭影片擷取為數位格式並分割成個別場景的艱辛歷程。處理完所有片段後,我希望瀏覽這些影片的體驗能像在 YouTube 上尋找影片一樣簡單。由於這些影片是我家人私密的回憶,真正的 YouTube 又太過公開。我需要一種既方便使用又安全的分享方式。

步驟三:分享

ClipBucket,那個號稱開源卻幾乎無法安裝的 YouTube 複刻版

我嘗試的第一個解決方案是ClipBucket,它自稱為可自行架設的開源 YouTube 複刻版。

ClipBucket 在 GitHub 上的儲存庫

ClipBucket 是使用者可自行架設(理論上)的 YouTube 開源複刻版。

令人費解的是,ClipBucket 完全沒有提供安裝說明。透過一份第三方指南,我使用Ansible這套伺服器組態管理工具將安裝流程自動化

困難的原因之一是 ClipBucket 的安裝指令碼根本就是壞的。當時身為 Google 員工,我無法為 YouTube 的複刻版貢獻修補程式,但我提交了一份錯誤回報,應該足以讓修正方式一目了然。數個月過去,他們始終未回應這個問題。相反地,他們在每次發布新版本時還引入了更多導致無法運行的錯誤。

ClipBucket 的商業模式是顧問服務——他們免費釋出程式碼,並向需要部署協助的客戶收費。慢慢地我才恍然大悟,靠付費安裝支援賺錢的公司,大概對讓使用者自行部署沒什麼興趣。

MediaGoblin,更現代的替代方案

在被 ClipBucket 搞得灰心了幾個月後,我重新評估了現有的選擇,找到了 MediaGoblin

MediaGoblin 首頁

MediaGoblin 是一個可自行架設的媒體分享平台。

MediaGoblin 有很多讓人喜歡的地方。不同於 ClipBucket 難以直視的 PHP,MediaGoblin 是用我相當熟悉的 Python 撰寫的。它提供了命令列介面,讓影片上傳的自動化變得容易。最棒的是,MediaGoblin 提供了 Docker 映像檔,能省去所有安裝上的猜測工夫。

Docker 是一項能讓開發者為應用程式打造可隨處執行的獨立執行環境的技術。我在許多專案中都大量依賴它。

重新將 MediaGoblin Docker 化的意外困難

我原以為 MediaGoblin 的 Docker 映像檔會讓部署變得輕而易舉。結果,並非如此。

預先建置的映像檔缺少我需要的兩項功能:

  • Authentication
    • MediaGoblin 預設是公開的,因此我需要一種防止陌生人存取網站的方法。
  • Transcoding(轉碼)
    • 每次上傳影片時,MediaGoblin 都會嘗試重新編碼以達到最佳串流效果。對於原本就適合串流的影片,這個步驟反而會降低畫質並浪費運算資源。
    • MediaGoblin 提供了跳過 transcoding 的設定選項,但現有的 Docker 映像檔無法設定。

沒問題。Docker 映像檔是開源的,所以我可以自行重新建置

可惜的是,該 Docker 映像檔已無法基於目前的 MediaGoblin 儲存庫建置。我嘗試將其同步到最後一次建置成功的版本對應的原始碼,但也失敗了。即使用的是完全相同的程式碼,MediaGoblin 的外部相依套件也已悄然改變,導致建置失敗。耗費數十個小時,反覆經歷 MediaGoblin 每次超過 10 分鐘的建置流程後,我終於讓它運作起來。

幾個月後,同樣的情況再次發生。在過去幾年裡,MediaGoblin 頻繁變動的相依套件已經好幾次搞壞了我的建置,就連撰寫本文期間又發生了一次。我最終建立了自己的 MediaGoblin 分支,將所有相依套件硬性指定為明確版本。換句話說,與其含糊地聲稱 MediaGoblin 可相容於 celery >= 3.0 的任何版本,我將其設定為相依於 celery 的 4.2.1 版本,因為我已針對該版本測試過 MediaGoblin。看來 MediaGoblin 需要一套 reproducible builds(可重現建置) 的機制,但我目前還沒有著手處理。

總之,經過數小時的奮戰,MediaGoblin 終於達到我能在 Docker 內建置與調整的狀態。從那之後,要跳過不必要的影片 transcoding加入 Nginx 以實現身分驗證就變得相當直接。

步驟四:託管

在本地端機器上讓 MediaGoblin 於 Docker 中運行後,下一步就是將這套環境部署到雲端伺服器,讓家人也能存取這些影片。

MediaGoblin 與影片儲存問題

有許多平台能接收應用程式的 Docker 映像檔並將其託管在可公開存取的網址上。棘手之處在於,除了 MediaGoblin 應用程式本身,還有 33 GB 的影片檔案需要分享。雖然可以將這些檔案硬塞進 Docker 映像檔中,但那既笨重又難看。僅僅修改設定檔中的一行,就得重新部署 33 GB 的資料。

在使用 ClipBucket 時,我透過 gcsfuse 解決了這個問題,gcsfuse 是一套能讓作業系統將 Google Cloud Storage 上的目錄掛載為一般檔案系統路徑的工具。我將影片檔案放在 Google Cloud Storage 上,並使用 gcsfuse 讓它們在 ClipBucket 看來就像本地檔案一樣。

差別在於 ClipBucket 運行於完整的虛擬機器中,而 MediaGoblin 則運行於 Docker 容器中。在 Docker 下掛載雲端儲存檔案結果遠比想像中複雜。我花了數十小時解決各種陷阱,並為此寫了一篇完整的部落格文章

MediaGoblin + Docker + gcsfuse 架構圖

整合 MediaGoblin 與 Google Cloud Storage 的初期架構,記錄於我2018 年的部落格文章

經過數週努力讓所有元件協同合作後,終於成功了。在未對 MediaGoblin 程式碼做任何修改的情況下,我成功讓它將媒體檔案讀寫到 Google Cloud Storage。

唯一的缺點是這讓 MediaGoblin 慢到無法使用。載入首頁的影片縮圖竟要整整 20 秒。若在觀看影片時快轉,MediaGoblin 會停滯長達 10 秒才恢復播放。

根本問題在於影片與圖片檔案到達使用者端前必須經過漫長迂迴的路徑。它們必須從 Google Cloud Storage 經過 gcsfuse 到 MediaGoblin 再到 Nginx,最後才抵達使用者的瀏覽器。gcsfuse 是主要的瓶頸,因為它並未針對速度進行最佳化。它在專案首頁上就已警告其延遲不佳:

來自 gcsfuse GitHub 儲存庫的延遲警告

gcsfuse 文件中關於效能緩慢的警告

理想情況下,瀏覽器應直接從 Google Cloud Storage 抓取檔案,繞過所有中間層。但要如何在不深入 MediaGoblin 程式碼、也不加入與 Google Cloud Storage 的複雜整合邏輯的情況下做到這點呢?

Nginx sub_filter 技巧

幸好,我找到了一個簡單但有點醜陋的解法。我在 Nginx 的 default.conf 檔案中加入了這個篩選器

sub_filter "/mgoblin_media/media_entries/" "https://storage.googleapis.com/MY-GCS-BUCKET/media_entries/";
sub_filter_once off;

在我的架構中,Nginx 作為終端使用者與 MediaGoblin 之間的代理。上述指示告訴 Nginx 在將 MediaGoblin 的所有 HTML 回應轉交給終端使用者前,先執行搜尋與取代。Nginx 會將 MediaGoblin 上所有指向媒體檔案的相對路徑替換為 Google Cloud Storage 的網址。

例如,MediaGoblin 會產生如下的 HTML:

<video width="720" height="480" controls autoplay>
  <source
    src="/mgoblin_media/media_entries/16/Michael-riding-a-bike.mp4"
    type="video/mp4"
  />
</video>

Nginx 會將回應修改為如下:

<video width="720" height="480" controls autoplay>
  <source
    src="https://storage.googleapis.com/MY-GCS-BUCKET/media_entries/16/Michael-riding-a-bike.mp4"
    type="video/mp4"
  />
</video>

整體架構如下:

MediaGoblin + Docker + Nginx 重寫回應至 GCS 的架構圖

Nginx 重寫來自 MediaGoblin 的回應,讓客戶端能直接從 Google Cloud Storage 取得媒體檔案。

這個解法的巧妙之處在於完全不需要修改 MediaGoblin 的程式碼。僅用兩行的 Nginx 指示,就能無縫整合 MediaGoblin 與 Google Cloud Storage,即使這兩個服務彼此完全不知對方的存在。

注意:此解法需要將 Google Cloud Storage 上的檔案設為可供所有人讀取。為了降低未經授權存取的風險,我使用一組長且隨機的儲存桶名稱(例如 mediagoblin-39dpduhfz1wstbprmyk5ak29)並確保儲存桶的存取控制政策能防止未經授權的使用者列出目錄內容。

最終成果

至此,我已擁有完整且可運作的解決方案。MediaGoblin 在 Google Cloud Platform 上於自己的容器中愉快地運行,這表示我不需要頻繁地修補或升級。整個流程都已自動化且可重現,讓推送變更或回溯到先前版本都變得輕而易舉。

我的家人很喜歡瀏覽影片的便利性。有了 Nginx 的效能技巧,體驗就跟瀏覽 YouTube 一樣流暢。

瀏覽畫面如下:

MediaGoblin 瀏覽畫面

我家家庭影片分享伺服器的瀏覽畫面

點擊縮圖後會進入如下畫面:

MediaGoblin 顯示影片的螢幕截圖

在媒體伺服器上觀看單一片段

經過多年的努力,能為家人實現我最初設想、宛如 YouTube 般探索影片的體驗,讓人感到無比欣慰。

番外篇:將成本壓至每月 1 美元以下

家庭影片是那種每隔幾個月才會看一次的東西。我的家人一年總共只存取該網站約 20 小時,但我的伺服器卻 24 小時全年無休地運行著。我每月支付 15 美元,卻讓伺服器有 99.7% 的時間處於閒置狀態。

2018 年底,Google 推出了 Cloud Run。其殺手級功能是以足夠快的速度啟動 Docker 容器來回應 HTTP 請求。這讓伺服器可以在待機模式下等待,只有在有人造訪網址時才運行。對於像我這樣不常被存取的應用程式,這將成本從每月 15 美元降至每年僅需幾美分。

由於某些我已不記得的原因,Cloud Run 無法與我的 MediaGoblin 映像檔搭配運作。但 Cloud Run 的出現提醒了我,Heroku 也免費提供類似的服務,而且他們的工具比 Google 的好用得多。

有了免費的應用程式伺服器,我唯一的成本就只剩下資料儲存。Google 的標準區域儲存費用為每 GB 2.3 美分,而影片合集佔用了 33 GB,因此我每月只需支付 0.77 美元。

來自 Google Cloud Platform 的 0.77 美元帳單

整套解決方案的成本每月僅需 0.77 美元。

給想嘗試者的建議

這個過程顯然花了我很長的時間,但我希望本文能為其他想數位化與分享家庭影片的人省下 80 至 90% 的工夫。下一節有關於我這套解決方案細節的詳細教學,但以下先提供一些數位化與分享家庭影片的通用建議:

  • 在原始擷取與剪輯階段盡可能擷取更多詮釋資料。
    • 錄影帶上的標籤往往含有寶貴資訊。
    • 記錄每個片段來自哪一卷錄影帶以及順序。
    • 記下片段中任何關於錄製日期的線索。
  • 考慮將原始擷取工作外包給專業人士。
    • 要達到影片數位化公司那樣的品質,對個人而言極其困難且昂貴。
    • 但請避開一家名為 EverPresent 的公司(若想了解詳情,請來信詢問)。
  • 若自行擷取,請準備充足的磁碟空間。
    • 未壓縮的標準畫質影片擷取檔每分鐘約 100 至 200 MB。
    • 我將所有檔案都儲存在我的 10 TB Synology DS412+ 上。
  • 以與應用程式無關的格式記錄詮釋資料。
    • 影片片段描述、時間碼、日期等。
    • 若將其保存在特定應用程式的格式中(或更糟的是直接丟棄),當你決定改用其他解決方案時,就無法重現這些成果。
    • 在剪輯時觀看影片,會看到許多有用的詮釋資料。若不記錄下來就會遺失。
      • 影片中發生了什麼事?
      • 有哪些人在裡面?
      • 是何時錄製的?
  • 標記你最喜歡的片段。
    • 老實說,大多數家庭影片素材都相當無聊。
    • 我會為喜愛的片段加上「best of」標籤,想看有趣影片時就瀏覽這些。
  • 盡早建構端到端解決方案。
    • 我當初是先擷取所有錄影帶,再剪輯所有錄影帶,依此類推。
    • 我真希望當初能從單一一卷錄影帶開始,完成分享所需的全部工作。那樣就能讓我明白流程前期的決策如何影響最終成果。
  • 盡量減少 transcoding。
    • 每次編輯或重新編碼片段,都會降低畫質。
    • 以盡可能高的品質擷取原始素材,然後將每個片段僅 transcode 一次為瀏覽器可原生播放的格式。
  • 使用最簡單可行的影片片段分享方案。
    • 回顧來看,對於僅需產生網頁來展示一組固定影片檔案這種不算複雜的情境,MediaGoblin 是過於複雜的工具。
    • 若重新來過,我會使用像 HugoJekyllGridsome 這類的 static site generator(靜態網站產生器)。
  • 製作一支蒙太奇。
    • 影片蒙太奇是將多支家庭影片中的精彩時刻集結在一起的有趣方式。
    • 蒙太奇的精髓在於音樂。The National 的〈Slow Show〉非常適合用於蒙太奇,卻似乎沒什麼人發現。

我的流程端到端完整教學

若想了解我如何做到這些細節,我製作了一份教學,完整呈現從頭到尾的工作流程,其中包含可重現此流程的所有原始碼與指令。


插圖由 Loraine Yow(蘿蘭·尤)繪製。

特別感謝我的家人允許我分享這些精選片段與靜態影像,感謝他們當初記錄下這一切,並在整個過程中的全力支持。

原文由 Michael Lynch 發布

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