홈시리즈멘토링

© 2026 정기창. All rights reserved.

본 블로그의 콘텐츠는 CC BY-NC-SA 4.0 라이선스를 따릅니다.

☕후원하기소개JSON Formatter러닝 대기질개인정보처리방침이용약관

© 2026 정기창. All rights reserved.

콘텐츠: CC BY-NC-SA 4.0

☕후원하기
소개|JSON Formatter|러닝 대기질|개인정보처리방침|이용약관

Vrew 내보내기가 0%에서 멈출 때 — 로그가 알려준 진짜 원인

정기창·2026년 5월 23일

윈도우 PC에서 영상 편집 프로그램 Vrew로 작업을 하다가, Vrew 내보내기가 멈추는 일을 겪었습니다. 다른 프로젝트는 멀쩡한데 유독 한 프로젝트만 진행률 0%에서 더 나아가지 않고, 한참을 기다리면 결국 취소되어 버렸습니다. 처음에는 원인을 투명 GIF라고 확신했는데, 로그를 끝까지 따라가 보니 그 가설이 틀렸다는 것을 알게 되었습니다. 이 글은 문제를 고친 기록이라기보다, 그럴듯했던 첫 가설을 데이터가 바로잡아 준 과정의 기록입니다.

증상: 특정 프로젝트만 0%에서 멈춘다

증상은 단순했습니다. 영상을 내보내려고 하면 진행률이 0%에 멈춰 있다가, 시간이 지나면 내보내기가 취소되었습니다. 모든 프로젝트가 그런 것은 아니었습니다. 짧고 단순한 내보내기는 잘 됐고, 다른 프로젝트들도 문제가 없었습니다. 유독 한 프로젝트만 이런 증상을 보였습니다.

특정 프로젝트만 실패한다는 것은 다행스러운 단서였습니다. 프로그램 자체나 PC 환경의 문제라기보다, 그 프로젝트 안에 들어 있는 무언가가 원인이라는 뜻이기 때문입니다. 다만 그 '무언가'가 무엇인지는 화면만 봐서는 알 수 없었습니다. 추측을 시작하기 전에, 먼저 로그를 찾기로 했습니다.

Vrew 로그 파일을 찾다

Vrew는 세션마다 로그 파일을 남깁니다. 위치는 윈도우의 임시 폴더 아래입니다.

%LOCALAPPDATA%\Temp\vrew-<날짜>_<시각>_*.log

이 로그는 생각보다 자세했습니다. 내보내기를 시작한 시점, 디코더와 인코더가 어떻게 동작했는지, 내부적으로 ffmpeg를 어떻게 호출했는지, 그리고 최종 결과가 Export SUCCESS였는지 Export CANCELLED였는지까지 전부 기록되어 있었습니다. 왜 멈췄는지를 추적할 단서가 이미 로그 안에 다 있었던 셈입니다.

로그가 보여준 내보내기 실패 흐름

내보내기가 멈춘 세션의 로그를 열어 보니, 실패는 매번 비슷한 순서로 진행되고 있었습니다.

Starting Export
WebCodecsVideoRenderer  | Decoder Error, try to reconfigure with SW acceleration: Decoding error.
                        : DOMException EncodingError(0): Decoding error. Error in EOF
                        | force software acceleration
WebCodecsEncoder        | Encoder stuck, attempt flushing total 0 frames
WebCodecsVideoRenderer  | Decoder decode not finished after 5 seconds
                        | Decoder Error ... : Decoder decode timeout
                        : {"__internal":"MediaProcessingError","type":"E07"}
Export CANCELLED

흐름을 정리하면 이렇습니다. 어떤 소스 클립을 디코더가 풀지 못했고, 소프트웨어 가속으로 폴백해서 다시 시도했지만 그것도 타임아웃이 났습니다. 받을 프레임이 들어오지 않으니 인코더는 0프레임에서 멈췄고, 결국 내보내기 전체가 취소되었습니다.

인상적이었던 것은 이것이 깔끔하게 에러를 띄우고 종료되는 형태가 아니라는 점이었습니다. 파이프라인이 그냥 그 자리에 멈춰 서 버렸습니다. 그래서 사용자 입장에서는 진행률 0%라는, 정보가 거의 없는 화면만 보게 되는 것이었습니다.

첫 가설: 투명 GIF가 범인이다

