Files
2026-09-15 18:16:45 -05:00

113 lines
3.2 KiB
TypeScript

import { yue2Configured } from '~/server/utils/yue2'
import { addStudioJob, kickStudioQueue, listStudioJobs, videoJobsBusy } from '~/server/utils/studioQueue'
import {
clampMusicDuration,
parseMusicEngine,
parseMusicSeed,
sanitizeMusicLyrics,
sanitizeMusicTags,
yueLyricsProblem
} from '~/utils/music'
export default defineEventHandler(async (event) => {
const body = await readBody<{
tags?: unknown
lyrics?: unknown
duration?: unknown
seed?: unknown
folderId?: unknown
name?: unknown
engine?: unknown
}>(event).catch(() => ({} as Record<string, unknown>))
const engine = parseMusicEngine(body.engine)
if (engine !== 'yue2') {
throw createError({ statusCode: 400, statusMessage: 'Only YuE2 is supported for music generation.' })
}
if (!yue2Configured()) {
throw createError({ statusCode: 503, statusMessage: 'YuE2 host is not configured. Set COMFY_CONTROL_URL.' })
}
const tags = sanitizeMusicTags(body.tags)
const lyrics = sanitizeMusicLyrics(body.lyrics, false)
if (!tags) {
throw createError({ statusCode: 400, statusMessage: 'Genre and style tags are required' })
}
const lyricsProblem = yueLyricsProblem(lyrics)
if (lyricsProblem) {
throw createError({ statusCode: 400, statusMessage: lyricsProblem })
}
const ownerKey = libraryOwnerKey(event)
const library = publicLibrary(event)
const folderId = library.folders.some(folder => folder.id === String(body.folderId || ''))
? String(body.folderId)
: library.folders[0]?.id
if (!folderId) {
throw createError({ statusCode: 400, statusMessage: 'Create a library folder before generating' })
}
assertFolderExists(event, folderId)
const destFolder = library.folders.find(folder => folder.id === folderId)
const folderLocked = Boolean(destFolder?.protected && !destFolder.unlocked)
const duration = clampMusicDuration(body.duration)
const seed = parseMusicSeed(body.seed)
const clipName = allocateOutputName(ownerKey, folderId, String(body.name || '').trim(), {
engine: 'yue2'
})
const familyId = crypto.randomUUID()
const studio = await addStudioJob({
ownerKey,
familyId,
kind: 'music',
payload: {
prompt: tags,
name: clipName,
folderId,
aspect: 'audio',
width: 0,
height: 0,
steps: 0,
turbo: false,
seed,
cfg: 0,
fps: 0,
samplerName: 'euler',
scheduler: 'simple',
duration,
sound: true,
workflow: 'v1',
useIdentityRefs: false,
hideThumbnail: false,
folderLocked,
referenceStillIds: [null, null, null, null],
extensions: [],
queueAutoRun: false,
lyrics,
instrumental: false,
lyricsStrength: 0,
musicEngine: 'yue2'
}
})
await kickStudioQueue()
let latest = listStudioJobs(ownerKey).find(item => item.id === studio.id)
if (!latest?.liveJobId && !(await videoJobsBusy())) {
await kickStudioQueue()
latest = listStudioJobs(ownerKey).find(item => item.id === studio.id)
}
const liveJobId = latest?.liveJobId || ''
return {
jobId: liveJobId || studio.id,
studioJobId: studio.id,
queued: !liveJobId,
seed,
duration,
folderId,
folderLocked,
engine: 'yue2' as const,
name: clipName
}
})