Files
make_video_site/README.md
Claude (owner) 6eac31401a docs(readme): fix two remaining mismatches with implementation
리뷰어 P2 잔여 2건 반영:
- URL 규칙의 1단계 폴더 설명을 "서브폴더 + 영상 혼합" → "하위 폴더만
  표시" 로 정정. 같은 문서 안 폴더 화면 스펙(73-75)과 일관되게 맞춤.
- 관리자 페이지 우클릭 메뉴에 적혀 있던 "영상-허용 토글" 제거. 실제
  dashboard.js 컨텍스트 메뉴는 이름 변경 / 삭제만 존재하고 DB 에도
  폴더별 영상 허용 필드가 없음.

코드 변경 없음. `npm run build` 통과.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 10:51:51 +09:00

113 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 비디오 사이트
영상 업로드/관리/공개 재생을 다 한 곳에서 처리하는 Express + EJS + better-sqlite3 사이트입니다. 컴퓨터 파일이나 YouTube 링크(단일 영상/플레이리스트) 를 가져와 폴더 단위로 정리하고, 짧은 공유 URL로 외부에 노출합니다.
## 실행
처음 한 번은 `setup` 으로 의존성 + yt-dlp 바이너리 + 빌드까지 한 번에 끝낼 수 있어요:
```bash
cp .env.example .env # 포트/호스트 등 환경변수 편집
npm run setup # npm install + ./bin/yt-dlp 다운로드 + tsc
npm start # .env 의 HOST:PORT 로 바인딩
```
수동으로 단계별로 하고 싶다면:
```bash
npm install
npm run build
npm start
```
설정은 전부 `.env` 한 파일로 모았습니다 (`.env.example` 참고).
- `HOST` — 바인딩 주소. 외부 노출이 필요하면 `0.0.0.0`, 로컬 전용이면 `127.0.0.1`.
- `PORT` — 바인딩 포트 (기본 `3000`).
- `SESSION_SECRET` — 운영 시 반드시 충분히 긴 랜덤 문자열로 교체.
- `UPLOAD_MAX_BYTES` — 업로드 용량 한도(바이트). 비우거나 미설정이면 기본 `1073741824` (1 GiB). 무제한으로 두려면 `0` 또는 `Infinity`. 잘못된 값은 기본 1 GiB 로 fallback.
- `HTTP_REQUEST_TIMEOUT_MS` — 대용량 업로드용 HTTP 요청 타임아웃(밀리초). `0`/미설정이면 무제한.
- `MAX_DOWNLOAD_CONCURRENCY` — 플레이리스트 임포트 시 동시 yt-dlp 잡 개수 상한 (기본 `2`).
- 관리자 비밀번호는 `account.json``password` 값 (초기값 `admin`, 운영 시 반드시 변경).
## 외부 의존
- `yt-dlp` — YouTube 영상 가져오기. `npm run setup``./bin/yt-dlp` 로 자동 설치하지만 PATH 에 이미 있어도 됩니다. Alpine / distroless 같이 glibc 가 없는 슬림 도커 베이스에서는 번들 바이너리가 안 도니 `apk add yt-dlp` (또는 `apt-get install yt-dlp`, `pip install yt-dlp`) 로 PATH 에 직접 설치하세요. 도커 빌드에서 아예 받지 않으려면 `SKIP_YT_DLP=1 npm run setup`. (검증 실패해도 setup 은 경고만 출력하고 빌드는 계속 진행됩니다.)
- `ffmpeg` / `ffprobe` — 영상 트림 저장, 60fps/FHD 변환, 메타 조회 (`PATH` 에 설치). 없으면 다운로드는 되지만 후처리가 건너뛰어집니다.
- 하드웨어 인코더(`h264_nvenc` / `h264_qsv` / `h264_videotoolbox` / `h264_vaapi`) — 있으면 자동 감지해 사용. 없으면 `libx264 -preset ultrafast` 폴백. VAAPI 는 `/dev/dri/renderD128` 이 보일 때만 후보로 들어갑니다.
## 데이터 위치
```
data/
app.db # 메인 sqlite (폴더 트리, 영상 메타, 세션)
folders/<topName>[/<subName>]/<videoId>/ # 영상 파일
original.<ext> # 원본 (항상 보존)
edited.<ext> # 트림 저장 시 생성
jobs/ # 백그라운드 yt-dlp 다운로드 잡 상태
tmp/ # 작업 임시 (업로드/변환 중간 산출물)
```
폴더 트리 / 영상 메타는 `app.db` 에서 관리하고, 실제 영상 파일은 폴더 경로 + videoId 디렉터리에 저장됩니다. 폴더 이름을 바꾸면 디스크 디렉터리도 같이 rename 됩니다.
## URL 규칙
공개:
- `/` — 최상위 폴더 목록
- `/folder/:topName` — 1단계 폴더 (하위 폴더만 표시)
- `/folder/:topName/:subName` — 2단계 폴더 (영상만 표시, 더 깊이 들어갈 수 없음)
- `/video/:topName/:videoId` 또는 `/video/:topName/:subName/:videoId` — 외부 공유용 짧은 URL. 경로의 폴더 부분이 영상 위치와 일치할 때만 200.
- `/file/video/:videoId` — 실제 파일 스트림 (편집본 있으면 편집본). `?edited=0` 으로 원본 강제.
관리자(`/op`):
- `/op` — 폴더 목록 (로그인 필요)
- `/op/folder/:topName[/[:subName]]` — 관리자 폴더 화면
- `/op/folder/:topName[/[:subName]]/video/editor` — 영상 추가/편집 화면
## 스펙
### 메인 페이지 (`/`)
- 최상위 폴더를 카드 그리드로 보여줍니다.
- 폴더를 누르면 `/folder/:폴더이름` 으로 이동.
### 폴더 화면 (`/folder/:topName[/:subName]`)
- 1단계 폴더 화면: **하위 폴더 카드만** 보여줍니다 (영상은 2단계로 내려야 표시).
- 2단계 폴더 화면: **영상만** 보여줍니다 (썸네일 + 제목). 더 깊이 들어갈 수 없습니다.
- 영상 카드 클릭 → `/video/:topName[/:subName]/:videoId` 로 이동, 플레이어가 뜨고 재생.
### 관리자 페이지 (`/op`)
- 로그인 로직과 navbar 는 `git.tkrmagid.kr/tkrmagid/minecraft_launcher` 의 op 페이지에서 가져온 형식을 따릅니다.
- 메인 페이지처럼 폴더를 나열, 우클릭으로 이름 수정 / 삭제.
- 오른쪽 위 "폴더 추가" 버튼으로 폴더 생성 (팝업에서 이름 입력).
### 관리자 폴더 화면 (`/op/folder/...`)
- 영상/서브폴더 목록을 유튜브 스타일로 나열. 우클릭으로 수정/삭제.
- 영상 ID 인라인 편집 가능 (전체 영상에 걸쳐 고유, 안전한 ID 형태만 허용. 변경하면 공유 URL 의 마지막 세그먼트가 같이 바뀝니다).
- 오른쪽 위 "영상 추가" → `/op/folder/.../video/editor` 로 이동.
- 2단계 폴더에서만 노출되는 "플레이리스트 임포트" 버튼: YouTube 플레이리스트 URL 을 넣으면 항목을 미리 보여주고 시작할 수 있습니다.
- 항목별 **드래그 정렬** (정렬 UI 는 플레이리스트 임포트 화면 한정), 제목 / ID 인라인 편집.
- ID 정책 토글:
- **랜덤** — 항목별로 24자 hex (crypto.randomUUID 압축, 폴백은 16진수 24자) 자동 생성.
- **시퀀셜** — 원래 플레이리스트 순서 기준 1, 2, 3, ... 으로 부여. zero-pad 옵션을 켜면 항목 총개수 자릿수에 맞춰 `001, 002, ...` 처럼 패딩.
- "가져오기 시작" 누르면 각 항목이 큐에 들어가고 백그라운드에서 다운로드 + 후처리.
### 영상추가 화면 (`/op/folder/.../video/editor`)
- 입력: 컴퓨터 파일 드래그&드롭, 파일 선택 다이얼로그, YouTube URL.
- YouTube URL 의 경우:
1. "확인" 버튼으로 `probe` (title / duration / 예상 파일 크기). probe 가 끝나야 "가져오기" 버튼이 활성화됩니다 (probe 전에 가져오기 누르는 것 자체가 막힘).
2. 예상 소요시간이 5분 이상으로 추정되면 경고를 띄움.
3. 백그라운드 잡이 시작되고, 다른 페이지를 만지더라도 진행 상황이 계속 업데이트됩니다.
- 진행률 바: yt-dlp 가 video / audio 스트림을 순차로 받으면서 매번 0%→100% 로 보고하는 걸 누적해 부드럽게 0→50% 로 climb 합니다. 50→99% 구간은 후처리(60fps 변환 + FHD 캡) 진행률. ffmpeg 가 잠시 진행률을 안 뱉어도 멈춰 보이지 않도록 2초마다 경과시간 메시지가 갱신됩니다.
- 다운로드 해상도는 강제 FHD 캡. 원본이 FHD 이하면 그대로, 초과면 강제로 1920×1080 이하로 다운스케일.
- 다운로드 후 자동 후처리:
- 원본이 60fps 미만이면 fps=60 프레임 복제로 끌어올림.
- 해상도가 FHD 초과면 FHD 로 다운스케일 (종횡비 유지, 짝수 변 보정).
- 위 둘 모두 불필요하면 인코딩 자체를 건너뜁니다.
- 위쪽 navbar 왼쪽에서 영상 이름 수정, 오른쪽 위 "저장" 으로 트림 적용. 원본은 항상 보존, 편집본은 `edited.<ext>` 로 따로 저장. 다시 편집해도 원본에서 시작합니다.
## 성능 메모
- 60fps/FHD 변환에 호스트 환경에서 사용 가능한 하드웨어 H.264 인코더를 자동 감지해 우선 사용합니다 (NVENC > QSV > VideoToolbox > VAAPI > libx264 ultrafast). 1프레임 lavfi 테스트로 실제 동작을 확인한 뒤 캐시합니다.
- 1프레임 테스트는 통과했지만 실파일 인코딩에서 깨지는 HW 인코더가 있어, 그 잡 한 건에 한해 libx264 ultrafast 로 자동 재시도합니다 (캐시는 그대로 둠 — 다음 잡 입력이 더 일반적이면 HW 가 다시 돕니다).
- 플레이리스트 임포트는 한 번에 너무 많은 yt-dlp 프로세스가 떠 디스크/CPU 를 죽이지 않도록 `MAX_DOWNLOAD_CONCURRENCY` (기본 2) 로 제한된 풀에서 순차로 시작됩니다. 대기 항목은 `queued` 상태로 큐에서 대기.