로그를 더 따라가 보니, 디코더가 풀지 못한 소스 클립은 채널 로고로 쓰인 투명 GIF였습니다. 여기서 한 가지 배경을 알아 둘 필요가 있습니다. Vrew는 GIF를 직접 다루지 못하기 때문에, GIF를 넣으면 내부적으로 WEBM(VP9 코덱)으로 자동 변환합니다. 그리고 GIF의 투명한 부분을 살리기 위해 변환 결과물에 알파 채널이 포함됩니다. 로그에도 변환된 파일이 alpha_mode: 1로 찍혀 있었습니다.

그래서 처음에는 이렇게 추론했습니다. "VP9에 알파 채널이 붙은 조합을 디코더가 못 푸는 것이다. 즉 투명도가 원인이다." 이 가설은 꽤 그럴듯했습니다. 알파 채널이 들어간 비디오는 실제로 다루기 까다로운 영역이고, 변환된 그 파일을 Vrew 자체 도구조차 제대로 읽지 못하는 로그도 함께 보였기 때문입니다. 정황이 한 방향을 가리키고 있었으니, 거기서 멈추고 싶은 유혹이 컸습니다.

반증: 성공 이력을 보니 투명 GIF도 잘 됐다

여기서 하마터면 결정적인 실수를 할 뻔했습니다. 저는 '안 되는 사례'만 들여다보고 있었습니다. 실패한 로그만 반복해서 읽으면, 가설은 그 안에서 보이는 것들로만 만들어집니다. 투명 GIF가 거기 있으니 투명 GIF가 범인처럼 보였던 것입니다.

그래서 방향을 바꿔, 과거에 성공한 내보내기 로그를 거슬러 올라가며 확인해 보았습니다. 결과는 가설을 무너뜨렸습니다. 투명 GIF가 들어간 내보내기가 과거에 여러 번 정상적으로 성공해 있었고, 그 성공 사례들의 변환 결과물도 전부 alpha_mode: 1, 즉 똑같이 알파 채널을 가지고 있었습니다.

투명도가 원인이라면 그 성공들은 설명되지 않습니다. 가설이 틀렸다는 것을 인정할 수밖에 없었습니다. 부끄럽지만, 그럴듯한 첫 가설에 갇혀 있었던 셈입니다.

진짜 차이는 길이였다

가설을 버리고 나니 질문이 분명해졌습니다. 성공한 GIF와 실패한 GIF는 무엇이 달랐을까. 두 쪽의 변환 결과물(webm)의 속성을 나란히 놓고 비교해 보았습니다.

구분 크기 (예시) 길이 결과
성공한 GIF들 768×768, 1280×720, 1920×1080 등 1.2초 ~ 6.5초 성공
문제의 로고 GIF 1249×357 0.1초 실패

차이는 길이에서 드러났습니다. 성공한 GIF는 전부 1초가 넘는, 실제로 움직이는 애니메이션이었습니다. 반면 실패한 로고 GIF만 길이가 0.1초였습니다. 확인해 보니 그 GIF는 프레임이 하나뿐이었습니다. 움직이지 않는 정지 이미지를 GIF 형식으로 저장해 둔 것이었습니다.

이렇게 보니 로그의 에러 메시지도 다시 읽혔습니다. 앞서 본 Error in EOF는 스트림의 끝(End Of File)에 도달했다는 뜻입니다. 디코더가 0.1초짜리 소스를 풀려고 시도하자마자 거의 즉시 스트림의 끝에 닿아 버리고, 인코더는 받을 프레임이 없어 0프레임에서 멈춥니다. 로그가 처음부터 길이를 가리키고 있었는데, 제가 투명도라는 가설을 먼저 세워 두고 그 렌즈로만 로그를 읽었던 것입니다.

해결: 1초가 넘는 GIF로 다시 만들기

원인을 알고 나니 해결은 단순했습니다. 사실 정지 이미지라면 GIF가 아니라 PNG로 넣는 것이 정석입니다. PNG는 비디오 디코더를 거치지 않으니, 디코더가 멈출 일 자체가 없습니다.

다만 이번 작업에는 해당 로고를 GIF 형식으로만 넣어야 하는 제약이 있었습니다. 그래서 PNG로 바꾸는 대신, 같은 로고를 1초가 넘는 길이의 GIF로 다시 만들어 넣었습니다. 내용은 그대로 정지된 로고이지만, 형식상으로는 1초 넘게 이어지는 GIF가 된 것입니다. 그렇게 교체한 뒤 내보내기를 다시 돌리니, 멈추지 않고 끝까지 진행되어 정상적으로 완료되었습니다. 혹시 같은 GIF 렌더링 오류로 내보내기가 0%에서 멈춰 있다면, 타임라인에 넣은 GIF가 지나치게 짧지는 않은지 — 정지 이미지를 짧은 GIF로 저장한 것은 아닌지 — 가장 먼저 확인해 보시길 권합니다.

