Commit Graph

25 Commits

Author SHA1 Message Date
Claude (owner)
042d9f7059 feat: 플레이리스트 추가 화면 임시저장(draft) 기능
다운로드 시작 전, 수정해둔 값(플레이리스트 URL + 항목별 제목/ID/시작/길이/
ID모드/자릿수채우기)을 폴더당 1개 저장하고 나중에 불러올 수 있게 함.

- db: playlist_drafts 테이블 (folder_id PK, JSON data, updated_at)
- storeDb: savePlaylistDraft/getPlaylistDraft/deletePlaylistDraft
- op: GET/POST/POST(delete) /playlist/draft (하위 폴더 한정)
- UI: 임시저장 / 불러오기 / 삭제 버튼 + 진입 시 기존 draft 자동 감지

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
2026-06-06 16:22:42 +09:00
Claude (owner)
0ec6b06b57 feat: 영상 ID 를 폴더 안에서만 유일하게 (UNIQUE(folder_id, id))
폴더가 다르면 같은 영상 ID(예: 1,2,3,4)를 재사용할 수 있도록 변경.
ID 가 더 이상 전역 PK 가 아니므로 모든 영상 조회를 폴더 스코프로 전환:
- db: videos PK→UNIQUE(folder_id,id) + 기존 DB 자동 마이그레이션
- storeDb: getVideo→getVideoInFolder(folderId,id) + getVideoByIdGlobal 폴백
- public/op/editor/youtube: 재생·썸네일·편집·삭제·rename 모두 폴더 스코프
- 썸네일을 경로기반 URL(/file/video/<폴더>/<id>/thumb)로 전환
- 클라이언트 mutation 요청에 folderId 동봉

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
2026-06-06 16:19:02 +09:00
Claude (owner)
8ed3b7d82a feat(tools): 관리자 화면에서 ffmpeg/yt-dlp 최신 버전 재설치
오래된 ffmpeg/yt-dlp 로 다운로드·변환이 실패할 때 관리자 대시보드에서
최신 버전으로 재설치할 수 있게 한다. 재설치 바이너리는 data 볼륨 하위
data/bin 에 두어 컨테이너 재생성에도 유지하고, 바이너리 해석 시 PATH 보다
우선 사용한다.

- src/paths.ts: binDir(data/bin) 추가
- src/editor.ts: binDir 우선 ffmpeg 해석 + getFfprobePath/resetFfmpegResolution
- src/youtube.ts: binDir 우선 yt-dlp 해석 + resetYtDlpResolution
- src/tools.ts(신규): 버전 조회 + 최신 재설치(GitHub yt-dlp, johnvansickle ffmpeg)
- src/routes/op.ts: GET /op/tools, POST /op/tools/:name/update
- views/op/dashboard.ejs, public/dashboard.js, public/styles.css: 도구 관리 UI
- Dockerfile: ffmpeg 정적 빌드(.tar.xz) 해제용 xz-utils 추가

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
2026-06-05 16:02:25 +09:00
Claude (owner)
f9a26aaa86 feat(playlist): show download progress page after import instead of navigating away
플레이리스트 등록 후 폴더로 바로 나가지 않고, 각 다운로드 잡을 폴링하며
항목별/전체 진행률을 보여주는 진행 화면을 추가. 단일 영상 추가 흐름과 동일하게
/op/job/:id 를 폴링하고, 모두 끝나면 "폴더로 이동" 버튼을 노출한다.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
2026-06-05 02:26:05 +09:00
Claude (owner)
e9924805cf fix(playlist): make trim modal numeric input length-based not end-time
Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
2026-06-03 21:35:35 +09:00
Claude (owner)
2f916fe958 feat(playlist): replace end-time inputs with play-length (default 15s)
Per-entry and bulk trim inputs now take a play length in seconds
(start + length) instead of an end time. Default 15s, so start=30
produces a 30–45s clip. Converted to endSec at register time; the
trim editor maps its end handle to a length on apply. Backend
unchanged (still works in start/end terms).

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
2026-06-03 21:29:58 +09:00
Claude (owner)
7105349a45 feat(stream): canonical folder-path video file URL
영상 파일 스트림 정규 주소를 재생 페이지와 동일한 폴더 경로 규칙으로 변경:
  /api/video/:topName[/:subName]/:videoId
