Skip to content

fix(video): 대표 프레임을 변환보다 먼저 뽑고, 붙은 걸 목록이 알게 한다 - #98

Merged
eigger merged 1 commit into
masterfrom
fix/video-poster-reliability
Sep 6, 2026
Merged

fix(video): 대표 프레임을 변환보다 먼저 뽑고, 붙은 걸 목록이 알게 한다#98
eigger merged 1 commit into
masterfrom
fix/video-poster-reliability

Conversation

@eigger

@eigger eigger commented Sep 6, 2026

Copy link
Copy Markdown
Owner

동영상 썸네일이 파일에 따라 되기도 하고 안 되기도 하던 원인이 서버·클라이언트에 하나씩 있었다.

서버 — 포스터가 변환 뒤에 있었다 (R122)

transcodeClaimedAttachment가 720p 변환을 끝낸 다음에야 프레임을 뽑았다. 변환이 타임아웃(8×실시간, 상한 15분)이나 OOM으로 죽으면 processNext의 catch가 failed로만 표시하고 포스터는 시도조차 하지 않았다. 작은 영상은 shouldSkipVideoTranscode로 일찍 빠져 포스터가 붙으므로 큰 영상만 썸네일이 없는 패턴이 된다.

프레임 한 장 추출은 수백 ms, 재인코딩은 분 단위다. 추출을 변환 앞으로 옮기고 별도 updateMany({ where: { posterPath: null } })로 즉시 붙인다 — 변환이 어떻게 끝나든 포스터는 남고, 큰 영상은 몇 분 일찍 썸네일이 생긴다.

서버 — 한 번 실패한 포스터는 영영 다시 안 뽑았다

상태가 종단(ready/skipped/failed)으로 가면 되돌아올 경로가 없어 그 영상은 영구히 썸네일이 없었다. backfillMissingPosters()를 기동 시 한 번, 최대 50건 돈다. 주기 실행은 하지 않는다 — 진짜로 못 읽는 파일이면 매 tick마다 ffmpeg를 20초씩 태우게 된다. 이미 그렇게 남은 기존 영상들이 이걸로 메워진다.

클라이언트 — 포스터가 붙은 걸 화면이 몰랐다 (R123)

업로드 응답에는 posterPath: null이 담기고(포스터는 변환 잡이 뒤에서 만든다) 목록은 그걸 그대로 들고 있다. 폴링도 재조회도 없어 새로고침 전까지 자리표시자(pending/processing) 또는 맨 <video>(종단 상태 + 포스터 없음)로 남았다. 안드로이드 크롬은 preload="metadata" <video>를 대개 검게 칠하므로 "썸네일이 안 나온다"로 보인다.

useVideoPosterRefresh가 대기 중인 첨부의 상태만 되묻어 제자리에 덮는다. 목록 전체 재조회(loadEvents)는 setEvents(page)로 배열을 갈아끼워 업로드 중 낙관적 첨부를 지우므로 쓰지 않는다. 2s→30s 백오프, 8회로 끝, 화면이 가려지면 쉰다.

새 라우트

GET /api/attachments/posters?ids= — 인증 필수, 가구 스코프(K-1), 읽기 전용(K-7), id 상한 60. posterPath·transcodeStatus만 돌려준다.

검증

  • npm test — shared 65, api 233, web 245 통과 (포스터 순서·중복 방지·백필 3건, 클라 헬퍼 6건 추가)
  • tsc --noEmit 통과 / npm run lint 기존 경고 2건 외 없음
  • DB 마이그레이션 없음

R119·R120은 master 세션 로그가 이미 참조하고 있어 새 항목은 R122부터 매겼다.

🤖 Generated with Claude Code

동영상 썸네일이 파일에 따라 되기도 하고 안 되기도 하던 원인이 양쪽에 하나씩 있었다.

서버: 포스터를 720p 변환이 끝난 뒤에 뽑고 있었다. 변환이 타임아웃(8×실시간,
상한 15분)이나 OOM으로 죽으면 processNext의 catch가 failed로만 표시하고 포스터는
시도조차 안 했다. 작은 영상은 shouldSkipVideoTranscode로 일찍 빠져 포스터가 붙으니,
큰 영상만 썸네일이 없는 패턴이 된다. 프레임 한 장은 수백 ms고 변환은 분 단위다 —
추출을 앞으로 옮기고 별도 updateMany로 즉시 붙인다 (R120).

한 번 실패한 포스터는 되돌아올 경로가 없어 영구히 비어 있었다. 기동 시 한 번,
최대 50건 메우는 backfillMissingPosters()를 둔다. 주기 실행은 하지 않는다.

클라이언트: 업로드 응답에는 posterPath가 null이고, 목록은 포스터가 뒤늦게 붙은 걸
알 방법이 없었다. 새로고침 전까지 자리표시자나 맨 <video>로 남는데, 안드로이드
크롬은 preload="metadata" <video>를 대개 검게 칠한다. useVideoPosterRefresh가
해당 첨부의 상태만 되묻어 제자리에 덮는다 — 목록 전체 재조회는 업로드 중 낙관적
첨부를 지우므로 쓰지 않는다 (R121). 2s→30s 백오프, 8회로 끝, 가려지면 쉰다.

새 라우트 GET /api/attachments/posters — 인증·가구 스코프·읽기 전용, id 상한 60.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@eigger
eigger force-pushed the fix/video-poster-reliability branch from d9226d6 to c11cd9a Compare September 6, 2026 06:05
@eigger
eigger merged commit a15f629 into master Sep 6, 2026
6 checks passed
@eigger
eigger deleted the fix/video-poster-reliability branch September 6, 2026 06:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant