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>
This commit is contained in:
Claude (owner)
2026-06-06 16:19:02 +09:00
parent 66469ca418
commit 0ec6b06b57
11 changed files with 232 additions and 106 deletions

View File

@@ -6,7 +6,7 @@ import { upscaleOriginalTo60Fps, applyTrimToVideo } from './editor.js'
import {
createVideo,
getFolder,
getVideo,
getVideoInFolder,
updateVideo,
videoDiskDir,
type VideoTrim
@@ -343,7 +343,7 @@ async function runJob(job: DownloadJob, bin: string): Promise<void> {
job.message = '다운로드 시작'
await persistJob(job)
const dir = videoDiskDir(job.videoId)
const dir = videoDiskDir(job.folderId, job.videoId)
// yt-dlp 가 다운로드 후 결정한 실제 파일명을 알려주도록 --print after_move:filepath
// 진행률은 --newline + --progress-template 으로 stdout 줄 단위 파싱.
const args = [
@@ -498,9 +498,9 @@ async function runJob(job: DownloadJob, bin: string): Promise<void> {
console.error('[youtube] 60fps 후처리 실패:', err)
}
const meta = getVideo(job.videoId)
const meta = getVideoInFolder(job.folderId, job.videoId)
if (meta) {
updateVideo(job.videoId, { originalFile: finalName })
updateVideo(job.folderId, job.videoId, { originalFile: finalName })
}
// 사용자가 지정한 자르기 구간이 있으면 다운로드/60fps 후처리가 끝난 원본에
@@ -510,7 +510,7 @@ async function runJob(job: DownloadJob, bin: string): Promise<void> {
try {
job.message = '자르기 적용 중'
await persistJob(job)
await applyTrimToVideo(job.videoId, job.trim)
await applyTrimToVideo(job.folderId, job.videoId, job.trim)
job.message = '자르기 완료'
} catch (err) {
console.error('[youtube] 자르기 적용 실패:', err)