경로의 폴더 부분이 영상 위치와 일치할 때만 200, 불일치 시 404.
재생 페이지(/video/...)와 ID 기반 레거시 스트림(/file/video/:id,
/api/video/:id/file)은 그대로 유지(외부 공유 호환).

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
2026-06-02 01:07:20 +09:00
Claude (owner)
34c040c15d feat(playlist): per-entry/bulk trim, preview, and trim editor on import
플레이리스트 가져오기 화면에 다음을 추가:
- 항목별 제목 입력을 절반으로 줄이고 오른쪽에 시작/종료 시간 입력 추가
  (기본 시작 0, 종료는 영상 길이)
- 정렬 옵션 옆에 일괄 시작/종료 입력과 적용/초기화 버튼 추가
  (초기화 = 시작 0, 종료 = 영상 길이)
- 항목 썸네일 클릭 시 YouTube 임베드로 미리보기 재생
- 항목 우클릭 → 자르기 메뉴 → /video/editor 식 타임라인 모달(YouTube IFrame)
  에서 구간을 정하면 항목의 시작/종료 입력에 반영
- 백엔드: playlist/start 가 항목별 startSec/endSec 를 받아 다운로드 잡에
  실어 보내고, 60fps 후처리 후 ffmpeg 로 해당 구간을 잘라 편집본 생성

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
2026-06-02 00:50:04 +09:00
Claude (owner)
066ae6b112 feat(thumbnails): generate and serve video thumbnails for folder cards
ffmpeg로 영상 첫 프레임을 즉석 추출해 thumb.jpg로 캐시하고
공개/관리자 폴더 카드에서 실제 썸네일을 표시한다. ffmpeg가 없거나
생성 불가하면 404로 떨어져 ▶ placeholder로 폴백한다.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
2026-06-02 00:41:06 +09:00
Claude (owner)
210787131b 관리자 폴더 카드 전체 영역 클릭 시 폴더 진입
내부 링크(.folderCardLink)에만 적용되던 클릭 영역을 카드 전체로 확장.
패딩을 카드 div 에서 링크로 옮기고 flex:1 로 카드를 가득 채워, 폴더
아이콘/이름 옆 배경을 눌러도 진입되도록 함. 공개 화면 카드는 자체가
<a> 라 :not(.adminFolder) 패딩 규칙으로 기존 동작 유지.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
2026-06-01 23:01:16 +09:00
Claude (owner)
0faf7af42a feat(playlist): show thumbnails in import preview
플레이리스트 가져오기 미리보기 목록에 각 항목 썸네일을 표시한다.
YouTube video id 로 mqdefault 썸네일 URL 을 만들고, id 가 없으면
yt-dlp 가 준 thumbnail 필드로 폴백. 깨진 이미지는 빈 박스 처리.

