Claude (owner) 83b391c0a8 build: add Dockerfile that compiles better-sqlite3 in build stage
도커 빌드가 `RUN npm install` 에서 better-sqlite3 네이티브 빌드 실패로
죽던 문제 해결.

근본 원인:
- 베이스가 Alpine(musl) 이라 better-sqlite3 prebuilt 바이너리가 없음
  (prebuild-install: "No prebuilt binaries found ... libc=musl").
- node-gyp 풀 빌드로 폴백하는데 이미지에 make/g++/python3 가 없어
  "not found: make" 로 실패.

해결:
- 멀티스테이지 Dockerfile 추가. build 스테이지에 python3/make/g++ 를
  깔아 better-sqlite3 를 소스에서 항상 컴파일하고, `npm prune --omit=dev`
  로 정리한 node_modules(컴파일된 .node 포함)를 runtime 스테이지로 복사.
  runtime 엔 빌드 체인이 없어 가볍고, prebuilt 존재 여부에 의존하지 않음.
- glibc(debian-slim) 베이스라 musl 갭 자체를 회피. 번들 yt-dlp 표준
  바이너리도 glibc 에서 동작.
- runtime 에 ffmpeg/ffprobe(트림·60fps·FHD 변환) + yt-dlp(릴리스 바이너리)
  설치. data 를 VOLUME 으로 노출, 3000 포트 EXPOSE.
- .dockerignore 로 node_modules/dist/data/.git/.env 등 컨텍스트 제외.

검증 (로컬 docker 29.4.1):
- `docker build` 성공 (better-sqlite3 컴파일 통과)
- 컨테이너 기동 → `GET /` HTTP 200
- ffmpeg 5.1.9 / ffprobe 5.1.9 / yt-dlp 2026.03.17 / better-sqlite3
  in-memory 쿼리 모두 정상

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

비디오 사이트

영상 업로드/관리/공개 재생을 다 한 곳에서 처리하는 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.jsonpassword 값 (초기값 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 상태로 큐에서 대기.
Description
No description provided
Readme 909 KiB
Languages
TypeScript 47.9%
JavaScript 34.3%
EJS 10%
CSS 7.1%
Dockerfile 0.7%