[실전 미디어 09] 비디오 썸네일의 iOS 벽

지난 편에서 이미지 글의 대표 썸네일을 자동으로 뽑는 구조를 만들었다. 그런데 첫 미디어가 동영상인 글에서는 그 구조가 통째로 무너졌다. 이미지는 첫 이미지를 잘라 썸네일을 만들면 그만이지만, 동영상은 화면에 재생될 한 순간의 정지 장면, 즉 첫 프레임을 어딘가에서 뽑아내야 대표 이미지가 나온다. 그리고 이 첫 프레임 추출이 특정 환경에서 구조적으로 실패하면서, 나는 오랫동안 동영상 글의 썸네일을 손으로 만들어 붙이는 반복 노동에 시달렸다. 이번 편은 그 벽의 정체를 데이터로 확인한 기록이다.


1. 이미지는 됐는데 동영상은 안 됐다

이미지 썸네일이 자리를 잡은 뒤, 나는 동영상도 같은 방식으로 되리라 기대했다. 발상은 단순했다. 동영상의 한 시점을 화면에 그려서 그 장면을 이미지로 뽑으면, 그게 곧 동영상의 대표 썸네일이 된다. 이미지 썸네일을 만들 때 쓰던 캔버스 방식을 동영상에도 그대로 적용하면 될 것 같았다. 그런데 이게 유독 동영상에서만 자꾸 빈 이미지를 뱉어냈다.


처음에는 내 코드의 실수라고 생각했다. 동영상이 아직 다 로드되지 않아서, 혹은 프레임을 그릴 시점을 잘못 잡아서 빈 화면이 나온다고 짐작했다. 그래서 로딩이 끝나기를 기다리는 처리를 넣고, 프레임을 그릴 시점을 조정하고, 여러 방법을 시도했다. 하지만 어떤 환경에서는 무엇을 해도 계속 빈 이미지만 나왔다. 코드 문제라기엔 실패가 지나치게 일관적이었다. 진짜 코드 버그라면 조건에 따라 되기도 하고 안 되기도 하는 법인데, 이건 특정 조건에서 항상 실패했다. 그 규칙성 자체가 원인이 코드 바깥에 있다는 신호였다.


단서는 실패가 환경을 탄다는 데 있었다. 내 데스크톱에서는 동영상 첫 프레임이 멀쩡하게 뽑혔다. 그런데 실제 사용자 대다수가 쓰는 모바일 환경에서는 같은 코드가 빈 이미지를 냈다. 즉 이건 로직의 문제가 아니라 실행되는 환경, 특히 특정 모바일 브라우저의 동작 방식에 뿌리를 둔 문제였다. 코드를 아무리 다듬어도 환경이 원인이면 코드로는 못 고친다.


이 지점에서 나는 접근을 바꿨다. 코드를 계속 고치는 대신, 왜 이 환경에서 동영상 프레임 추출이 실패하는지 그 근본을 조사하기로 했다. 증상만 쫓다가는 끝없이 우회만 시도하게 되니, 원인을 알아야 제대로 된 해결이든 포기든 결정할 수 있었다. 조사 결과는 예상보다 훨씬 근본적이었다.


2. 클라이언트에서 첫 프레임을 그린다는 것

동영상 첫 프레임을 뽑는 원리부터 짚어야 한다. 브라우저에서 동영상을 화면에 올려 특정 시점으로 이동시킨 뒤, 그 순간의 화면을 캔버스에 그리면 정지 이미지가 된다. 그렇게 그린 캔버스를 이미지 파일로 내보내면 첫 프레임 썸네일이 완성된다. 이미지 썸네일과 개념적으로 같고, 중간에 동영상을 캔버스에 그리는 단계만 추가된 것이다.


이 방식이 성립하려면 동영상을 캔버스에 그리는 그 한 단계가 반드시 성공해야 한다. 화면에 보이지 않는 상태의 동영상이라도, 특정 시점의 프레임이 캔버스에 제대로 옮겨져야 한다. 그런데 바로 이 단계가 어떤 환경에서는 아무것도 그리지 않고 투명하거나 검은 빈 화면만 남겼다. 나머지 단계가 다 정상이어도 이 한 단계가 비면 결과는 빈 이미지였다.


문제는 이 단계가 실패해도 오류를 던지지 않는다는 것이었다. 캔버스에 그리는 명령은 성공한 것처럼 조용히 넘어가고, 내보낸 이미지도 형식상 멀쩡한 이미지 파일이었다. 앞선 편들에서 여러 번 만난 그 조용한 실패의 또 다른 형태였다. 결과물은 있는데 내용이 비어 있으니, 검증 없이는 실패를 알아챌 수 없었다.


그래서 이 방식은 근본적으로 취약했다. 프레임을 그리는 단계가 환경에 좌우되고, 실패해도 티가 나지 않으니, 어떤 사용자 기기에서는 되고 어떤 기기에서는 안 되는 예측 불가능한 결과가 나왔다. 특정 환경에서 왜 이 단계가 실패하는지가 핵심이었고, 그 답은 브라우저 엔진의 오래된 한계에 있었다.


3. 특정 환경의 구조적 한계

조사해보니 이건 내 사이트만의 문제가 아니라 널리 알려진, 그러나 오래 고쳐지지 않은 브라우저 엔진의 한계였다. 일부 모바일 브라우저는 하드웨어 동영상 디코더 구조상, 화면에 실제로 보이지 않는 동영상을 캔버스에 그리면 빈 프레임을 내놓는다. 화면에 직접 재생되는 동영상은 잘 나오지만, 썸네일을 만들려고 화면 밖에서 몰래 그리려 하면 비어버리는 것이다.