- youtube.ts: PlaylistEntry 에 thumbnailUrl 추가 + flat-playlist 파싱에서 채움
- playlist.js: probe 매핑에 thumbnailUrl 반영, 각 행에 img 렌더(lazy + onerror)
- styles.css: 행 그리드에 썸네일 칼럼(96px 16:9) 추가

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
2026-06-01 22:52:05 +09:00
Claude (owner)
31c754a3ad feat(playlist): import UI with drag reorder + random/sequential ID toggle (STEP 5)
Adds /op/folder/:top/:sub/playlist/new page that lets the operator paste a
YouTube playlist URL, probe entries via yt-dlp, reorder them by drag, edit
title/ID per row, and choose between random IDs or zero-padded sequential
IDs before registering all jobs in one batch.
2026-06-01 00:15:39 +09:00
Claude (owner)
55ce30733b feat(videos): inline ID edit with live availability check
영상 편집 페이지에 영상 ID 입력란을 추가했다. 입력 시 250ms 디바운스로
/op/videos/idAvailable 를 호출해 중복/형식을 실시간 확인하고, 변경
버튼으로 /op/videos/:id/changeId 를 호출한 뒤 새 ID 의 편집 페이지로
location.replace 한다. 공유 주소 복사 버튼도 함께 노출한다.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 23:51:18 +09:00
Claude
c5faa8808c feat(folders): nested folder tree (max depth 2) + id-based mutations
- new storeDb.ts: DB+FS layer (folders w/ parent_id, videos w/ global id)
- depth cap: sub-folder cannot have children (enforced in createFolder)
- video id: editable anytime (changeVideoId), uniqueness via PK
- routes:
  - public /folder/:topName[/:subName], /video/:topName[/:subName]/:videoId
  - admin /op/folder/:topName[/:subName], id-based /op/folders/:id/*
    and /op/videos/:id/* (rename/changeId/delete/save + idAvailable check)
- views: breadcrumb + subFolder grid; 플레이리스트 추가 button shows at 2단계
- client JS: id-based mutation endpoints, share URL data attribute,
  in-site player pushes share URL to address bar
- store.ts slimmed to Account + readAccounts (FS layer moved to storeDb)
- pickIntId accepts numeric JSON bodies

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 23:36:02 +09:00
Claude
5728c42ab7 fix(import): ignore stale probe responses on URL change
If user changed the URL while a probe was in flight, the late response
for the old URL would still set lastProbedUrl and enable the start
button, breaking the "확인 전 가져오기 차단" UI invariant.

Drop the response (and the catch path, and the post-confirm path) when
the current input no longer matches the probed URL.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 03:14:30 +09:00
Claude
33addb304a fix(import): invalidate probe when URL input changes
Review P2: probe 성공 후 사용자가 URL 입력값을 다른 URL 로 바꿔도 "확인"
버튼이 여전히 활성화 상태였습니다. 그래서 A 로 probe → B 로 수정 → 확인을
누르면 B 가 probe 없이 바로 다운로드 시작됐습니다.

수정:

- `lastProbedUrl` 로 마지막으로 probe 통과한 URL 기록.
- ytUrl 의 input 이벤트에서 현재 값이 lastProbedUrl 과 다르면
  ytStartBtn 을 disable 로 되돌리고 ytProbeBtn 을 다시 활성화.
- ytStartBtn 클릭 핸들러에도 가드 추가: 클릭 시점에 URL ≠ lastProbedUrl
  이면 안내 메시지와 함께 차단 (race condition 대비).

이제 "확인 누르기 전 가져오기 못 누르게" 요구사항이 어느 순서로 입력이
들어와도 만족됩니다.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 03:11:00 +09:00
Claude
11db6df8d2 fix(import): unbuffer yt-dlp progress, speed up 60fps via fps filter, lock probe btn
세 가지 사용자 보고 처리:

1) 다운로드 바가 멈춰 있다가 한번에 50% 로 점프
   - 원인: yt-dlp 는 파이썬 스크립트라 stdout 이 pipe 로 연결되면
     block-buffered 가 되어 진행률 라인들이 4KB 버퍼에 모였다가 종료 직전에
     쏟아짐. node 의 stdout 'data' 핸들러가 그 전엔 아무 것도 못 봄.
   - 수정: spawn 의 env 에 PYTHONUNBUFFERED=1 추가. 라인 단위로 즉시 flush.

2) 변환을 20배 빠르게
   - 원인: minterpolate(mci) 는 motion estimation 자체가 무거워 mci 기본값
     이어도 영상 길이의 수 배 시간이 든다 (blend 도 5~10배가 한계).
   - 수정: 다운로드 후 60fps 변환과 trim 재인코딩 양쪽에서 minterpolate 를
     `fps=60` (단순 프레임 복제) 으로 교체. preset 도 `veryfast` → `ultrafast`.
     실측상 영상 길이의 1/3 ~ 1배 시간 — mci 대비 수십 배 빠름.
   - 시각적 부드러움은 30fps 와 동일하지만 컨테이너/타이밍은 60fps cfr 로 유지.
     사용자가 알려준 "timing 만 60fps 로 바뀌고 실제 부드러움은 그대로" 경로.

3) "확인" 전 "가져오기" 재클릭 막기
   - probe 클릭 시 ytProbeBtn 도 disabled.
   - probe 실패 / 5분 경고 취소 / start 실패 시만 재활성화.
   - 정상 흐름은 probe 성공 → 확인 클릭 → 다운로드 → redirect 라 재활성화
     필요 없음. ytStartBtn 도 클릭 직후 disabled 로 중복 클릭 방지.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 03:07:47 +09:00
Claude
67d4fb89b8 fix(job-poll): disable caching and recover from fetch errors
Review P1: 진행률 폴링이 304 응답을 받으면 r.json() 이 reject 되는데
.catch() 가 없어 setTimeout 도 안 걸리고 폴링이 영구 중단됐습니다.
이게 "60fps 변환 확인 중" 에서 바가 멈춰 보이던 진짜 원인이었어요.

세 곳을 다 고침:

1) src/routes/op.ts `/op/job/:id`
   - `Cache-Control: no-store, no-cache, must-revalidate` + `Pragma: no-cache`
   - 브라우저가 conditional GET 으로 304 를 받지 않게 한다.

2) public/editor.js fetch
   - `{ cache: 'no-store' }` 옵션. 서버 헤더 + 클라 옵션 둘 다.

3) public/editor.js pollJob
   - `.catch()` 추가. 일시적 네트워크/파싱 오류여도 2 초 백오프로
     폴링을 재개한다. 변환이 오래 걸려도 바가 계속 갱신됨을 보장.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 03:00:58 +09:00
59f96a12a6 feat(routes): add short /file/video/:id alias for video stream
외부 공유용으로 더 짧은 영상 파일 URL 을 제공:
  /file/video/:videoId   ← 신규 (영상 주소 복사 결과)
  /api/video/:videoId/file ← 호환용 alias 로 유지 (기존 링크 안 깨지게)
  /api/video/:videoId     ← 메타 JSON (변경 없음)

핸들러는 한 함수를 두 라우트에 공유. 우클릭 "영상 주소 복사" 가
이제 새 짧은 경로를 클립보드에 넣음.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 21:58:37 +09:00
6ee089b9b7 fix(folder): copy raw video stream URL instead of player page
영상 주소 복사 결과를 /player/:id 가 아닌 실제 스트리밍 엔드포인트
/api/video/:id/file 로 변경. 공개/관리자 양쪽 모두 적용.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 18:45:37 +09:00
51bf813ccc feat(folder): right-click "영상 주소 복사" on public and admin
- 공개 폴더(/folder/:name): 새 ctxMenu + public-folder.js 로 우클릭 메뉴 신설
  ("영상 주소 복사" 만 포함). 좌클릭 재생 기능은 그대로 유지.
- 관리자 폴더(/op/folder/:name): 기존 ctxMenu 에 "영상 주소 복사" 항목과
  folder.js 에 copyUrl 핸들러 추가.
- 양쪽 모두 navigator.clipboard.writeText(origin + "/player/" + id) 사용,
  실패하면 hidden textarea + execCommand("copy") fallback, 그것도 실패하면
  window.prompt 으로 직접 복사 안내. 성공 시 flashToast 로 피드백.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 18:42:49 +09:00
db6fe6224d fix(editor): handle loadedmetadata race so timeline shows duration
비디오의 src 가 HTML 인라인이라 loadedmetadata 가 스크립트 실행보다
먼저 끝나는 경우, 늦게 붙은 리스너가 영원히 못 받아 duration=0 으로
멈춰있던 문제 수정. readyState 즉시 검사 + durationchange 도 같이
구독하도록 변경.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 18:35:10 +09:00
f587dce5ce feat(editor): timeline scrubber with draggable trim handles
영상편집기에 lossless-cut 류 트림 타임라인 추가. 숫자 입력만 있던 기존 UI 를
시각적 재생바 + 좌/우 드래그 핸들 + 플레이헤드로 교체.

- 좌/우 파란 핸들을 끌어 in/out 점 설정 (pointer events 기반, 터치 지원)
- 흰색 플레이헤드가 영상 재생 위치 따라감
- 타임라인 빈 공간 클릭 → 그 지점으로 시킹
- "[ 시작점" / "끝점 ]" 버튼으로 현재 시점 마크
- "선택 재생" 으로 선택구간만 미리보기, "초기화" 로 전체 선택 복원
- 기존 숫자 입력은 보조 입력으로 유지하고 상태와 양방향 동기화

저장 페이로드는 그대로 (startSec/endSec). 서버측 ffmpeg 트림 로직 변경 없음.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 18:31:07 +09:00
bb116f5c24 fix: hide modal/player overlays when hidden attr is set
.modalOverlay { display: flex } and .playerOverlay { display: flex }
were overriding the browser default [hidden] { display: none }, so the
"폴더 추가" modal stayed visible on /op/dashboard load and blocked all
other UI. Added a single [hidden] { display: none !important } rule so
the hidden attribute always wins regardless of later display rules.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 17:05:27 +09:00
0db04cf5cd feat: implement video site per README spec
- Express + EJS + express-session stack (auth/navbar ported from minecraft_launcher)
- Public: main folder list, folder video grid, internal popup player (/player/:videoId)
- Admin (/op): login, folder CRUD with right-click context menu + add-folder modal
- Admin folder: video grid with right-click edit/rename/delete, "영상 추가" -> editor
- Video editor: drag-drop upload, file picker, YouTube URL probe (ETA + 5분 경고),
  background yt-dlp download with progress polling, navbar title edit, trim controls,
  save runs ffmpeg trim (original preserved)
- Filesystem storage under data/folders/<name>/<videoId>/{meta.json, original.<ext>, edited.<ext>}

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 16:42:00 +09:00