Index
2026-08-25 — Engineering

리포트 비교 영상 시킹(스크러버) 버그 수정

cosmos | WM finetune 리포트 인프라 · 원인: moov atom 위치 + webui 서버 HTTP Range 미지원

TL;DR

166
재먹싱 영상
109
preload 수정
2
중첩 원인

1 배경 / 목적

WM 실패 물리 finetune 프로젝트(H4 대조 실험 등)의 리포트에는 GT · base · FT-succ · FT-mix 4분할 비교 영상이 다수 임베드되어 있다. 사용자가 특정 구간(사건 전후)만 골라 보려고 재생 바를 드래그했으나 이동이 전혀 되지 않는다고 보고했다 — 리포트가 단순 텍스트/표가 아니라 영상 검토가 핵심인 프로젝트라 이 기능이 막히면 리뷰 자체가 어려워진다.

기존 한계: 영상은 재생/일시정지만 되고 임의 구간 탐색이 불가 — 사건 전후 비교처럼 짧은 구간을 반복 확인해야 하는 워크플로우와 상충.

2 작업 내용

1차 진단 — moov atom 위치 (08-25 04:30)

imageio_ffmpeg.write_frames의 기본 출력은 MP4 컨테이너의 메타데이터 블록(moov atom)을 파일 에 쓴다 (예: 파일 547,029 B 중 544,707 B 지점). 브라우저는 moov를 읽어야 시킹 인덱스를 만들 수 있는데, 파일 끝에 있으면 전체 다운로드 전엔 이동이 불가능하다. 여기에 HTML의 preload="none"이 겹쳐 duration조차 읽지 않은 상태였다. 서버(FastAPI StaticFiles)가 Range를 지원한다고 가정하고 이 시점에는 파일 문제로만 판단.

조치 1: 리포트 영상 166개 전부 -c copy -movflags +faststart 재먹싱 (무손실, moov → offset 36) 스크립트: tmp/wm_ft/faststart_all.sh 조치 2: HTML의 preload="none" → preload="metadata" 109곳 치환 조치 3: scripts/wm_compare_video.py에 output_params=["-movflags","+faststart"] 추가 → 이후 신규 렌더도 자동으로 faststart 적용 (moov@36 확인)

2차 진단 — 진짜 원인: 서버 HTTP Range 미지원 (08-25 06:00)

faststart 재먹싱만으로는 해결되지 않았다. curl로 직접 확인한 결과 Range: bytes=1000-2000 요청에도 서버가 200 + 전체 파일을 반환했다(accept-ranges 헤더 자체가 없음). 원인은 ~/repos/cowork webui가 쓰는 starlette 0.38.6StaticFiles의 HTTP Range 지원은 0.45부터 추가됐다. 즉 파일을 아무리 고쳐도 서버가 Range 요청을 처리 못 하면 브라우저는 시킹 인덱스를 만들 수 없다.

파일: ~/repos/cowork/webui/server.py 변경: RangeStaticFiles(StaticFiles) 커스텀 클래스 추가 - Range 헤더 있으면 → 206 + content-range/accept-ranges로 슬라이스 스트리밍 - Range 헤더 없으면 → 기존 경로 그대로 + accept-ranges: bytes 헤더 추가 - 멀티레인지·비정상 헤더 → 전체 파일로 폴백 - 범위 초과(예: bytes=99999999-) → 416 적용: /reports 마운트만 이 클래스로 교체 (다른 라우트·의존성 변경 없음) 재시작: scripts/bootstrap_services.sh

starlette를 업그레이드하지 않고 커스텀 클래스로 우회한 이유: 0.45로 올리면 FastAPI/다른 미들웨어와의 호환성 확인이 추가로 필요해 범위가 커진다 — /reports 마운트 하나만 좁게 고치는 쪽이 리스크가 작다고 판단(사용자 승인).

3 결과

Range 요청기대 응답실측비고
bytes=1000-2000206 + content-range206, 1000-2000/547029슬라이스 스트리밍 정상
bytes=-500206, 파일 끝 500B206, 546529-547028suffix range 정상
bytes=500000-206, 나머지 전체206, 500000-547028open-ended range 정상
Range 없음200 + accept-ranges200, accept-ranges: bytes기존 경로 유지
HTML/인덱스 페이지200 정상200비디오 외 경로 영향 없음
검증: 위 5개 케이스 전부 기대대로 응답. 다른 리포트(H4 외)의 영상에서도 시킹 정상 동작 확인.
1차 조치(faststart 재먹싱)만으로는 미해결이었던 것이 핵심 — 파일 쪽 원인(moov 위치)을 고쳐도 서버가 Range를 못 받으면 브라우저가 여전히 전체 파일을 받아야 시킹 인덱스가 생긴다. 두 원인이 중첩되어 있었다.

4 Takeaway

의미

이 프로젝트(WM finetune)는 결과 판정이 수치(LPIPS/beats_copy)뿐 아니라 영상 육안 비교에 크게 의존한다 (H4 카드에서만 비교 영상 31개, 지금까지 리포트 전체 166개). 시킹이 막히면 사건 전후 몇 초 구간을 반복 확인하는 리뷰 자체가 비효율적이 되므로, 겉보기엔 "리포트 UX" 문제지만 실질적으로는 실험 검증 워크플로우의 병목이었다.

증상 하나에 원인이 두 개 겹쳐 있을 수 있다는 점도 확인됐다 — 1차 조치(파일 재먹싱)가 "안 됐다"는 것 자체가 두 번째 원인(서버)을 찾아내는 단서가 됐다. curl -H "Range: ..."로 서버 응답을 직접 찍어본 것이 결정적이었다.

5 Next Steps

미해결 한계

starlette는 여전히 0.38.6 — 이번 수정은 /reports 마운트에 한정된 우회이며, webui의 다른 정적 파일 라우트나 향후 신규 마운트에는 같은 문제가 재발할 수 있다(각각 RangeStaticFiles를 적용해야 함).

다음 작업

webui 의존성 전체를 starlette 0.45+로 올리는 것을 별도 작업으로 검토(FastAPI 호환성 확인 필요) — 되면 RangeStaticFiles 커스텀 클래스를 제거하고 표준 StaticFiles로 되돌릴 수 있다. 당장은 신규 리포트 영상이 wm_compare_video.py를 통해 생성되는 한 faststart는 자동 적용되므로 추가 조치 불요.