import { addStudioJob, kickStudioQueue, listStudioJobs, videoJobsBusy } from '~/server/utils/studioQueue' import { comfyConfigured } from '~/server/utils/comfy' import { clampLyricsStrength, clampMusicCfg, clampMusicDuration, clampMusicSteps, MUSIC_CFG_DEFAULT, MUSIC_CFG_DEFAULT_15, MUSIC_STEPS_DEFAULT, MUSIC_STEPS_DEFAULT_15, parseMusicEngine, parseMusicSeed, sanitizeMusicLyrics, sanitizeMusicTags } from '~/utils/music' export default defineEventHandler(async (event) => { const body = await readBody<{ tags?: unknown lyrics?: unknown duration?: unknown steps?: unknown seed?: unknown cfg?: unknown lyricsStrength?: unknown instrumental?: unknown folderId?: unknown name?: unknown engine?: unknown sampler_name?: unknown scheduler?: unknown }>(event).catch(() => ({} as Record)) if (!comfyConfigured()) { throw createError({ statusCode: 503, statusMessage: 'Beast ComfyUI is not configured. Set COMFY_HOST.' }) } const instrumental = body.instrumental === true || body.instrumental === 'true' const tags = sanitizeMusicTags(body.tags) const lyrics = sanitizeMusicLyrics(body.lyrics, instrumental) if (!tags) { throw createError({ statusCode: 400, statusMessage: 'Genre and style tags are required' }) } if (!instrumental && !lyrics) { throw createError({ statusCode: 400, statusMessage: 'Lyrics are required unless Instrumental is on' }) } 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 engine = parseMusicEngine(body.engine) const duration = clampMusicDuration(body.duration) const steps = clampMusicSteps( body.steps, engine === 'ace-step-1.5' ? MUSIC_STEPS_DEFAULT_15 : MUSIC_STEPS_DEFAULT ) const cfg = clampMusicCfg( body.cfg, engine === 'ace-step-1.5' ? MUSIC_CFG_DEFAULT_15 : MUSIC_CFG_DEFAULT ) const lyricsStrength = clampLyricsStrength(body.lyricsStrength) const seed = parseMusicSeed(body.seed) const clipName = allocateOutputName(ownerKey, folderId, String(body.name || '').trim(), { engine: engine === 'ace-step-1.5' ? 'ace15' : engine === 'yue' ? 'yue' : 'ace' }) const samplerName = String(body.sampler_name || 'euler') const scheduler = String(body.scheduler || 'simple') 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, turbo: false, seed, cfg, fps: 0, samplerName, scheduler, duration, sound: !instrumental, workflow: 'v1', useIdentityRefs: false, hideThumbnail: false, folderLocked, referenceStillIds: [null, null, null, null], extensions: [], queueAutoRun: false, lyrics, instrumental, lyricsStrength, musicEngine: engine } }) 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, steps, cfg, folderId, folderLocked, engine, name: clipName } })