이건 오래전부터 보고된 엔진 차원의 미해결 문제였다. 관련 버그 보고가 여러 건 쌓여 있었고, 공식적으로 제시된 우회책조차 모든 프레임을 미리 이미지로 내보내라는 식의 번거로운 회피뿐이었다. 다시 말해 이 환경에서 동영상 첫 프레임을 안정적으로 뽑는 정석 방법은 존재하지 않았다. 클라이언트 코드로 풀 수 있는 문제가 아니었던 것이다.


왜 하필 이게 치명적이었냐면, 내 사이트 사용자 구성 때문이었다. 뒤에서 데이터로 보겠지만 사용자 대다수가 바로 이 한계를 가진 환경에서 접속했다. 즉 동영상을 올리는 사람 대부분이 첫 프레임 추출이 구조적으로 불가능한 환경에 있었다. 소수 예외가 아니라 다수가 실패하는 상황이었으니, 우회로 버틸 수 있는 규모가 아니었다.


게다가 서버에는 이걸 대신 처리할 백업 경로가 아예 없었다. 서버측 인코더는 정지 이미지를 다루는 도구여서 동영상 파일을 읽지 못했다. 그래서 클라이언트가 실패하면 그걸 받아줄 곳이 없어, 결국 사람이 손으로 첫 프레임을 뽑아 붙이는 수밖에 없었다. 자동화의 마지막 단계가 사람으로 떨어지는, 전형적인 편법 상태였다. 매번 손으로 처리하다 보면 언젠가는 빠뜨리게 되고, 그렇게 누락된 동영상 글은 목록에서 대표 이미지 없이 휑하게 떴다. 사람이 병목이자 단일 실패 지점이 되어 있었다.


4. 데이터로 확인하다

가설을 세웠으면 데이터로 확인해야 했다. 먼저 진단 로그를 뒤졌다. 최근 며칠간의 오류 기록을 보니 동영상 프레임 추출 실패가 꾸준히 반복해서 찍혀 있었다. 반면 첫 미디어가 이미지인 경우의 썸네일 생성은 정상 경로로 잘 돌고 있었다. 실패가 오직 동영상에만 몰려 있다는 게 로그로 분명히 드러났다.


다음은 사용자 환경 분포였다. 접속 통계를 보니 사용자의 대다수가 앞서 말한 한계를 가진 모바일 환경이었고, 데스크톱 비중은 아주 작았다. 내 데스크톱에서 잘 되니 괜찮다고 착각했던 게 얼마나 위험했는지 숫자로 확인된 셈이다. 개발자 한 명의 환경은 실제 사용자 분포와 전혀 달랐다.


혹시 다른 원인은 아닌지도 점검했다. 특히 동영상 파일이 다른 출처에 있어서 보안 정책 때문에 캔버스가 오염되어 실패하는 것 아니냐는 가설을 세웠다. 그런데 관련 헤더를 확인해보니 그쪽은 정상이었다. 보안 정책은 원인이 아니었고, 남는 건 앞서 말한 엔진의 구조적 한계뿐이었다. 하나씩 배제하니 원인이 명확해졌다.


이렇게 로그와 환경 분포와 배제된 가설을 종합하니 결론은 흔들리지 않았다. 대다수 사용자 환경에서 클라이언트 동영상 프레임 추출은 구조적으로 실패하며, 이건 코드로 고칠 수 없다. 데이터가 뒷받침하니 나는 확신을 갖고 다음 단계로 넘어갈 수 있었다. 추측이 아니라 측정이 방향을 정해줬다.


5. 클라이언트로는 못 고친다는 결론

결론은 분명했다. 클라이언트에서 동영상 첫 프레임을 뽑겠다는 접근 자체를 포기해야 했다. 코드를 아무리 정교하게 다듬어도 다수 사용자 환경의 엔진 한계는 넘을 수 없었고, 우회책은 실용적이지 않았다. 잘못된 접근을 붙잡고 개선하는 건 시간 낭비였다. 접근을 바꾸는 게 유일한 길이었다.


이 결론은 앞선 편들의 교훈과 같은 맥락이었다. 브라우저가 감당 못 하는 일은 서버로 넘긴다는 원칙 말이다. 긴 이미지를 서버 변환에 넘겼듯이, 동영상 프레임 추출도 브라우저 환경과 무관하게 동작하는 서버측 처리로 옮기면 됐다. 환경에 좌우되는 문제는 환경을 통제할 수 있는 곳으로 옮기는 게 정석이었다.


중요한 건 이걸 우회가 아니라 해당 없음으로 만드는 것이었다. 서버에서 동영상 프레임을 뽑으면 브라우저 엔진의 한계는 애초에 개입할 여지가 없다. 특정 환경에서 되고 안 되고를 따질 필요 자체가 사라진다. 문제를 교묘하게 피해 가는 게 아니라, 문제가 존재할 수 없는 자리로 작업을 옮기는 것이 진짜 근본 해결이었다.


그래서 다음 목표는 정해졌다. 서버에서, 브라우저와 무관하게, 동영상의 한 프레임을 이미지로 뽑아내는 방법을 찾는 것이다. 마침 내가 쓰는 스택에 그걸 정확히 해주는 기능이 있었다. 다음 편에서는 서버사이드 프레임 추출로 이 오랜 벽을 허물고, 손으로 썸네일을 붙이던 반복 노동을 영구히 없앤 이야기를 다룬다.