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

@@ -1,7 +1,7 @@
import { spawn, spawnSync } from 'node:child_process'
import { promises as fs, existsSync } from 'node:fs'
import path from 'node:path'
import { getVideo, updateVideo, videoDiskDir, type VideoTrim } from './storeDb.js'
import { getVideoInFolder, updateVideo, videoDiskDir, type VideoTrim } from './storeDb.js'
import { binDir } from './paths.js'
export class FfmpegUnavailableError extends Error {
@@ -408,13 +408,14 @@ export async function upscaleOriginalTo60Fps(
* stream copy 를 우선 시도해 빠르게 자르고, 실패하면 재인코딩.
*/
export async function applyTrimToVideo(
folderId: number,
videoId: string,
trim: VideoTrim
): Promise<string> {
const bin = getFfmpegPath()
const meta = getVideo(videoId)
const meta = getVideoInFolder(folderId, videoId)
if (!meta) throw new Error('비디오를 찾을 수 없습니다.')
const dir = videoDiskDir(videoId)
const dir = videoDiskDir(folderId, videoId)
const inputPath = path.join(dir, meta.originalFile)
await fs.access(inputPath)
@@ -476,7 +477,7 @@ export async function applyTrimToVideo(
}
await fs.rename(tmpPath, outPath)
updateVideo(videoId, {
updateVideo(folderId, videoId, {
editedFile: outName,
trim: { startSec, endSec }
})
@@ -496,10 +497,10 @@ const THUMB_NAME = 'thumb.jpg'
* 카드에서는 ▶ placeholder 로 폴백.
* 한 번 만들면 파일로 캐시되어 다음 요청부터는 즉시 응답.
*/
export async function ensureThumbnail(videoId: string): Promise<string | null> {
const meta = getVideo(videoId)
export async function ensureThumbnail(folderId: number, videoId: string): Promise<string | null> {
const meta = getVideoInFolder(folderId, videoId)
if (!meta) return null
const dir = videoDiskDir(videoId)
const dir = videoDiskDir(folderId, videoId)
const thumbPath = path.join(dir, THUMB_NAME)
try {
await fs.access(thumbPath)