남는 불확실성: 치수 변수는 분리하지 못했다

여기서 솔직하게 적어 둘 것이 있습니다. "0.1초라는 짧은 길이가 원인이었다"는 결론은 강한 정황 증거이고, 길이를 늘린 해결책이 실제로 통했다는 점이 그것을 뒷받침합니다. 그럼에도 이것을 100% 단정하지는 않으려 합니다.

실패한 로고 GIF는 길이만 짧았던 것이 아니라, 치수도 1249×357이라는 홀수 값을 가지고 있었습니다. 반면 성공한 사례들은 대부분 짝수 치수였습니다. 비디오 코덱에 따라 홀수 치수가 문제를 일으키는 경우도 있기 때문에, 이 변수를 길이와 완전히 분리해 검증하지는 못했습니다. 길이만 늘린 GIF가 우연히 짝수 치수가 되었다면, 통한 이유가 길이 때문인지 치수 때문인지 단정하기 어렵습니다.

그래서 이 글에서는 "증거가 가장 강하게 가리킨 원인은 길이였고, 길이를 늘리는 해결책이 실제로 통했다"까지만 말하려 합니다. 모르는 것을 모른다고 적어 두는 편이, 매끄럽지만 검증되지 않은 단정보다 낫다는 생각이 들었습니다.

닫는 생각: 되는 사례를 같이 봐야 한다

이번 일에서 가장 오래 남은 것은 해결책 자체가 아니라, 가설이 어떻게 교정되었는가 하는 과정이었습니다. 돌이켜 생각해보면 몇 가지가 정리됩니다.

첫째, '안 되는 사례'만 노려보면 가설은 그 안에 갇힙니다. 실패한 로그에 투명 GIF가 보였기 때문에 투명 GIF가 범인처럼 보였습니다. '되는 사례'를 나란히 놓고 비교하고 나서야 진짜 변수인 길이가 드러났습니다. 무엇이 문제인지 찾을 때는, 무엇이 정상인지도 같은 무게로 봐야 합니다.

둘째, 로그는 대개 이미 답을 갖고 있습니다. Error in EOF는 처음부터 거기 있었습니다. 추측을 시작하기 전에 로그를 먼저, 그리고 끝까지 읽는 것만으로도 많은 길을 줄일 수 있었습니다.

셋째, 가설은 데이터로 교정하는 것입니다. 그럴듯한 첫 가설일수록 오히려 더 위험합니다. 그럴듯하기 때문에 반증을 찾는 손길이 느슨해지기 때문입니다. 가설이 매끈하게 느껴질 때일수록, 그것을 무너뜨릴 사례를 의식적으로 찾아봐야 한다는 것을 다시 배웠습니다.

Vrew영상 편집디버깅로그 분석가설 검증GIF 렌더링

관련 글

배포 실패 로그를 추적하다 크립토마이너를 발견한 이야기

Coolify에서 NestJS 배포가 타임아웃으로 실패했습니다. 서버 리소스를 확인하려고 docker stats를 열었는데, Next.js 프론트엔드 컨테이너가 CPU 123%, 메모리 2.41GB를 점유하고 있었습니다. 컨테이너 안에서 발견한 정체불명의 바이너리와 마이닝 풀 연결까지, 배포 실패에서 크립토마이너를 발견하기까지의 추적 기록입니다.

관련도 85%

Coolify HEALTHCHECK 버그를 발견하고 오픈소스 PR을 올리기까지

NestJS Worker 배포 중 Coolify의 HEALTHCHECK 감지 버그를 발견했습니다. Dockerfile 전체에서 문자열을 검색하는 로직이 multi-stage target을 고려하지 않는 문제였고, 소스 분석부터 PR 제출까지의 과정을 정리했습니다.

관련도 85%

영상에서 텍스트를 뽑고 싶었다 — ffmpeg, whisper-cpp, VAD로 로컬 STT 파이프라인 만들기

영상 파일에서 텍스트를 추출하는 로컬 STT 파이프라인을 구축한 경험입니다. ffmpeg가 오디오를 왜 16kHz 모노 PCM으로 변환하는지, whisper-cpp가 Python 없이 어떻게 동작하는지, VAD가 STT 품질을 어떻게 개선하는지 하나씩 따라가봅니다.

관련도 85%