simplify skill 결과 정리: - editor.ts: HW→libx264 잡 폴백 패턴을 encodeWithFallback() 한 곳으로 모으고 upscale/trim 양쪽에서 호출. 진행률 무전송 변형은 runner 인자로 분기. - editor.ts: upscaleOriginalTo60Fps 가 fps/해상도/길이를 위해 spawnSync ffprobe 를 3번 돌리던 것을 probeVideoMeta() 한 번 호출로 통합. 핫패스에서 이벤트 루프 블로킹을 줄임. 이제 미사용이 된 probeVideoResolution/ probeVideoDuration 은 제거. - routes/helpers.ts: op.ts/public.ts 양쪽에 동일하게 있던 collectFolderSegments 를 공용 모듈로 추출. - youtube.ts: probeYoutube/probeYoutubePlaylist 의 stderr 누적을 runFfmpeg 와 동일하게 마지막 2KB 만 유지하도록 캡. README.md 도 5/31 STEP 1~5 (중첩 폴더 + 영상 ID 편집 + 플레이리스트 임포트 + 짧은 공유 URL) 과 5/16 묶음(부드러운 진행바 + 60fps/FHD 캡 + HW 인코더 자동 감지) 까지 반영해 전반 갱신. `npm run build` 통과. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
7.9 KiB
7.9 KiB
비디오 사이트
영상 업로드/관리/공개 재생을 다 한 곳에서 처리하는 Express + EJS + better-sqlite3 사이트입니다. 컴퓨터 파일이나 YouTube 링크(단일 영상/플레이리스트) 를 가져와 폴더 단위로 정리하고, 짧은 공유 URL로 외부에 노출합니다.
실행
처음 한 번은 setup 으로 의존성 + yt-dlp 바이너리 + 빌드까지 한 번에 끝낼 수 있어요:
cp .env.example .env # 포트/호스트 등 환경변수 편집
npm run setup # npm install + ./bin/yt-dlp 다운로드 + tsc
npm start # .env 의 HOST:PORT 로 바인딩
수동으로 단계별로 하고 싶다면:
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 (폴더 트리, 영상 메타, 잡 큐, 세션)
videos/<videoId>/ # 영상 파일 (id 기준 평면 디렉터리)
original.<ext> # 원본 (항상 보존)
edited.<ext> # 트림 저장 시 생성
uploads/ # 업로드 임시 (multer)
tmp/ # 작업 임시
폴더 트리 / 영상 메타는 모두 app.db 에서 관리합니다. 디스크 상 영상 파일은 영상 ID 기준 평면 구조라 폴더 이름을 바꿔도 파일 이동이 일어나지 않습니다.
URL 규칙
공개:
/— 최상위 폴더 목록/folder/:topName— 최상위 폴더 (서브폴더 + 영상 혼합)/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])
- 최상위 폴더: 서브폴더와 영상이 섞여 나옵니다. 서브폴더는 카드, 영상은 썸네일 + 제목.
- 서브폴더(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 을 넣으면 항목을 미리 보여주고 시작할 수 있습니다.
- 항목별 드래그 정렬, 제목/ID 인라인 편집.
- ID 정책 토글: 랜덤 (8자 base32 자동) / 시퀀셜 (원래 순서 기준 1-N, zero-pad 옵션).
- "가져오기 시작" 누르면 각 항목이 큐에 들어가고 백그라운드에서 다운로드 + 후처리.
영상추가 화면 (/op/folder/.../video/editor)
- 입력: 컴퓨터 파일 드래그&드롭, 파일 선택 다이얼로그, YouTube URL.
- YouTube URL 의 경우:
- "확인" 버튼으로
probe(title / duration / 예상 파일 크기). probe 가 끝나야 "가져오기" 버튼이 활성화됩니다 (probe 전에 가져오기 누르는 것 자체가 막힘). - 예상 소요시간이 5분 이상으로 추정되면 경고를 띄움.
- 백그라운드 잡이 시작되고, 다른 페이지를 만지더라도 진행 상황이 계속 업데이트됩니다.
- "확인" 버튼으로
- 진행률 바: 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상태로 큐에서 대기.