라이브 녹화 파일이 재생 중간에 멈추는 문제 — 원인과 대응

Docker Compose로 운영 중인 자체 개발 라이브 스트림 녹화 도구(Python/FastAPI + yt-dlp 기반)에서 겪은 문제를 정리한다.

증상

한 채널의 녹화 파일을 열어보니 재생시간은 3시간에 가까운데 용량은 200MB 남짓으로 비정상적으로 작았다. 재생해보면 앞부분은 정상적으로 나오는데, 재생 위치를 중간으로 옮기면 그대로 멈췄다.

원인 추적

앱이 채널별로 남기는 실행 로그를 다시 확인해보니, 방송 도중 이런 에러가 있었다.

ERROR: Did not get any data blocks
ERROR: Did not get any data blocks

이 시점 전후로 비디오 트랙과 오디오 트랙의 다운로드 진행 상황을 대조해보니, 비디오 데이터는 방송 초반 일부만 받고 사실상 멈췄는데, 오디오 데이터는 끝까지 계속 정상적으로 들어오고 있었다.

이 도구는 화질 선택의 유연성을 위해 비디오와 오디오를 분리된 스트림으로 따로 받아서 나중에 합치는 방식을 쓰고 있었다. 그런데 한쪽 스트림(비디오)만 방송 서버 쪽 사정으로 일시적으로 끊기고 다른 쪽(오디오)은 계속 정상 공급되면, 전체 다운로드는 결국 “성공”으로 끝난다. 파일의 재생시간(길이) 메타데이터는 계속 들어온 오디오 트랙을 기준으로 정상적으로 잡히지만, 실제 비디오 프레임은 초반 이후로 존재하지 않는 “부분 손상” 파일이 만들어지는 것이다.

이건 다운로드 도구 입장에서 에러로 취급되지 않는다 — 재시도를 계속하다가 결국 오디오만이라도 끝까지 받으면 “완료”로 처리되기 때문에, 겉보기엔 정상 녹화와 구분이 안 된다. 근본 원인은 방송 송출 측의 일시적인 데이터 공급 문제로, 수신하는 쪽(녹화 도구)에서 근본적으로 예방하기는 불가능한 영역이다.

대응 1 — 예방 (해당 채널 한정)

이 채널이 실제로 어떤 화질 옵션을 제공하는지 직접 조회해보니, 비디오와 오디오가 이미 하나로 합쳐진 단일 스트림 형태로도 최고 화질(1080p60)을 제공하고 있었다.

그래서 이 채널에 한해, 화질 셀렉터를 아래처럼 바꿨다.

-f best/bestvideo+bestaudio
  • 우선 이미 합쳐진 단일 스트림(best) 중 최고 화질을 시도하고
  • 그런 옵션이 없을 때만 기존 방식(분리 스트림을 따로 받아 합치는 bestvideo+bestaudio)으로 대체

화질 손해 없이, 이 채널에서는 앞으로 “한쪽 트랙만 끊기는” 부분 손상 자체가 구조적으로 발생할 수 없게 됐다. 단일 스트림이므로 끊기면 그냥 그 시점에서 녹화가 끝날 뿐, 트랙끼리 서로 다른 길이로 어긋나는 일 자체가 없다.

대응 2 — 감지 (전체 채널 공통)

모든 채널에 이 예방법을 똑같이 적용할 수 있는 건 아니었다(채널에 따라 합쳐진 고화질 옵션이 없을 수도 있다). 그래서 예방이 안 되는 경우를 대비해, 녹화가 끝날 때마다 자동으로 검사하는 로직을 추가했다.

녹화 완료 직후 파일을 분석 도구(ffprobe)로 검사해서, 비디오 트랙과 오디오 트랙의 실제 재생 길이를 각각 구하고 비교한다.

gap = abs(durations["video"] - durations["audio"])
if gap >= 300:  # 5분 이상 차이나면 의심
    logger.warning(...)
    rec["av_mismatch"] = True

두 트랙 길이가 일정 기준(5분) 이상 차이 나거나, 애초에 한쪽 트랙이 아예 없으면 경고를 남긴다. 이렇게 하면 다른 채널에서 같은 문제가 생겨도, 사람이 직접 재생해보고 나서야 알아채는 게 아니라 로그에서 바로 확인할 수 있다.

대응 3 — 가시화 (전체 채널 공통)

로그만 남기면 나중에 컨테이너 로그를 뒤져야 하니, 실제로 문제를 더 쉽게 발견할 수 있도록 한 단계 더 추가했다. 문제가 감지되면 원본 영상 파일은 전혀 건드리지 않고(삭제도, 이동도 하지 않음), 같은 폴더에 경고 텍스트 파일을 자동으로 만든다.

recording_2026-09-07.mp4
recording_2026-09-07.mp4.WARNING.txt   ← 문제 감지 시 자동 생성

경고 파일에는 어떤 트랙이 얼마나 짧게 끝났는지, 대략 어느 지점부터 재생이 끊길지에 대한 안내를 담았다. 파일 탐색기로 폴더를 열어보기만 해도 문제 있는 녹화가 바로 눈에 띈다.

자동으로 삭제하거나 별도 폴더로 옮기는 방법도 고려했지만, 채택하지 않았다. 이런 부분 손상 파일도 방송 앞부분(비디오가 정상적으로 들어온 구간)까지는 여전히 정상적으로 재생 가능한 경우가 많아서, 일부라도 건질 여지가 있는 파일을 시스템이 판단만으로 지우거나 옮기는 건 위험하다고 판단했다. 표시만 해두고, 최종 판단(지울지 말지)은 사람이 직접 하도록 남겨뒀다.

복구 가능 여부

이런 파일은 구조적으로 복구가 불가능하다. moov atom(색인 정보)이 깨진 경우라면 remux로 되살릴 수 있지만, 이번 문제는 색인이 아니라 데이터 자체가 그 구간에 존재하지 않는 경우라서, 파일을 아무리 재정리해도 없는 프레임을 만들어낼 수는 없다. 유일한 대안은 원본 방송의 다시보기(VOD)가 남아있다면 거기서 다시 받는 것뿐이다.

배운 점

  • “다운로드 성공”과 “재생 가능한 완전한 파일”은 다른 개념이다. 비디오/오디오를 분리해서 받는 구조에서는, 한쪽만 조용히 끊겨도 전체 다운로드는 정상 종료로 처리될 수 있다. 이런 부분 손상은 별도로 검증하지 않으면 절대 드러나지 않는다.
  • 가능하면 합쳐진 단일 스트림을 우선 시도하는 게 안전하다. 트랙이 분리되어 있으면 “한쪽만 끊기는” 상황 자체가 생길 수 있지만, 하나의 스트림이면 끊길 때 그냥 거기서 끝나는 것이지 트랙끼리 어긋나는 일 자체가 없다. 화질 손해가 없다면 굳이 분리 방식을 고집할 이유가 없다.
  • 근본 원인을 못 없애는 문제는, “예방”이 안 되면 최소한 “빠르게 발견하기”로 대응한다. 방송 송출 측 문제는 수신 측에서 막을 수 없지만, 문제가 생겼을 때 사람이 재생해보고 나서야 아는 것과 녹화 직후 자동으로 알게 되는 것은 대응 속도에서 큰 차이가 난다.
  • 자동화에 “삭제”를 넣을 때는 신중해야 한다. 문제를 감지하는 로직과, 그 문제에 대해 무엇을 할지(삭제/이동/표시)는 별개의 결정이다. 특히 판단 기준이 확률적/경험적인 경우(이번의 “5분 이상 차이”처럼), 오탐 가능성을 고려해 되돌릴 수 없는 조치(삭제)보다는 되돌릴 수 있는 조치(표시)를 우선하는 게 안전하다.