1,377프레임 추출 후 60프레임만 남았지만, 그중 어느 것도 시간을 알지 못했다
요약
오픈 소스 비디오 도구에서 프레임 추출 및 희소화 과정 중 타임스탬프 정보가 손실되는 문제를 다룹니다. ffmpeg의 showinfo 필터를 활용하여 가변 프레임 레이트에서도 정확한 PTS(Presentation Time Stamp)를 유지하는 해결 방법을 제시합니다.
핵심 포인트
- 프레임 추출 및 중복 제거 과정에서 타임스탬프 손실 발생
- 단순 산술 연산은 가변 프레임 레이트(VFR)에서 오차 유발
- ffmpeg의 showinfo 필터를 통해 실제 PTS 추출 가능
- 추출된 프레임과 타임스탬프를 JSON 형태로 매핑하여 관리
지난주 한 사용자가 제가 몇 달 동안 배포해 온 오픈 소스 비디오 도구(open-source video tool)에 이슈(issue)를 제기했는데, 이는 제가 간과하고 있던 문제를 정확히 짚어냈습니다.
그는 LLM(Large Language Model)이 강의 내용을 읽을 수 있도록 crv를 통해 강의를 처리합니다. 22분 분량의 강의 한 개를 처리했을 때: 1,377개의 후보 프레임(candidate frames)이 추출되었고, 중복 제거(dedup) 및 --max-frames를 통한 희소화(thinning) 과정을 거쳐 60개가 남았습니다. 이 60개의 프레임은 올바른 순서로 출력됩니다. 하지만 그게 전부입니다.
그의 불만 사항을 한 문장으로 요약하자면 이렇습니다: LLM은 슬라이드를 설명할 수는 있지만, 그 슬라이드가 화면에 언제 있었는지는 말할 수 없다는 것입니다.
이것은 들리는 것보다 훨씬 더 큰 문제를 일으킵니다:
- 타임스탬프(timestamp)와 함께 시각적 증거를 인용할 수 없습니다.
- 차트를 인근의
transcript.json세그먼트(segments)와 정렬할 수 없습니다. - 키프레임(keyframe)에서 비디오의 해당 시점으로 바로 이동할 수 없습니다.
- 나중에 해당 주장을 검증할 수 없습니다.
전사 데이터(transcript)에는 내내 타임스탬프가 있었습니다. 하지만 프레임에는 없었습니다.
타임스탬프가 사라진 이유
파이프라인은 다음과 같이 진행됩니다: ffmpeg로 추출하고, 거의 동일한 프레임을 버리고, --max-frames로 희소화한 뒤, 모든 파일 이름을 frame_001.jpg, frame_002.jpg로 변경합니다.
이 모든 단계는 위치 정보 측면에서 손실(lossy)을 발생시킵니다. 추출 단계에서 파일을 쓰고, 중복 제거 단계에서 일부를 삭제하며, 희소화 단계에서 더 많은 것을 삭제하고, 이름 변경 단계에서 그 간극을 메워버립니다. 여러분이 frame_012.jpg를 손에 쥐었을 때, 파일 이름에 남은 유일한 사실은 "생존한 12번째 프레임"이라는 점뿐이며, 무엇의 12번째인지에 대한 정보는 출력 디렉토리에서 더 이상 복구할 수 없습니다.
유혹적인 해결책은 산술 연산입니다: timestamp = frame_number / fps. 하지만 이는 가변 프레임 레이트(variable frame rate) 소스에서는 잘못된 방식이며, 대부분의 화면 녹화와 많은 휴대폰 영상이 이에 해당합니다. 이 방식은 그럴듯해 보이는 숫자를 내놓지만 시간이 지날수록 오차가 발생(drift)합니다.
실제로 작동하는 방법
ffmpeg는 이미 알고 있습니다. showinfo 필터를 사용하면 이미 실행 중인 동일한 select 패스(pass)에서 통과하는 모든 프레임의 실제 PTS(Presentation Time Stamp)를 출력합니다:
-vf "select=...,showinfo"
해당 로그를 파싱하면 별도의 2차 디코딩 패스 (decode pass) 없이도 실제 PTS(Presentation Time Stamp)를 얻을 수 있습니다. 그런 다음 이를 계속 유지합니다. 추출 시 PTS를 부착하고, 중복 제거 (dedup), 프레임 축소 (thinning), 이름 변경 (rename) 과정을 거치는 동안에도 이를 계속 유지한 뒤, 이미지 옆에 frames.json 파일로 기록합니다:
{
"frames": [
{
...
selection_reason은 어떤 중복 제거 채널이 해당 프레임을 유지했는지 기록합니다. 이 항목은 초기에 추가할 가치가 있습니다. 원하는 프레임이 누락되었을 때, 어느 단계에서 해당 프레임이 사라졌는지 확인하기 위해 읽어야 하는 정보이기 때문입니다.
내가 거의 건너뛸 뻔했던 부분
만약 showinfo 로그와 추출된 파일의 개수가 일치하지 않으면, 이 도구는 근사치를 작성하는 대신 타임스탬프를 아예 작성하지 않습니다.
도구를 작성할 당시에는 이것이 지나치게 엄격하다고 느꼈습니다. 하지만 실제로는 그 반대입니다. 타임스탬프가 누락되면 모델은 "언제인지 모르겠다"라고 말합니다. 하지만 잘못된 타임스탬프가 있으면 모델은 매우 확신에 차서 00:03:41이라고 인용할 것이며, 이후의 어떤 단계에서도 이를 잡아낼 수 없습니다. 모델에게 검증 가능한 증거를 전달하는 것이 전체 작업인 파이프라인에서, 그럴듯해 보이는 잘못된 숫자를 내보내는 것은 발생할 수 있는 최악의 상황입니다.
결과가 유지되었는가
이슈를 제기했던 사용자가 새로운 빌드에서 자신의 22분 12초짜리 강의를 다시 실행하여 직접 확인했습니다. 1,377개의 후보 프레임이 최종 60개 프레임으로 줄어들었고, 매핑에는 60개의 항목이 있었으며, 모두 단조 증가 (monotonic) 했고, 누락되거나 추가된 이미지 파일도 없었습니다. 그는 원본 소스를 대상으로 전체 추출 패스를 다시 재생하여 모든 최종 이미지를 기록된 타임스탬프와 대조했습니다. 60개 중 60개 모두 일치했습니다.
나는 그에게 그렇게 해달라고 요청하지 않았습니다. 이는 누군가가 이 프로젝트를 위해 해준 일 중 가장 유용한 일이었습니다.
교훈
LLM에 데이터를 공급하는 추출, 필터링 및 이름 변경 파이프라인을 구축한다면, 위치 정보(position)가 어디에 저장될지 초기에 결정하십시오. 식별자 (identifier)를 네 단계를 거쳐 전달하는 것이 나중에 디렉토리 목록을 보고 이를 재구성하는 것보다 훨씬 비용이 적게 듭니다. 그리고 식별자가 불확실할 때는, 무언가를 내보내기보다 아무것도 내보내지 마십시오.
crv는 MIT 라이선스이며 PyPI에서 사용할 수 있습니다:
pip install -U claude-real-video
Source: https://github.com/HUANGCHIHHUNGLeo/claude-real-video
프레임과 전사(transcript) 외에도 카메라 움직임(camera motion), 오디오 및 화자 레이블(speaker labels)이 필요한 경우 사용할 수 있는 유료 Pro 빌드도 있습니다: https://capafy.ai/agent/llm-real-video-pro-let-any-llm-watch-videos/5451082151?ct=devto
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기