commit 2aae3d1c0dcb2ccddc19ad0f20d786f45c8718b0 Author: minsung Date: Wed Apr 1 15:11:39 2026 +0900 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 diff --git a/.claude/agents/code-reviewer.md b/.claude/agents/code-reviewer.md new file mode 100644 index 0000000..775ee6f --- /dev/null +++ b/.claude/agents/code-reviewer.md @@ -0,0 +1,48 @@ +--- +name: code-reviewer +description: 변경된 코드의 품질, 보안, CLAUDE.md 규칙 준수를 검증하는 리뷰 에이전트. 코드 리뷰 요청 시 사용. +tools: Read, Grep, Glob, Bash +model: sonnet +--- + +You are a senior code reviewer for the abcvideo project. + +## 작업 시작 + +1. **CLAUDE.md** 읽기 → 핵심 구현 규칙 파악 +2. `git diff` 또는 `git diff HEAD~1`로 변경사항 확인 + +## 리뷰 체크리스트 + +### 보안 +- [ ] Path traversal 방어: `path.resolve()` + 허용 디렉토리 검증 +- [ ] 업로드 파일 검증: MIME + FFprobe 코덱 확인 +- [ ] CORS 설정 적절성 +- [ ] `crossorigin="anonymous"` 설정 여부 (Canvas 사용 시) + +### 메모리 관리 +- [ ] `URL.createObjectURL()` 후 `revokeObjectURL()` 호출 +- [ ] Canvas 재사용 (매번 새로 생성하지 않음) +- [ ] hls.js `backBufferLength: 30` 설정 여부 +- [ ] QuotaExceededError 핸들링 + +### 성능 +- [ ] `timeupdate` 이벤트에서 직접 DOM 조작 없음 +- [ ] `requestAnimationFrame` 또는 `requestVideoFrameCallback` 사용 +- [ ] 주석 배열 이진 탐색 O(log n) +- [ ] 오버레이에 `will-change: transform` 적용 + +### 코드 품질 +- [ ] TypeScript 타입 정확성 +- [ ] 에러 핸들링 적절성 +- [ ] 불필요한 코드/주석 없음 +- [ ] CLAUDE.md 패턴 준수 (Range Request, FFmpeg spawn 등) + +## 출력 형식 + +피드백을 우선순위별로 정리: +1. **Critical** — 반드시 수정 (보안 취약점, 메모리 누수, 빌드 실패) +2. **Warning** — 수정 권장 (성능 이슈, 규칙 미준수) +3. **Suggestion** — 개선 제안 (코드 스타일, 가독성) + +각 항목에 파일명:라인번호와 구체적 수정 방법을 포함. diff --git a/.claude/agents/ffmpeg-helper.md b/.claude/agents/ffmpeg-helper.md new file mode 100644 index 0000000..511b3f7 --- /dev/null +++ b/.claude/agents/ffmpeg-helper.md @@ -0,0 +1,41 @@ +--- +name: ffmpeg-helper +description: FFmpeg/FFprobe 명령 생성, 실행, 디버깅을 담당하는 전문 에이전트. FFmpeg 관련 질문이나 영상 처리 작업 시 사용. +tools: Bash, Read, Grep, Glob +model: sonnet +--- + +You are an FFmpeg expert for the abcvideo project. + +## 역할 + +- FFmpeg/FFprobe 명령 생성 및 실행 +- HLS 변환, 프레임 추출, 코덱 분석 +- 영상 메타데이터 확인 +- FFmpeg 에러 디버깅 + +## CLAUDE.md 규칙 준수 + +### HLS 변환 +- H.264 소스: `-c copy` (재인코딩 없이 리먹스, 수 초 내 완료) +- 비-H.264 소스: `-c:v libx264 -preset medium -crf 23 -profile:v high -level 4.1` +- 세그먼트: 6초 (`-hls_time 6`) +- 키프레임: 2초 간격 (`-force_key_frames "expr:gte(t,n_forced*2)"`) +- 반드시 `-hls_list_size 0 -hls_playlist_type vod` 포함 + +### 프레임 추출 +- 정확 추출: `-accurate_seek -ss {time} -i {file} -frames:v 1` +- `-ss`는 `-i` **앞에** 배치 (입력 전 시크) +- 품질: PNG (분석용) 또는 JPEG `-q:v 2` (썸네일용) + +### 코덱 감지 +```bash +ffprobe -v quiet -select_streams v:0 -show_entries stream=codec_name -of csv=p=0 input.mp4 +``` + +## 작업 방식 + +1. 사용자 요청을 FFmpeg 명령으로 변환 +2. 명령 설명 (각 플래그의 의미) +3. 실행 전 확인 요청 +4. 실행 후 결과 분석 diff --git a/.claude/agents/step-worker.md b/.claude/agents/step-worker.md new file mode 100644 index 0000000..e65fba0 --- /dev/null +++ b/.claude/agents/step-worker.md @@ -0,0 +1,37 @@ +--- +name: step-worker +description: PLAN.md의 특정 단계를 읽고 태스크를 순서대로 수행하는 작업 에이전트. 단계별 구현 작업을 요청받을 때 사용. +model: opus +effort: high +--- + +You are the primary implementation agent for the abcvideo project. + +## 작업 시작 프로토콜 (반드시 순서대로) + +1. **CLAUDE.md** 읽기 → 아키텍처, 기술 스택, 구현 규칙 파악 +2. **PLAN.md** 읽기 → 담당 단계의 세부 태스크 확인 +3. **PROGRESS.md** 읽기 → 이전 단계 완료 여부, 알려진 이슈, 인계사항 확인 + +## 작업 규칙 + +- 작업 시작 전 PROGRESS.md에 해당 단계를 "🔄 진행 중"으로 업데이트 +- PLAN.md의 태스크를 위에서 아래로 순서대로 수행 +- 각 태스크 완료 시 PLAN.md에서 `- [ ]`를 `- [x]`로 변경 +- 의미 있는 마일스톤마다 PROGRESS.md 업데이트 (완료 항목, 이슈) +- 이전 에이전트가 작성한 코드를 이유 없이 대폭 변경하지 않음 +- CLAUDE.md의 핵심 구현 규칙을 반드시 준수 + +## 작업 완료 시 + +PROGRESS.md에 다음을 기록: +- 상태를 "✅ 완료"로 변경 +- 완료 항목 목록 +- 미완료/이슈 사항 +- **다음 단계 참고**: 후속 에이전트가 반드시 알아야 할 사항 + +## 주의사항 + +- 산출물(완료 기준)을 충족하지 못하면 "✅ 완료"로 표시하지 않음 +- 블로커 발견 시 PROGRESS.md에 기록하고 사용자에게 보고 +- 한 단계에서 다른 단계의 태스크를 수행하지 않음 diff --git a/.claude/agents/test-runner.md b/.claude/agents/test-runner.md new file mode 100644 index 0000000..1fe4a38 --- /dev/null +++ b/.claude/agents/test-runner.md @@ -0,0 +1,71 @@ +--- +name: test-runner +description: 빌드 확인, API 테스트, 단계별 산출물 검증을 수행하는 에이전트. 구현 완료 후 검증 요청 시 사용. +tools: Bash, Read, Grep, Glob +model: sonnet +--- + +You are the verification agent for the abcvideo project. + +## 역할 + +구현된 코드가 PLAN.md의 산출물(완료 기준)을 충족하는지 검증. + +## 검증 프로토콜 + +1. **PLAN.md** 읽기 → 해당 단계의 산출물(완료 기준) 확인 +2. **PROGRESS.md** 읽기 → 현재 상태, 알려진 이슈 확인 +3. 산출물 항목을 하나씩 검증 +4. 결과를 PROGRESS.md에 기록 + +## 검증 항목별 방법 + +### 빌드 검증 +```bash +# TypeScript 컴파일 +cd client && npx tsc --noEmit +cd server && npx tsc --noEmit + +# 빌드 +npm run build +``` + +### 서버 검증 +```bash +# 서버 기동 +npm run dev:server & +sleep 3 + +# API 헬스 체크 +curl -s http://localhost:3001/api/videos + +# Range Request 테스트 +curl -I -H "Range: bytes=0-1023" http://localhost:3001/api/stream/{videoId} + +# HLS 변환 테스트 +curl -X POST http://localhost:3001/api/hls/{videoId}/convert + +# 주석 API 테스트 +curl -X POST http://localhost:3001/api/annotations/{videoId} \ + -H "Content-Type: application/json" \ + -d '{"type":"memo","timeStart":10,"text":"test"}' +``` + +### 클라이언트 검증 +```bash +# Vite 개발 서버 기동 +npm run dev:client & +sleep 3 + +# 빌드 확인 +cd client && npm run build +``` + +## 출력 형식 + +각 산출물 항목에 대해: +- ✅ 통과: 항목명 + 확인 방법 +- ❌ 실패: 항목명 + 에러 내용 + 원인 분석 +- ⚠️ 부분: 항목명 + 동작하지만 이슈 있음 + +최종 결과를 PROGRESS.md에 기록. diff --git a/.claude/hooks/auto-lint.sh b/.claude/hooks/auto-lint.sh new file mode 100644 index 0000000..ff98ba5 --- /dev/null +++ b/.claude/hooks/auto-lint.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# auto-lint.sh +# PostToolUse (Edit|Write) 훅: TypeScript 파일 수정 후 컴파일 체크 +# +# 수정된 파일이 .ts/.tsx인 경우 tsc --noEmit으로 타입 체크 + +INPUT=$(cat) +FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty') + +# 파일 경로가 없으면 통과 +if [ -z "$FILE_PATH" ]; then + exit 0 +fi + +# TypeScript 파일이 아니면 통과 +if ! echo "$FILE_PATH" | grep -qE '\.(ts|tsx)$'; then + exit 0 +fi + +# client/ 파일인 경우 +if echo "$FILE_PATH" | grep -q 'client/'; then + cd "$CLAUDE_PROJECT_DIR/client" 2>/dev/null + if [ -f "tsconfig.json" ]; then + npx tsc --noEmit --pretty false 2>&1 | tail -5 + fi +fi + +# server/ 파일인 경우 +if echo "$FILE_PATH" | grep -q 'server/'; then + cd "$CLAUDE_PROJECT_DIR/server" 2>/dev/null + if [ -f "tsconfig.json" ]; then + npx tsc --noEmit --pretty false 2>&1 | tail -5 + fi +fi + +exit 0 diff --git a/.claude/hooks/guard-history-fields.py b/.claude/hooks/guard-history-fields.py new file mode 100644 index 0000000..6ffd553 --- /dev/null +++ b/.claude/hooks/guard-history-fields.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +""" +PostToolUse(Write) 훅: 히스토리 파일 필수 필드 검증 +path.json 의 history_path 하위 *.md 파일에 +소요 시간 / Context 사용량 누락 시 exit 2로 Write 차단 +""" +import sys +import json +import re +import os + +# ── path.json 로드 ──────────────────────────────────────────────────────────── +HOOKS_DIR = os.path.dirname(os.path.abspath(__file__)) +PATH_JSON = os.path.join(HOOKS_DIR, "path.json") + +try: + with open(PATH_JSON, encoding="utf-8") as f: + paths = json.load(f) +except Exception: + paths = {} + +history_path = paths.get("history_path", "docs/history").replace("\\", "/").rstrip("/") +# history_path 하위 어느 깊이든 *.md 이면 검사 +history_pattern = re.escape(history_path) + r"/.+\.md$" + +# ── 훅 입력 파싱 ────────────────────────────────────────────────────────────── +try: + data = json.load(sys.stdin) +except Exception: + sys.exit(0) + +tool_input = data.get("tool_input", {}) +file_path = tool_input.get("file_path", "") +content = tool_input.get("content", "") + +normalized = file_path.replace("\\", "/") +if not re.search(history_pattern, normalized): + sys.exit(0) + +missing = [] + +if not re.search( + r"\*\*소요\s*시간\*\*|^##\s*소요\s*시간|소요\s*시간\s*:", + content, + re.MULTILINE, +): + missing.append("소요 시간") + +if not re.search( + r"\*\*Context\s*사용량\*\*|^##\s*Context\s*사용량|Context\s*사용량\s*:", + content, + re.MULTILINE | re.IGNORECASE, +): + missing.append("Context 사용량") + +if missing: + sys.stderr.write("[BLOCKED] 히스토리 파일 필수 항목 누락: " + ", ".join(missing) + "\n") + sys.stderr.write("\n") + sys.stderr.write("파일 상단에 다음 항목을 반드시 포함하세요:\n") + sys.stderr.write("\n") + sys.stderr.write(" **소요 시간**: X분\n") + sys.stderr.write(" **Context 사용량**: input Xk / output Xk tokens\n") + sys.stderr.write("\n") + sys.stderr.write("파일: " + file_path + "\n") + sys.exit(2) + +# ── 이슈 번호 검사 (없으면 사용자에게 질문 요청) ────────────────────────────── +if not re.search(r"\*\*이슈\*\*\s*[:\s]+#\d+", content, re.MULTILINE): + sys.stderr.write("[이슈 번호 필요] 히스토리 파일에 이슈 번호가 없습니다.\n") + sys.stderr.write("\n") + sys.stderr.write("사용자에게 다음을 질문하세요:\n") + sys.stderr.write(" '이 작업과 관련된 Gitea 이슈 번호가 있나요? (예: #1 / 없으면 #0)'\n") + sys.stderr.write("\n") + sys.stderr.write("답변 후 파일 상단에 추가하고 다시 저장하세요:\n") + sys.stderr.write(" **이슈**: #N\n") + sys.stderr.write("\n") + sys.stderr.write("파일: " + file_path + "\n") + sys.exit(2) + +sys.exit(0) diff --git a/.claude/hooks/guard-history-fields.sh b/.claude/hooks/guard-history-fields.sh new file mode 100644 index 0000000..756e175 --- /dev/null +++ b/.claude/hooks/guard-history-fields.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# PostToolUse(Write) 훅: 히스토리 파일 필수 필드 검증 +# 대상: docs/*/history/*.md +# 필수 항목(소요 시간, Context 사용량) 누락 시 exit 2로 Write 차단 +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cat | python3 "$SCRIPT_DIR/guard-history-fields.py" diff --git a/.claude/hooks/guard-history-reminder.sh b/.claude/hooks/guard-history-reminder.sh new file mode 100644 index 0000000..d0814d7 --- /dev/null +++ b/.claude/hooks/guard-history-reminder.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# Stop 훅: 작업 완료 후 히스토리 기록 리마인더 +# path.json 의 history_path 에서 저장 경로를 읽어 출력 +set -euo pipefail + +HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PATH_JSON="$HOOKS_DIR/path.json" +PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(cd "$HOOKS_DIR/../.." && pwd)}" + +if command -v jq &>/dev/null && [[ -f "$PATH_JSON" ]]; then + history_path=$(jq -r '.history_path // "docs/history"' "$PATH_JSON") +else + history_path="docs/history" +fi + +TODAY=$(date +%Y-%m-%d) +HISTORY_ABS="$PROJECT_DIR/$history_path" + +# 오늘 날짜로 시작하는 히스토리 파일이 있으면 통과 +if ls "$HISTORY_ABS/${TODAY}"_*.md 2>/dev/null | grep -q .; then + exit 0 +fi + +cat >&2 <&2 + exit 2 +fi + +# storage/ 외부에 영상 파일 쓰기 차단 +# 주의: .ts는 TypeScript와 HLS 세그먼트 모두에 사용됨 +# src/ 하위의 .ts 파일은 TypeScript이므로 허용 +if echo "$FILE_PATH" | grep -qiE '\.(mp4|mkv|webm|mov|avi|m3u8)$'; then + if ! echo "$FILE_PATH" | grep -q 'storage/'; then + echo "Blocked: 영상/HLS 파일은 storage/ 디렉토리 안에만 생성 가능합니다." >&2 + exit 2 + fi +fi +# HLS .ts 세그먼트 파일만 차단 (src/ 하위 TypeScript 제외) +if echo "$FILE_PATH" | grep -qE '\.ts$'; then + if ! echo "$FILE_PATH" | grep -qE '(src/|\.config\.ts|tsconfig)'; then + if echo "$FILE_PATH" | grep -q 'storage/'; then + : # storage 내 .ts 세그먼트는 허용 + elif echo "$FILE_PATH" | grep -qiE 'segment|hls'; then + echo "Blocked: HLS 세그먼트 파일은 storage/ 디렉토리 안에만 생성 가능합니다." >&2 + exit 2 + fi + fi +fi + +exit 0 diff --git a/.claude/hooks/session-context.sh b/.claude/hooks/session-context.sh new file mode 100644 index 0000000..3f4b1bb --- /dev/null +++ b/.claude/hooks/session-context.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# UserPromptSubmit 훅: 프로젝트 컨텍스트 주입 +# path.json 의 history_path 아래 최근 작업 문서를 읽어 출력 +set -euo pipefail + +HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PATH_JSON="$HOOKS_DIR/path.json" +PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(cd "$HOOKS_DIR/../.." && pwd)}" + +if command -v jq &>/dev/null && [[ -f "$PATH_JSON" ]]; then + history_rel=$(jq -r '.history_path // "docs/history"' "$PATH_JSON") +else + history_rel="docs/history" +fi + +history_path="$PROJECT_DIR/$history_rel" + +echo "### Project operating context" +echo "" +echo "Recent history files (${history_rel}):" +if [[ -d "$history_path" ]]; then + find "$history_path" -maxdepth 1 -type f -name "*.md" | sort -r | head -5 | sed "s|$PROJECT_DIR/||" | sed 's#^#- #' +else + echo "- (none)" +fi +echo "" +echo "### 히스토리 기록 규칙" +echo "작업이 완료되면 반드시 ${history_rel}/YYYY-MM-DD_{작업명}.md 파일을 작성하라." +echo "필수 항목: **소요 시간**, **Context 사용량** (누락 시 저장 차단됨)" diff --git a/.claude/hooks/session-start.sh b/.claude/hooks/session-start.sh new file mode 100644 index 0000000..484bdbb --- /dev/null +++ b/.claude/hooks/session-start.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# session-start.sh +# SessionStart 훅: 세션 시작 시 프로젝트 상태를 컨텍스트에 주입 +# +# PROGRESS.md의 현재 상태 요약을 읽어서 에이전트에게 전달 + +PROGRESS_FILE="$CLAUDE_PROJECT_DIR/PROGRESS.md" + +if [ -f "$PROGRESS_FILE" ]; then + # PROGRESS.md에서 "현재 상태 요약" 섹션 추출 (첫 20줄) + STATUS=$(head -20 "$PROGRESS_FILE") + + cat <" +disable-model-invocation: true +--- + +## FFmpeg 명령 생성 및 실행 + +요청: **$ARGUMENTS** + +### 실행 규칙 + +1. **CLAUDE.md**의 FFmpeg 관련 규칙을 먼저 확인하세요: + - HLS 변환: 세그먼트 6초, 키프레임 2초, H.264 소스는 `-c copy` + - 프레임 추출: `-accurate_seek -ss {time} -i {file} -frames:v 1` + - 코덱 감지: `ffprobe -v quiet -select_streams v:0 -show_entries stream=codec_name` + +2. 요청 내용을 FFmpeg/FFprobe 명령으로 변환 + +3. 실행 전 다음을 표시: + - 생성된 명령 + - 각 플래그의 의미 (한줄 설명) + - 예상 결과 + +4. 명령 실행 후 결과 분석: + - 성공: 출력 파일 위치, 크기, 소요 시간 + - 실패: stderr 분석, 원인 설명, 수정된 명령 제안 + +### 자주 사용하는 패턴 + +- `영상 정보 확인` → `ffprobe -v quiet -print_format json -show_format -show_streams` +- `HLS 변환` → CLAUDE.md의 HLS 변환 명령 패턴 사용 +- `프레임 추출` → `-accurate_seek -ss {time} -i {file} -frames:v 1` +- `코덱 확인` → `ffprobe -v quiet -select_streams v:0 -show_entries stream=codec_name -of csv=p=0` +- `썸네일 스프라이트` → 10초 간격 프레임 추출 후 타일링 diff --git a/.claude/skills/review/SKILL.md b/.claude/skills/review/SKILL.md new file mode 100644 index 0000000..4e3f699 --- /dev/null +++ b/.claude/skills/review/SKILL.md @@ -0,0 +1,40 @@ +--- +name: review +description: 최근 코드 변경사항을 CLAUDE.md 규칙 기준으로 리뷰합니다. +disable-model-invocation: true +context: fork +agent: code-reviewer +--- + +## 코드 리뷰 + +최근 변경사항을 CLAUDE.md의 핵심 구현 규칙 기준으로 리뷰하세요. + +### 리뷰 범위 + +변경된 파일 확인: +!`git diff --name-only HEAD~1 2>/dev/null || echo "git diff 불가 - 전체 파일 리뷰"` + +### 리뷰 기준 (CLAUDE.md에서 발췌) + +**보안:** +- Path traversal 방어 (path.resolve + 허용 디렉토리 검증) +- 업로드 MIME + FFprobe 검증 +- CORS 설정 + +**메모리:** +- URL.createObjectURL → revokeObjectURL 쌍 +- Canvas 재사용 +- hls.js backBufferLength: 30 + +**성능:** +- timeupdate 직접 DOM 조작 금지 +- requestAnimationFrame/requestVideoFrameCallback 사용 +- 이진 탐색 O(log n) + +**패턴 준수:** +- Range Request: createReadStream + pipeline +- FFmpeg: child_process.spawn 래퍼 +- Video.js: ref + useEffect 패턴 + +피드백을 Critical / Warning / Suggestion 으로 분류하세요. diff --git a/.claude/skills/status/SKILL.md b/.claude/skills/status/SKILL.md new file mode 100644 index 0000000..c221df1 --- /dev/null +++ b/.claude/skills/status/SKILL.md @@ -0,0 +1,25 @@ +--- +name: status +description: 프로젝트 진행 상태를 확인하고 다음 작업을 제안합니다. +disable-model-invocation: true +--- + +## 프로젝트 진행 상태 확인 + +아래 파일들을 읽고 현재 상태를 요약하세요: + +1. **PROGRESS.md** 읽기 — 현재 상태 요약, 단계별 진행 기록 + +2. **PLAN.md** 읽기 — 미완료 태스크 (`- [ ]`) 개수 파악 + +3. 다음 내용을 표 형태로 보고: + +| 단계 | 상태 | 완료 태스크 | 남은 태스크 | +|------|------|------------|------------| + +4. **다음에 할 작업** 제안: + - 의존성이 충족된 다음 단계 식별 + - 블로커가 있으면 강조 + - 병렬 진행 가능한 단계가 있으면 알림 + +5. 블로커/이슈가 있으면 빨간색으로 강조 diff --git a/.claude/skills/step/SKILL.md b/.claude/skills/step/SKILL.md new file mode 100644 index 0000000..cb2ee9c --- /dev/null +++ b/.claude/skills/step/SKILL.md @@ -0,0 +1,34 @@ +--- +name: step +description: PLAN.md의 특정 단계를 실행합니다. 단계 번호를 인자로 전달합니다. +argument-hint: "<단계번호>" +disable-model-invocation: true +--- + +## 단계 $ARGUMENTS 실행 + +step-worker 에이전트를 사용하여 PLAN.md의 **단계 $ARGUMENTS**를 수행합니다. + +### 실행 프로토콜 + +1. 먼저 아래 3개 파일을 순서대로 읽으세요: + - `CLAUDE.md` — 프로젝트 규칙/아키텍처 + - `PLAN.md` — 단계 $ARGUMENTS의 세부 태스크 확인 + - `PROGRESS.md` — 이전 단계 완료 여부, 블로커, 인계사항 + +2. PROGRESS.md에서 단계 $ARGUMENTS의 상태를 "🔄 진행 중"으로 업데이트 + +3. PLAN.md의 단계 $ARGUMENTS 태스크를 위에서 아래로 순서대로 수행: + - 각 태스크 완료 시 PLAN.md에서 `- [ ]`를 `- [x]`로 변경 + - 의미 있는 마일스톤마다 PROGRESS.md 업데이트 + +4. 모든 태스크 완료 후: + - PLAN.md의 **산출물(완료 기준)**을 직접 검증 + - PROGRESS.md에 최종 결과 기록 (완료 항목, 미완료/이슈, 다음 단계 참고) + - 산출물 충족 시 상태를 "✅ 완료"로 변경 + +### 주의사항 + +- 이전 단계가 완료되지 않았으면 사용자에게 알리고 중단 +- 블로커 발견 시 PROGRESS.md에 기록 후 사용자에게 보고 +- 이전 에이전트가 작성한 코드를 이유 없이 변경하지 않음 diff --git a/.claude/skills/verify/SKILL.md b/.claude/skills/verify/SKILL.md new file mode 100644 index 0000000..3d79904 --- /dev/null +++ b/.claude/skills/verify/SKILL.md @@ -0,0 +1,34 @@ +--- +name: verify +description: 특정 단계의 산출물(완료 기준)을 검증합니다. 단계 번호를 인자로 전달합니다. +argument-hint: "<단계번호>" +disable-model-invocation: true +--- + +## 단계 $ARGUMENTS 검증 + +PLAN.md의 **단계 $ARGUMENTS**에 정의된 산출물(완료 기준)을 하나씩 검증합니다. + +### 검증 프로토콜 + +1. **PLAN.md** 읽기 → 단계 $ARGUMENTS의 "산출물 (완료 기준)" 섹션 확인 +2. **PROGRESS.md** 읽기 → 현재 상태, 알려진 이슈 확인 + +3. 각 산출물 항목을 실제로 실행하여 검증: + - 빌드 확인: `npx tsc --noEmit`, `npm run build` + - 서버 기동: 서버 시작 후 API 엔드포인트 curl 테스트 + - 클라이언트: 개발 서버 기동 확인, 빌드 성공 확인 + - 기능 테스트: 해당 단계의 핵심 기능 동작 확인 + +4. 각 항목에 대해 결과 보고: + - ✅ 통과: 확인 방법 + 결과 + - ❌ 실패: 에러 내용 + 원인 분석 + 수정 제안 + - ⚠️ 부분 통과: 동작하지만 이슈 있음 + +5. 종합 결과를 PROGRESS.md에 기록 + +### 주의사항 + +- 실행 중인 프로세스는 검증 후 반드시 종료 +- 서버 포트 충돌 시 기존 프로세스 확인 후 처리 +- 검증만 수행하고 코드 수정은 하지 않음 (수정이 필요하면 보고) diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..3b3c0fb --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "env": { + "browser": true, + "node": true, + "es2022": true + }, + "rules": { + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95ef4cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +node_modules/ +dist/ +build/ +.env +.env.local +storage/videos/ +storage/hls/ +storage/frames/ +storage/thumbnails/ +storage/*.db +*.log +.DS_Store + +# 대용량 미디어 파일 +*.MP4 +*.mp4 +*.mkv +*.avi +*.mov +*.webm +*.srt + +# 샘플 데이터 (대용량) +samplevideo/ + +# Python 소스 입력 대용량 파일 +pythonsource/input/*.MP4 +pythonsource/input/*.mp4 +pythonsource/input/*.srt + +# SQLite WAL/SHM +storage/*.db-wal +storage/*.db-shm + +# cloudflared log +storage/cloudflared.log diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..1f4c4bb --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "printWidth": 100 +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..11555d6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,609 @@ +# CLAUDE.md — abcvideo 프로젝트 + +이 파일은 Claude Code가 abcvideo 프로젝트에서 작업할 때 참조하는 가이드입니다. + +--- + +## 작업 완료 시 필수 — 히스토리 기록 + +작업(대화 세션)이 끝날 때 반드시 아래 형식으로 히스토리 파일을 작성한다. + +``` +파일 경로: docs/history/YYYY-MM-DD_{작업명}.md +``` + +필수 항목 (누락 시 Stop 훅이 응답을 차단함): + +```markdown +**소요 시간**: X분 +**Context 사용량**: input Xk / output Xk tokens +``` + +- 날짜는 오늘 날짜(YYYY-MM-DD), 작업명은 작업 내용을 한글 또는 영문으로 간략히 +- 같은 날 여러 작업이면 각각 별도 파일 (예: `2026-04-01_StationOverlay-분석.md`) +- 소요 시간: 대화 시작부터 작업 완료까지의 실제 경과 시간 +- Context 사용량: 이 대화의 토큰 사용량 (모를 경우 추정값 기재) + +--- + +## 에이전트 협업 규칙 + +### 필수 파일 + +| 파일 | 용도 | 누가 관리 | +|------|------|----------| +| `CLAUDE.md` | 프로젝트 가이드 (아키텍처, 규칙, 기술 스택) | 사용자/아키텍트 | +| `PLAN.md` | 전체 구현 계획 + 각 단계별 세부 태스크 | 계획 수립 시 작성, 에이전트가 필요 시 업데이트 | +| `PROGRESS.md` | 각 단계의 진행 상태 + 완료/미완료/이슈 기록 | 작업하는 에이전트가 실시간 업데이트 | + +### 에이전트 작업 시작 프로토콜 + +모든 에이전트는 작업 시작 시 반드시 아래 순서를 따른다: + +``` +1. CLAUDE.md 읽기 → 프로젝트 규칙/아키텍처 파악 +2. PLAN.md 읽기 → 전체 계획에서 자신이 담당할 단계 확인 +3. PROGRESS.md 읽기 → 이전 단계 완료 여부, 현재 상태, 알려진 이슈 확인 +4. 작업 시작 전 PROGRESS.md에 자신의 단계를 "🔄 진행 중"으로 업데이트 +5. 작업 완료 후 PROGRESS.md에 결과 기록 (완료 항목, 남은 이슈, 다음 단계 참고사항) +``` + +### PLAN.md 구조 + +```markdown +# PLAN.md +## 단계 N: 단계 이름 +### 목표 +### 세부 태스크 +- [ ] 태스크 1 +- [ ] 태스크 2 +### 산출물 (완료 기준) +### 다음 단계 의존성 +``` + +- 각 단계는 독립적으로 실행 가능한 단위로 분할 +- 태스크는 체크박스(`- [ ]` / `- [x]`)로 관리 +- 완료 기준(산출물)이 명확해야 다음 에이전트가 판단 가능 + +### PROGRESS.md 구조 + +```markdown +# PROGRESS.md +## 현재 상태 요약 +- 마지막 완료 단계: N +- 현재 진행 단계: N+1 +- 블로커: (있으면 기록) + +## 단계별 진행 기록 +### 단계 N: 단계 이름 +- 상태: ✅ 완료 | 🔄 진행 중 | ⏳ 대기 | ❌ 실패 +- 완료일: YYYY-MM-DD +- 완료 항목: + - 항목 1 + - 항목 2 +- 미완료/이슈: + - 이슈 설명 +- 다음 단계 참고: + - 후속 에이전트가 알아야 할 사항 +``` + +- 에이전트는 작업 중 **의미 있는 마일스톤마다** PROGRESS.md를 업데이트 +- 실패 시 원인과 시도한 방법을 기록하여 다음 에이전트가 같은 실수를 반복하지 않도록 함 +- 이전 에이전트가 남긴 "다음 단계 참고"를 반드시 확인 후 작업 시작 + +### 에이전트 간 인계 규칙 + +1. **이전 단계 산출물 검증**: 코드가 빌드/실행 가능한 상태인지 확인 후 작업 시작 +2. **기존 코드 존중**: 이전 에이전트가 작성한 코드를 이유 없이 대폭 변경하지 않음 +3. **충돌 방지**: 같은 파일을 동시에 수정하는 병렬 에이전트 실행 금지 +4. **이슈 에스컬레이션**: 블로커 발견 시 PROGRESS.md에 기록하고 사용자에게 보고 + +--- + +## 프로젝트 개요 + +웹 브라우저(PC)에서 **2GB 이상의 대용량 동영상**을 안정적으로 재생하는 특화 기능 탑재 플레이어. +단순 재생을 넘어 **프레임 추출, 단위 이동, 텍스트 오버레이** 등 영상 분석/편집 보조 도구로 기능. + +### 핵심 요구사항 + +1. 2GB 이상 동영상 안정 재생 (서버 파일 + 로컬 파일 모두) +2. 프레임 단위 추출 (정확도 우선) +3. 입력 단위 기준 앞뒤 이동 (프레임 / 초 / 장면) +4. 영상 위 텍스트 입력 (자막형 + 메모형 병행 지원) +5. 확장 가능한 플러그인 아키텍처 + +### 설계 결정 (2026-03-24 확정) + +| 항목 | 결정 | 비고 | +|------|------|------| +| 사용자 모델 | **단일 사용자 도구** | 인증/세션/큐 불필요, 구현 단순화 | +| 로컬 파일 | **업로드 없이 직접 재생** | File API → `URL.createObjectURL()` | +| 프레임 정확도 | **표준 수준** | CFR: `1/fps` 산술, VFR: best-effort | +| HLS 변환 전 재생 | **원본 즉시 재생** | Range Request로 바로 재생, HLS는 백그라운드 변환 | +| 브라우저 지원 | **PC 웹 우선** | Chrome/Firefox/Edge, iOS Safari 무시 | + +--- + +## 아키텍처 결정 (ADR) + +### 재생 이중 경로 + +``` +서버 파일: GET /api/stream/:videoId (Range Request) → 즉시 재생 + POST /api/hls/:videoId/convert → 백그라운드 HLS 변환 + HLS 준비 완료 시 → hls.js로 전환 (seek 안정성 향상) + +로컬 파일: File API → URL.createObjectURL() →