- 텍스트(측점/POI) 전 프레임 사전 계산 Map (requestIdleCallback 백그라운드) - 드론 데이터 이동 평균 스무딩 (smoothFrame ±N프레임) - 30fps→60fps 프레임 간 선형 보간 (performance.now() 기반) - EMA(지수이동평균) 표시 위치 스무딩 (α=0.01 기본값) - 글씨 2배 크기, bold, strokeText 테두리, 배경 박스 제거 - 카메라 파라미터 패널에 smooth/EMA α 슬라이더 추가 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.6 KiB
4.6 KiB
VERIFICATION.md — abcvideo 검증 결과
전체 7단계 구현 완료 후 진행한 검증 결과입니다. 검증 일자: 2026-03-24
검증 결과 요약
| 범주 | 통과 | 실패 | 상태 |
|---|---|---|---|
| V1 정적 검증 (빌드/타입) | 4/4 | 0 | ✅ |
| V2 서버 API 검증 | 13/14 | 1(문서 불일치) | ✅ |
| V3 코드 품질 검증 | 9/10 → 10/10 | 0 (수정 완료) | ✅ |
| V4 파일 구조 검증 | 19/19 | 0 | ✅ |
전체: 모든 항목 통과 (1건 코드 수정으로 해결)
V1. 정적 검증
| 항목 | 상태 | 비고 |
|---|---|---|
V1-1: server tsc --noEmit |
✅ PASS | 에러 없음 |
V1-2: client tsc --noEmit |
✅ PASS | 에러 없음 (RAF 수정 후 재확인 완료) |
V1-3: client npm run build |
✅ PASS | 1,480 kB / gzip 453 kB (Video.js 특성상 정상) |
| V1-4: 필수 파일 19개 존재 | ✅ PASS | 모든 파일 확인 |
V2. 서버 API 검증
| 항목 | 상태 | 실제 응답 |
|---|---|---|
V2-1: GET /api/health |
✅ PASS | {"status":"ok","timestamp":"..."} |
V2-2: GET /api/videos |
✅ PASS | [] (빈 목록) |
V2-3: GET /api/stream/nonexistent |
✅ PASS | HTTP 404 |
V2-4: POST /api/upload/init |
✅ PASS | {"uploadId":"..."} (필드명: totalChunks) |
V2-5: GET /api/annotations/test-video |
✅ PASS | [] |
V2-6: POST /api/annotations/test-video |
✅ PASS | UUID id 포함 주석 객체 반환 |
V2-7: PUT /api/annotations/test-video/:id |
✅ PASS | 수정된 text 반영 |
V2-8: DELETE /api/annotations/test-video/:id |
✅ PASS | {"success":true} |
| V2-9: export VTT | ✅ PASS | WEBVTT\n\n1\n00:00:10.500 --> 00:00:15.000\n... |
| V2-10: export SRT | ✅ PASS | 1\n00:00:10,500 --> 00:00:15,000\n... (콤마 정확) |
| V2-11: export JSON | ✅ PASS | JSON 배열 |
| V2-12: export CSV | ✅ PASS | 헤더 + 데이터 행 |
| V2-13a: Path traversal (URL인코딩) | ✅ PASS | HTTP 400 |
| V2-13b: Path traversal (평문) | ✅ PASS | HTTP 404 (Express 라우터 정규화) |
참고: V2-4 테스트 스펙에 fileSize 대신 totalChunks 사용해야 함 — API 동작 정상, 문서 불일치만 존재 (tus → 자체 구현 전환 시 발생).
V3. 코드 품질 검증
| 항목 | 상태 | 코드 위치 |
|---|---|---|
V3-1: backBufferLength: 30 |
✅ PASS | useVideoPlayer.ts:11 |
V3-2: requestVideoFrameCallback FPS 감지 |
✅ PASS | useFrameStep.ts |
V3-3: requestAnimationFrame 오버레이 동기화 |
✅ PASS (수정) | App.tsx RAF 루프 추가 |
| V3-4: 단일 Canvas 재사용 | ✅ PASS | frameCapture.ts:1 (모듈 싱글턴) |
| V3-5: Path traversal guard | ✅ PASS | security.ts path.resolve() + startsWith() |
V3-6: FFmpeg -accurate_seek 순서 |
✅ PASS | frame.ts -accurate_seek -ss -i 순서 |
V3-7: VTT . / SRT , 구분자 |
✅ PASS | annotations.ts sep 파라미터 |
| V3-8: 키보드 단축키 완전 구현 | ✅ PASS | useKeyboard.ts |
| V3-9: ErrorBoundary App 래핑 | ✅ PASS | main.tsx |
| V3-10: HLS 6초 세그먼트 + 키프레임 | ✅ PASS | hls.ts + config.ts |
수정 내역 (V3-3):
- 문제:
MemoOverlay가 Zustand 스토어의currentTime(timeupdate 기반)을 prop으로 받아 표시 - 수정:
VideoPlayerHandle에getVideoElement()추가,App.tsx에 RAF 루프 추가 →memoTimestate를 60fps로 업데이트 - 효과: timeupdate의 ~4fps 제한 → RAF 60fps 정밀 업데이트
V4. 파일 구조 검증
| 항목 | 상태 |
|---|---|
| server/src/app.ts | ✅ |
| server/src/routes/{stream,hls,upload,annotations,frame,meta}.ts | ✅ |
| server/src/services/{ffmpeg,streaming,storage}.ts | ✅ |
| server/src/middleware/security.ts | ✅ |
| client/src/hooks/{useVideoPlayer,useFrameStep,useKeyboard,useAnnotations}.ts | ✅ |
| client/src/components/player/VideoPlayer.tsx | ✅ |
| client/src/components/overlay/MemoOverlay.tsx | ✅ |
| client/src/components/{ErrorBoundary,HelpOverlay}.tsx | ✅ |
| README.md | ✅ |
알려진 제약 (검증 범위 외)
- FFmpeg 미설치: HLS 변환(
POST /api/hls/:videoId/convert), 프레임 추출(GET /api/frame/:videoId)은 FFmpeg 설치 후 동작 - 브라우저 UI 수동 테스트 항목: 로컬 파일 드래그앤드롭 재생, HLS 전환 재생, 전체화면, 메모 드래그, Shift+S 캡처
- 번들 크기: 1,480 kB (Video.js 특성, 코드 스플리팅으로 개선 가능)
- 장면 이동
[/]: 실제 키프레임 탐지 대신 ±30초 이동으로 구현됨 (FFmpeg 연동 시 개선 가능)