feat: StationOverlay 렌더링 최적화 및 스무딩 적용 close #1

- 텍스트(측점/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>
This commit is contained in:
minsung
2026-04-01 15:11:39 +09:00
commit 2aae3d1c0d
89 changed files with 15739 additions and 0 deletions

282
PLAN.md Normal file
View File

@@ -0,0 +1,282 @@
# PLAN.md — abcvideo 구현 계획
> 이 파일은 전체 구현 계획과 각 단계별 세부 태스크를 정의합니다.
> 에이전트는 작업 시작 전 반드시 이 파일을 읽고, 자신이 담당할 단계를 확인합니다.
---
## 단계 1: 프로젝트 구조 세팅
### 목표
모노레포 구조 생성, TypeScript/빌드 설정, 개발 환경 구축
### 세부 태스크
- [ ] 루트 `package.json` (npm workspaces: client, server, shared)
- [ ] `client/` — Vite + React 18 + TypeScript 초기화
- [ ] `server/` — TypeScript + ts-node/tsx 설정
- [ ] `shared/` — 공유 타입 패키지 설정
- [ ] Tailwind CSS 설치 및 설정 (client)
- [ ] ESLint + Prettier 설정 (루트)
- [ ] `tsconfig.json` — 루트/client/server/shared 각각 설정 (경로 별칭 포함)
- [ ] `storage/` 디렉토리 구조 생성 + `.gitignore` 설정
- [ ] `server/.env.example` 작성
- [ ] 개발 서버 동시 실행 스크립트 (`concurrently` 또는 npm scripts)
- [ ] 빈 앱 기동 확인 (client dev + server dev 동시 실행)
### 산출물 (완료 기준)
- `npm install` 성공
- `npm run dev`로 client(5173) + server(3001) 동시 기동
- TypeScript 컴파일 에러 없음
- 빈 React 앱이 브라우저에 표시됨
### 다음 단계 의존성
- 단계 2, 3 모두 이 단계 완료 후 시작 가능
---
## 단계 2: 백엔드 서버
### 목표
Express 서버 + Range Request 스트리밍 + FFmpeg spawn 래퍼 + tus 업로드 + HLS 변환 + SSE 진행률
### 세부 태스크
#### 2-1. 서버 기본 골격
- [ ] Express 앱 생성 (`server/src/app.ts`)
- [ ] CORS 미들웨어 설정 (개발: localhost:5173 허용)
- [ ] 보안 미들웨어: Path traversal 방어 (`middleware/security.ts`)
- [ ] 에러 핸들링 미들웨어
- [ ] 환경변수 로드 (dotenv + `server/.env`)
- [ ] `storage/` 하위 디렉토리 자동 생성 로직
#### 2-2. Range Request 스트리밍
- [ ] `routes/stream.ts` — GET /api/stream/:videoId
- [ ] `services/streaming.ts` — Range 파싱, createReadStream, 206 응답
- [ ] Accept-Ranges 헤더, Content-Range 헤더 정확한 구현
- [ ] highWaterMark 1MB, 청크 크기 10MB 제한
- [ ] 테스트: curl로 Range Request 동작 확인
#### 2-3. FFmpeg spawn 래퍼
- [ ] `services/ffmpeg.ts` — runFFmpeg(), runFFprobe() 함수
- [ ] FFmpeg 설치 확인 로직 (서버 시작 시)
- [ ] stderr 진행률 파싱 (`time=HH:MM:SS.ms` 패턴)
- [ ] 코덱 감지: FFprobe로 H.264 여부 확인 → `-c copy` vs 트랜스코딩 분기
- [ ] 프레임 추출: `-accurate_seek -ss {time} -i {file} -frames:v 1`
#### 2-4. HLS 변환
- [ ] `routes/hls.ts` — POST /api/hls/:videoId/convert, GET 플레이리스트/세그먼트
- [ ] 변환 작업 관리 (Map 기반 인메모리 상태: idle/converting/done/error)
- [ ] H.264 → `-c copy` (빠른 리먹스), 비-H.264 → 트랜스코딩
- [ ] 세그먼트 6초, 키프레임 2초 간격
- [ ] SSE 진행률 엔드포인트: GET /api/hls/:videoId/progress
#### 2-5. tus 업로드
- [ ] `@tus/server` + `@tus/file-store` 설정
- [ ] `routes/upload.ts` — /api/upload 경로에 tus 서버 마운트
- [ ] 업로드 완료 훅: `onUploadFinish`에서 파일 이동 + FFprobe 메타데이터 추출
- [ ] 업로드 크기 제한: MAX_UPLOAD_SIZE (기본 20GB)
- [ ] MIME 타입 + FFprobe 실제 코덱 검증
#### 2-6. 메타데이터 & 영상 관리
- [ ] `routes/meta.ts` — GET /api/meta/:videoId (FFprobe 기반)
- [ ] GET /api/videos — 업로드된 영상 목록
- [ ] DELETE /api/videos/:videoId — 원본+HLS+프레임+썸네일+DB 일괄 삭제
#### 2-7. SQLite + 주석 API
- [ ] `db/schema.sql` — annotations 테이블 스키마
- [ ] better-sqlite3 초기화 (`services/storage.ts`)
- [ ] `routes/annotations.ts` — CRUD + 내보내기 (VTT, SRT, JSON, CSV)
#### 2-8. 파일 정리
- [ ] 서버 시작 시 24시간 이상 된 임시 파일 정리
- [ ] 주기적 정리 (setInterval, 1시간마다)
- [ ] 디스크 잔여 공간 체크 (check-disk-space, 10GB 미만 시 로그 경고)
### 산출물 (완료 기준)
- 서버 기동 성공 (FFmpeg 감지 로그 출력)
- curl로 Range Request 스트리밍 동작 확인
- curl로 영상 업로드 (tus) 성공
- HLS 변환 시작/진행률/완료 확인
- 주석 CRUD API 동작 확인
### 다음 단계 의존성
- 단계 3 (기본 플레이어)은 최소 2-1, 2-2 완료 시 시작 가능
- 단계 4 (프레임 추출)은 2-3 완료 필요
- 단계 6 (텍스트 오버레이)은 2-7 완료 필요
---
## 단계 3: 기본 플레이어
### 목표
Video.js + 이중 경로 재생 (로컬 File API + 서버 Range Request/HLS) + 기본 UI
### 세부 태스크
#### 3-1. Video.js 통합
- [ ] Video.js 8.23.x + 타입 정의 설치
- [ ] `components/player/VideoPlayer.tsx` — ref + useEffect 패턴
- [ ] `hooks/useVideoPlayer.ts` — 초기화/제어/이벤트 추상화
- [ ] Video.js 옵션: fluid, responsive, playbackRates 설정
- [ ] 전체화면: 컨테이너 div 기준 fullscreen (오버레이 유지)
#### 3-2. 서버 파일 재생
- [ ] 서버 영상 목록 조회 UI
- [ ] Range Request URL로 즉시 재생
- [ ] HLS 변환 트리거 + SSE 진행률 표시
- [ ] HLS 준비 완료 시 hls.js로 소스 전환
#### 3-3. 로컬 파일 재생
- [ ] 파일 드래그앤드롭 / 파일 선택 UI
- [ ] `URL.createObjectURL()` → Video.js src 설정
- [ ] 재생 종료/파일 변경 시 `URL.revokeObjectURL()` 호출
#### 3-4. hls.js 설정
- [ ] hls.js 1.6.x 설치 및 Video.js VHS와의 역할 분담 결정
- [ ] backBufferLength: 30, maxBufferSize: 60MB 등 필수 설정 적용
- [ ] 에러 복구 로직 (QuotaExceededError 대응)
#### 3-5. 기본 UI 레이아웃
- [ ] 메인 레이아웃: 플레이어 영역 + 사이드 패널 (영상 목록/주석)
- [ ] 반응형 기본 구조 (Tailwind)
- [ ] Zustand 스토어 기본 구조 (현재 영상, 재생 상태, 소스 타입)
### 산출물 (완료 기준)
- 로컬 mp4 파일 드래그앤드롭 → 즉시 재생
- 서버 영상 선택 → Range Request로 재생
- HLS 변환 완료 후 HLS로 전환 재생
- 전체화면 동작 확인
### 다음 단계 의존성
- 단계 4, 5, 6 모두 이 단계 완료 후 시작 가능
---
## 단계 4: 프레임 추출
### 목표
Canvas 즉시 미리보기 + 서버 FFmpeg API를 통한 정확한 프레임 추출
### 세부 태스크
- [ ] `utils/frameCapture.ts` — Canvas 기반 현재 프레임 캡처 (로컬 파일용 즉시 미리보기)
- [ ] 단일 Canvas 재사용 패턴 적용
- [ ] `routes/frame.ts` 연동 — 서버 FFmpeg 프레임 추출 요청
- [ ] 프레임 추출 결과 표시 UI (모달 또는 패널)
- [ ] 프레임 이미지 다운로드 기능
- [ ] Shift+S 단축키 → 현재 프레임 캡처
### 산출물 (완료 기준)
- 일시정지 상태에서 Shift+S → 프레임 이미지 표시 + 다운로드
- 서버 영상: FFmpeg 정확 추출
- 로컬 파일: Canvas 즉시 캡처
### 다음 단계 의존성
- 단계 5에서 FPS 감지 로직과 연계
---
## 단계 5: 단위 이동
### 목표
프레임/초/장면 단위 seek + requestVideoFrameCallback FPS 감지 + 키보드 단축키
### 세부 태스크
- [ ] `hooks/useFrameStep.ts` — requestVideoFrameCallback으로 FPS 동적 감지 (fallback 30fps)
- [ ] 프레임 이동: `,`(이전) / `.`(다음) — 일시정지 상태에서만
- [ ] 초 이동: ←/→(5초), J/L(10초)
- [ ] 장면 이동: `[`/`]` — 키프레임 기반 또는 일정 간격(30초) 이동
- [ ] 10% 단위 탐색: 0~9 키
- [ ] 재생 속도: +/- 키 (0.25x ~ 4x)
- [ ] 전체화면 토글: F 키
- [ ] 음소거 토글: M 키
- [ ] 키보드 이벤트: 플레이어 포커스 시에만 활성, 텍스트 입력 시 비활성
- [ ] 현재 프레임 번호 / 타임코드 표시 UI
### 산출물 (완료 기준)
- 모든 키보드 단축키 동작 확인
- 프레임 단위 이동 시 타임코드 정확 갱신
- FPS 감지 로그 출력
### 다음 단계 의존성
- 없음 (단계 6과 병렬 진행 가능)
---
## 단계 6: 텍스트 오버레이
### 목표
자막형(WebVTT track) + 메모형(interact.js DOM 오버레이) + 내보내기(subsrt)
### 세부 태스크
#### 6-1. 자막형
- [ ] WebVTT `<track kind="subtitles">` 연동
- [ ] 자막 추가/편집 UI (시작 시간, 종료 시간, 텍스트)
- [ ] 서버 주석 API 연동 (type: "subtitle")
- [ ] VTT/SRT 내보내기 (subsrt 라이브러리)
#### 6-2. 메모형
- [ ] `components/overlay/MemoOverlay.tsx` — DOM 오버레이 컨테이너
- [ ] interact.js 드래그/리사이즈 연동
- [ ] 좌표 정규화: px → % 변환/저장
- [ ] 메모 추가 UI: Shift+M → 현재 시점에 메모 생성
- [ ] 메모 편집/삭제 UI
- [ ] 표시 로직: requestAnimationFrame 루프 + 이진 탐색
- [ ] 서버 주석 API 연동 (type: "memo")
- [ ] JSON/CSV 내보내기
#### 6-3. 주석 관리 패널
- [ ] `hooks/useAnnotations.ts` — 주석 CRUD + 서버 동기화
- [ ] 사이드 패널: 시간순 주석 목록, 클릭 시 해당 시점으로 이동
- [ ] 타임라인 위 주석 마커 표시
### 산출물 (완료 기준)
- 자막 추가 → 영상 재생 시 하단에 표시
- 메모 추가 → 드래그로 위치 조정 가능
- VTT/SRT/JSON/CSV 내보내기 동작 확인
### 다음 단계 의존성
- 없음 (단계 5와 병렬 진행 가능)
---
## 단계 7: UI/UX 통합
### 목표
전체 기능 통합, UI 다듬기, 썸네일 미리보기, 디스크 관리
### 세부 태스크
- [ ] 전체 레이아웃 정리 (플레이어 + 컨트롤 + 사이드패널 통합)
- [ ] 탐색바 썸네일 미리보기 (서버 FFmpeg 스프라이트 + VTT)
- [ ] HLS 변환 진행률 UI (프로그레스 바 + 상태 텍스트)
- [ ] 영상 목록 UI 개선 (업로드 상태, HLS 변환 상태, 용량)
- [ ] 디스크 사용량 표시 + 경고
- [ ] 로딩/에러 상태 처리 (스켈레톤, 에러 바운더리)
- [ ] 키보드 단축키 도움말 오버레이 (? 키)
- [ ] 전체 기능 통합 테스트
- [ ] README.md 작성 (설치, 실행, 사용법)
### 산출물 (완료 기준)
- 전체 기능이 하나의 UI에서 동작
- 서버 파일 + 로컬 파일 모두 테스트 완료
- README.md로 프로젝트 세팅/실행 가능
### 다음 단계 의존성
- 모든 이전 단계 완료 필요
---
## 단계 의존성 다이어그램
```
단계 1 (프로젝트 구조)
├── 단계 2 (백엔드 서버)
│ ├── 단계 4 (프레임 추출) ──┐
│ └── 단계 6-자막 (주석 API) │
└── 단계 3 (기본 플레이어) │
├── 단계 4 (프레임 추출) ──┤
├── 단계 5 (단위 이동) ────┤ → 단계 7 (UI/UX 통합)
└── 단계 6 (텍스트 오버레이)┘
※ 단계 5와 6은 병렬 진행 가능
```