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

Michael Lynch

45개 비디오테이프를 디지털화하기 위한 8년간의 도전 (2부)

원문은 Michael Lynch님이 에 게재했습니다. 이 블로그 구독하기

1부에서는 오래된 홈 비디오를 디지털 형식으로 담아내고 개별 장면으로 나누기까지의 고된 여정을 설명했다. 모든 클립의 처리를 마친 뒤에는 마치 유튜브에서 영상을 찾아보듯 간편하게 영상을 둘러볼 수 있기를 바랐다. 이 영상들은 가족의 사적인 추억이 담긴 것이기에 진짜 유튜브에 올리기에는 너무 공개적이었다. 사용하기 쉽고 동시에 안전한 공유 방법이 필요했다.

3단계: 공유

ClipBucket, 사실상 설치할 수 없는 오픈소스 유튜브 클론

내가 처음 시도한 해결책은 스스로 호스팅할 수 있는 오픈소스 유튜브 클론을 표방하는 ClipBucket이었다.

GitHub의 ClipBucket 저장소

ClipBucket은 사용자가 직접 호스팅할 수 있는(이론상으로는) 오픈소스 유튜브 클론이다.

의아하게도 ClipBucket은 설치 안내를 전혀 제공하지 않았다. 서드파티 가이드를 참고해 서버용 구성 관리 도구인 Ansible을 이용해 설치 과정을 자동화했다.

어려움의 일부는 ClipBucket의 설치 스크립트가 완전히 망가져 있었다는 점이었다. 당시 Google 직원이었던 나는 유튜브 클론에 패치를 기여할 수 없었지만, 수정 방법이 명확히 드러나는 버그 리포트를 제출했다. 몇 달이 지났지만 그들은 문제를 인정조차 하지 않았다. 오히려 매 릴리스마다 더 많은 치명적인 오류를 새로 만들어냈다.

ClipBucket은 컨설팅 모델로 사업을 운영했다. 코드를 무료로 공개하고 배포에 도움이 필요한 고객에게 비용을 청구하는 방식이었다. 유료 설치 지원을 통해 수익을 내는 회사가 셀프 설치에는 그다지 관심이 없을 것이라는 사실이 서서히 깨달아졌다.

MediaGoblin, 더 현대적인 대안

ClipBucket 때문에 몇 달간 좌절한 끝에, 다른 선택지를 다시 살펴보다 MediaGoblin을 찾았다.

MediaGoblin 홈페이지

MediaGoblin은 셀프 호스팅 방식의 미디어 공유 플랫폼이다.

MediaGoblin은 마음에 드는 점이 많았다. 보기 흉한 PHP로 만들어진 ClipBucket과 달리 MediaGoblin은 내가 경험이 풍부한 Python으로 작성되어 있었다. 비디오 업로드를 쉽게 자동화할 수 있는 커맨드라인 인터페이스도 제공했다. 무엇보다 MediaGoblin은 Docker 이미지를 제공해 설치 과정에서 헤맬 필요가 없었다.

Docker는 애플리케이션을 어디서나 실행할 수 있는 독립적인 환경을 구축하도록 해주는 기술이다. 나는 수많은 프로젝트에서 Docker에 크게 의존하고 있다.

MediaGoblin을 다시 Docker화하는 의외의 난관

MediaGoblin의 Docker 이미지 덕분에 배포가 아주 쉬울 거라 생각했다. 하지만 전혀 그렇지 않았다.

미리 빌드된 이미지에는 내가 필요로 하는 두 가지 기능이 빠져 있었다.

  • 인증
    • MediaGoblin은 기본적으로 공개 상태라, 외부인이 사이트에 접근하지 못하도록 막을 방법이 필요했다.
  • 트랜스코딩
    • 비디오를 업로드할 때마다 MediaGoblin은 최적의 스트리밍을 위해 재인코딩을 시도한다. 이미 스트리밍에 적합한 비디오의 경우 이 과정은 화질을 떨어뜨리고 연산 자원을 낭비할 뿐이다.
    • MediaGoblin은 트랜스코딩을 건너뛰는 설정 옵션을 제공하지만, 기존 Docker 이미지는 설정 변경이 불가능했다.

문제없었다. Docker 이미지는 오픈소스였으니 직접 다시 빌드하면 됐다.

안타깝게도 Docker 이미지는 더 이상 최신 MediaGoblin 저장소를 기준으로 빌드되지 않았다. 마지막으로 성공했던 빌드와 동일한 버전에 맞춰 보려 했지만 역시 실패했다. 완전히 동일한 코드로 빌드했는데도 MediaGoblin의 외부 의존성이 그사이 바뀌어 빌드가 깨진 것이었다. 수십 시간을 쏟으며 10분 넘게 걸리는 MediaGoblin 빌드 과정을 수십 번 반복한 끝에 마침내 빌드에 성공했다.

몇 달 뒤 같은 일이 또 벌어졌다. 지난 2년 동안 MediaGoblin의 의존성 변경 때문에 내 빌드가 여러 번 깨졌고, 이 글을 쓰는 도중에도 한 번 더 그랬다. 결국 MediaGoblin을 직접 포크해 모든 의존성을 명시적 버전으로 고정했다. 다시 말해, MediaGoblin이 celery 3.0 이상이면 어떤 버전에서든 동작한다는 불확실한 선언 대신, 내가 직접 테스트한 celery 4.2.1 릴리스에 의존하도록 설정한 것이다. MediaGoblin에는 재현 가능한 빌드를 위한 메커니즘이 필요해 보이지만, 아직 그 작업까지는 손대지 않았다.

어쨌든 오랜 고생 끝에 MediaGoblin을 Docker 안에서 빌드하고 조정할 수 있는 단계에 이르렀다. 그 다음부터는 불필요한 비디오 트랜스코딩을 건너뛰고 인증을 위해 Nginx를 추가하는 작업은 비교적 간단했다.

4단계: 호스팅

로컬 머신에서 Docker로 MediaGoblin을 실행하게 되자, 다음 단계는 가족이 영상에 접근할 수 있도록 내 설정을 클라우드 서버에 배포하는 것이었다.

MediaGoblin과 비디오 저장 문제

애플리케이션의 Docker 이미지를 받아 공개 URL에서 호스팅해 주는 플랫폼은 많다. 문제는 MediaGoblin 애플리케이션 자체 외에도 공유해야 할 비디오 파일이 33GB나 있다는 점이었다. 파일을 Docker 이미지에 직접 넣는 것도 가능했지만 번거롭고 깔끔하지 못한 방법이었다. 설정 파일 한 줄을 바꾸기 위해서도 33GB의 데이터를 다시 배포해야 했기 때문이다.

ClipBucket을 쓸 때는 gcsfuse라는 유틸리티로 이 문제를 해결했다. gcsfuse는 운영체제가 Google Cloud Storage상의 디렉터리를 일반 파일시스템 경로처럼 불러올 수 있게 해준다. 비디오 파일을 Google Cloud Storage에 올려두고 gcsfuse를 이용해 ClipBucket에서는 로컬 파일인 것처럼 보이게 했다.

차이점은 ClipBucket은 완전한 가상 머신에서 실행된 반면 MediaGoblin은 Docker 컨테이너에서 실행된다는 것이었다. Docker 환경에서 클라우드 스토리지 파일을 마운트하는 작업은 훨씬 더 복잡했다. 온갖 함정을 해결하는 데 수십 시간을 쏟았고, 그 과정에 대해 별도의 블로그 글까지 썼다.

MediaGoblin + Docker + gcsfuse 아키텍처 다이어그램

2018년 블로그 글에 정리한 MediaGoblin과 Google Cloud Storage 통합을 위한 초기 아키텍처

몇 주에 걸쳐 모든 구성 요소가 잘 어우러지도록 조율한 끝에 마침내 동작했다. MediaGoblin 코드를 전혀 수정하지 않고도 미디어 파일을 Google Cloud Storage에서 읽고 쓰도록 속일 수 있었다.

유일한 문제는 MediaGoblin이 쓸 수 없을 정도로 느려졌다는 것이었다. 홈페이지에서 비디오 썸네일을 불러오는 데만 20초가 걸렸다. 비디오를 보다가 앞으로 건너뛰면 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 URL로 교체한다.

예를 들어 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 성능 트릭 덕분에 유튜브를 둘러보는 것만큼이나 반응이 빨랐다.

탐색 화면은 이렇게 생겼다.

MediaGoblin 탐색 화면

우리 가족 홈 비디오 공유 서버의 탐색 화면

썸네일을 클릭하면 다음과 같은 화면이 나왔다.

MediaGoblin에서 비디오를 표시하는 스크린샷

미디어 서버에서 개별 클립을 보는 화면

수년간의 작업 끝에, 처음에 상상했던 것처럼 가족에게 유튜브 같은 영상 탐색 경험을 선사할 수 있게 되어 엄청난 보람을 느꼈다.

보너스: 비용을 월 1달러 미만으로 낮추기

홈 비디오는 몇 달에 한 번씩만 보는 종류의 것이다. 우리 가족이 사이트에 접속한 시간은 1년에 총 20시간 정도였지만, 서버는 24시간 내내 돌아가고 있었다. 99.7%의 시간 동안 유휴 상태인 서버에 매달 15달러를 내고 있었던 셈이다.

2018년 말 Google은 Cloud Run을 출시했다. 핵심 기능은 HTTP 요청에 응답할 만큼 빠르게 Docker 컨테이너를 실행하는 것이었다. 이를 통해 서버는 대기 상태에 있다가 누군가 URL에 접속할 때만 실행될 수 있었다. 내 것처럼 자주 접속하지 않는 앱의 경우 비용이 월 15달러에서 연간 몇 센트로 줄어들었다.

이제 기억나지 않는 이유로 Cloud Run은 내 MediaGoblin 이미지와는 동작하지 않았다. 하지만 Cloud Run의 존재 덕분에 Heroku가 비슷한 서비스를 무료로 제공하고 있으며, 그 도구들이 Google보다 훨씬 사용하기 쉽다는 것을 떠올리게 됐다.

