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

Michael Lynch

我花了八年將 45 卷錄影帶數位化(下)

原文由 Michael Lynch 發布,訂閱此部落格

上篇中,我描述了自己如何歷經艱辛,將家中舊的家庭錄影帶轉成數位格式,並分割成一個個獨立的場景。處理完所有片段後,我希望瀏覽這些影片的體驗,能像在 YouTube 上搜尋影片一樣簡單。但這些影片是我們全家的私人回憶,放在真正的 YouTube 上實在太公開了。我需要一種既好用又安全私密的分享方式。

步驟三:分享

ClipBucket,那個號稱開源 YouTube 分身、卻根本裝不起來的東西

我第一個嘗試的解決方案是ClipBucket,它標榜自己是可自行架設的開源 YouTube 分身。

GitHub 上的 ClipBucket 儲存庫

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 映像檔,部署會變得輕而易舉。結果,完全不是這麼回事。

我需要的兩個功能,在現成的映像檔中都沒有:

  • 身份驗證
    • MediaGoblin 預設是公開的,所以我需要一種方法來防止陌生人存取網站。
  • 轉碼
    • 每次上傳影片時,MediaGoblin 都會嘗試重新編碼以達到最佳的串流效果。對於已經適合串流的影片來說,這個步驟只會降低畫質、浪費運算資源。
    • MediaGoblin 雖然提供了跳過轉碼的設定選項,但現有的 Docker 映像檔卻無法設定。

沒關係。Docker 映像檔是開源的,所以我可以自己重新建置

可惜的是,這個 Docker 映像檔已經無法對應當前的MediaGoblin 儲存庫成功建置。我試著將它同步到上次建置成功時對應的版本,但還是失敗。即使我用完全相同的程式碼來建置,MediaGoblin 的外部依賴套件早已在背後悄悄更動,導致建置失敗。花了數十個小時,反覆經歷 MediaGoblin 每次長達 10 分鐘以上的建置過程後,我終於讓它動了起來。

幾個月後,同樣的狀況又發生了。過去兩年來,MediaGoblin 頻繁變動的依賴套件已經好幾次弄壞我的建置,甚至在我撰寫本文時又發生了一次。最後我乾脆做了自己的 MediaGoblin 分支,把所有依賴套件寫死成明確的版本號。換句話說,與其含糊地宣稱 MediaGoblin 支援任何 celery >= 3.0 的版本,我直接讓它依賴於我實際測試過的 celery 4.2.1 版。MediaGoblin 似乎需要一套可重現建置的機制,只是我還沒空去處理。

總之,經過好幾個小時的奮戰,MediaGoblin 總算到了可以在 Docker 內建置與調整的階段。接下來,要跳過不必要的影片轉碼加入 Nginx 來做身份驗證,就相對單純了。

步驟四:託管

MediaGoblin 已經能在我本機的 Docker 中運作,下一步就是將整套環境部署到雲端伺服器上,讓家人也能存取這些影片。

MediaGoblin 與影片儲存的難題

市面上有不少平台可以直接接收應用程式的 Docker 映像檔,並將其部署到公開網址上。麻煩的是,除了 MediaGoblin 應用程式本身,還有 33 GB 的影片檔案要分享。雖然可以把這些檔案硬塞進 Docker 映像檔裡,但那既笨拙又難看——只要改一行設定檔,就得重新部署 33 GB 的資料。

在使用 ClipBucket 時,我是用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 小時,但我的伺服器卻是全年無休地跑著。我每個月要付 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」標籤,想看有趣的影片時就直接瀏覽這些精選。
  • 盡早建立端到端的完整解決方案。
    • 我當初是先把所有錄影帶全部擷取完,才開始剪輯,以此類推。
    • 我真希望當初是先從一卷錄影帶開始,把分享所需的流程完整跑通一次。這樣就能更早看出前期決策如何影響最終成果。
  • 盡量減少轉碼次數。
    • 每次編輯或重新編碼片段,都會讓畫質劣化。
    • 以盡可能高的畫質擷取原始素材,然後將每個片段僅轉碼一次,轉成瀏覽器可直接播放的格式。
  • 分享影片片段時,使用越簡單的解決方案越好。
    • 回頭看,MediaGoblin 對於「產生網頁來展示一組不會變動的影片檔」這種其實不算複雜的需求來說,實在是過於複雜的工具。
    • 如果重來一次,我會改用像 HugoJekyllGridsome 這類的靜態網站產生器。
  • 做一部精華剪輯。
    • 影片蒙太奇是將多支家庭影片中的精彩時刻集結在一起的有趣方式。
    • 蒙太奇的靈魂在於音樂。The National 的〈Slow Show〉非常適合用來做蒙太奇,只是好像還沒什麼人發現。

我的完整流程端到端教學

如果你想了解我是如何一步步實作的,我準備了一份教學,完整展示從頭到尾的工作流程。裡面包含了所有可重現此流程的原始碼與指令。


插圖由 Loraine Yow 繪製。

特別感謝家人在過程中慷慨同意讓我分享這些精選片段與截圖,感謝他們當初錄下這一切,也感謝他們一路以來的支持。

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

留言