- routes/op.ts: editor / upload / youtube-start reject when folder.parentId === null
- op/folder.ejs: at depth 1 show only "하위 폴더 추가"; at depth 2 show
"영상 추가" + "플레이리스트 추가". Video grid and sub-folder modal
rendered conditionally.
- folder.ejs (public): at depth 1 render only sub-folder grid; at depth 2
render only video grid.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
외부 공유용으로 더 짧은 영상 파일 URL 을 제공:
/file/video/:videoId ← 신규 (영상 주소 복사 결과)
/api/video/:videoId/file ← 호환용 alias 로 유지 (기존 링크 안 깨지게)
/api/video/:videoId ← 메타 JSON (변경 없음)
핸들러는 한 함수를 두 라우트에 공유. 우클릭 "영상 주소 복사" 가
이제 새 짧은 경로를 클립보드에 넣음.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
리뷰어 지적사항 반영:
- "abc" 같은 오타도 Infinity 로 풀리던 문제 수정. 잘못된 값은 기본 1 GiB 로
fallback 하고 경고 로그를 남김. 무제한은 "0" 또는 "Infinity" 만 명시적으로 인정.
- .env.example / README 의 "비우면 무제한" 표현을 코드 동작과 일치시켜
"비우면 기본 1 GiB" 로 정정.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
기본 업로드 한도를 1 GiB (1073741824 바이트) 로 설정. .env 의 UPLOAD_MAX_BYTES 로
바꿀 수 있고, 0 이나 Infinity 로 두면 무제한.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
scripts/setup.mjs runs `npm install`, downloads the platform-specific
yt-dlp binary from GitHub releases to ./bin/yt-dlp (which src/youtube.ts
already prefers), checks for ffmpeg and prints install hints, then runs
`tsc`. One command replaces three for fresh checkouts.
While verifying setup, hit `MulterError: File too large` (LIMIT_FILE_SIZE)
on a 10 GB mkv upload, and ETXTBSY on freshly downloaded yt-dlp.
- ETXTBSY: the redirect path in downloadFile opened a writestream to the
destination before following the redirect, so the (unused) outer stream
still held the file open when the post-download spawnSync ran. Split
redirect-following from file writing so only the final 200 response
opens the destination file.
- LIMIT_FILE_SIZE: removed the hard-coded 4 GB cap. Upload limit now
defaults to Infinity and is configurable via UPLOAD_MAX_BYTES.
Wrapped multer's middleware so its errors (LIMIT_FILE_SIZE etc.) come
back as a clean 413 JSON instead of a stack trace from the global
error handler.
- Also disabled Node's default 5 minute requestTimeout so 10 GB uploads
over slow links don't get cut mid-stream. Configurable via
HTTP_REQUEST_TIMEOUT_MS.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
P1: views were emitting <%- JSON.stringify(...) %> directly inside <script>
tags. A video title like "</script><script>alert(1)</script>" would break
out of the script and inject HTML. Added res.locals.jsonForScript() that
escapes <, >, &, U+2028, U+2029 before output and switched all three
templates (op/editor.ejs, op/folder.ejs, folder.ejs) to use it.
P2: The internal popup player in /folder/:name always hit
/api/video/:id/file which returned the original. Made the file endpoint
default to the edited variant when present and only fall back to original
when ?edited=0 is given. Editor page passes ?edited=0 explicitly so the
operator always re-trims from the original. Standalone /player/:id no
longer needs the ?edited=1 hint.
Verified: rendered editor HTML escapes </script> payloads to \u003c/script,
default file endpoint serves edited.mp4 while ?edited=0 serves original.mp4.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>