무료 앱 서버를 사용하니 유일한 비용은 데이터 저장 비용뿐이었다. Google 표준 리전 스토리지는 GB당 2.3센트이고, 비디오 컬렉션 용량이 33GB이므로 월 비용은 0.77달러에 불과했다.

Google Cloud Platform의 0.77달러 청구서

이 전체 솔루션의 비용은 월 0.77달러에 불과하다.

이 작업을 시도하려는 분들을 위한 팁

이 과정에 분명 오랜 시간이 걸렸지만, 이 글이 다른 사람들이 홈 비디오를 디지털화하고 공유하는 데 드는 수고를 80~90%는 줄여주길 바란다. 다음 섹션에는 내 솔루션의 세부 사항을 담은 자세한 워크스루가 있지만, 홈 비디오를 디지털화하고 공유하기 위한 일반적인 팁을 먼저 정리하면 다음과 같다.

  • 원본 캡처 및 편집 단계에서 가능한 한 많은 메타데이터를 확보하라.
    • 테이프에 붙은 라벨에 귀중한 정보가 담겨 있는 경우가 많다.
    • 어느 클립이 어떤 테이프에서 어떤 순서로 나왔는지 기록을 남겨라.
    • 클립에 녹화된 날짜에 대한 단서가 있으면 메모해 두라.
  • 원본 캡처는 전문가에게 맡기는 것을 고려하라.
    • 비디오 디지털화 업체만큼의 품질을 직접 구현하는 것은 극히 어렵고 비용도 많이 든다.
    • 다만 EverPresent라는 업체는 피하는 것이 좋다(자세한 내용이 궁금하면 이메일로 문의하라).
  • 직접 캡처한다면 충분한 디스크 공간을 확보하라.
    • 비압축 비디오 캡처는 표준 화질 기준으로 분당 약 100~200MB다.
    • 나는 모든 것을 10TB 용량의 Synology DS412+에 저장했다.
  • 메타데이터는 특정 애플리케이션에 종속되지 않은 형식으로 기록하라.
    • 클립 설명, 타임코드, 날짜 등
    • 특정 애플리케이션 전용 형식으로 보관하거나 아예 버리면, 다른 솔루션을 선택했을 때 작업을 재현할 수 없다.
    • 영상을 편집하면서 지켜보면 유용한 메타데이터가 많이 보인다. 기록하지 않으면 사라진다.
      • 영상에서 무슨 일이 일어나고 있는가?
      • 누가 등장하는가?
      • 언제 녹화된 것인가?
  • 즐겨찾기를 표시해 두라.
    • 솔직히 대부분의 홈 비디오 영상은 꽤 지루하다.
    • 나는 마음에 드는 클립에 “best of” 태그를 달아 두고, 재미있는 영상을 보고 싶을 때 그 태그가 붙은 영상만 둘러본다.
  • 엔드투엔드 솔루션을 가능한 한 빨리 구축하라.
    • 나는 모든 테이프를 먼저 캡처한 뒤, 그 다음에 전부 편집하는 식으로 진행했다.
    • 하나의 테이프부터 시작해 공유에 필요한 작업까지 마쳤더라면 좋았을 것이다. 그렇게 했다면 초기 과정에서의 결정이 최종 결과에 어떤 영향을 미치는지 더 일찍 알 수 있었을 것이다.
  • 트랜스코딩을 최소화하라.
    • 클립을 편집하거나 재인코딩할 때마다 화질이 저하된다.
    • 원본 영상을 가능한 한 최고 품질로 캡처한 뒤, 각 클립을 브라우저에서 네이티브로 재생할 수 있는 형식으로 정확히 한 번만 트랜스코딩하라.
  • 비디오 클립을 공유할 때는 가능한 한 가장 단순한 해결책을 사용하라.
    • 돌이켜보면 MediaGoblin은 변하지 않는 비디오 파일 집합을 보여주기 위한 웹 페이지를 생성하는, 그리 복잡하지 않은 시나리오에는 너무 복잡한 도구였다.
    • 다시 시작한다면 Hugo, Jekyll 또는 Gridsome 같은 정적 사이트 생성기를 사용했을 것이다.
  • 몽타주를 만들어라.
    • 비디오 몽타주는 여러 홈 비디오에서 최고의 순간들을 한데 모으는 재미있는 방법이다.
    • 몽타주에서 가장 중요한 것은 음악이다. “Slow Show” by The National은 몽타주에 환상적인 곡인데, 아직 아무도 그 사실을 깨닫지 못한 것 같다.

내 작업 과정 전체 워크스루

이 작업을 어떻게 했는지 세부적인 내용이 궁금하다면, 전체 워크플로를 처음부터 끝까지 보여주는 튜토리얼을 만들었다. 내 과정을 재현하는 데 필요한 모든 소스 코드와 명령어가 포함되어 있다.


일러스트: Loraine Yow

이 클립과 스틸 일부를 공유하도록 허락해 주고, 애초에 모든 것을 기록해 주었으며, 이 과정 내내 큰 힘이 되어 준 가족에게 특별히 감사한다.

이 글은 muse-spark-1.2-contributor 모델을 사용해 번역했습니다.

댓글