290 lines
8.5 KiB
TypeScript
290 lines
8.5 KiB
TypeScript
import { persistLoraFields, readLoraStack, type LoraStackItem } from '~/utils/loras'
|
|
import {
|
|
clampMusicDuration,
|
|
clampMusicSteps,
|
|
sanitizeMusicLyrics,
|
|
sanitizeMusicTags
|
|
} from '~/utils/music'
|
|
|
|
export const LOAD_QUEUED_JOB_KEY = 'aigen-load-queued-job'
|
|
export const LOAD_MUSIC_JOB_KEY = 'aigen-load-music-job'
|
|
|
|
export type QueuedInspectKind = 'video' | 'edit' | 'music'
|
|
export type QueuedInspectSource = 'studio' | 'live'
|
|
|
|
export type QueuedShotDraft = {
|
|
prompt: string
|
|
duration: number
|
|
loraName?: string
|
|
loraStack?: LoraStackItem[]
|
|
}
|
|
|
|
export type QueuedInspectPayload = {
|
|
prompt?: string
|
|
promptMid?: string
|
|
promptPre?: string
|
|
promptPost?: string
|
|
name?: string
|
|
folderId?: string
|
|
aspect?: string
|
|
width?: number
|
|
height?: number
|
|
steps?: number
|
|
turbo?: boolean
|
|
seed?: number
|
|
cfg?: number
|
|
fps?: number
|
|
samplerName?: string
|
|
scheduler?: string
|
|
duration?: number
|
|
sound?: boolean
|
|
workflow?: string
|
|
stillId?: string
|
|
stillFilename?: string
|
|
hideThumbnail?: boolean
|
|
folderLocked?: boolean
|
|
loraName?: string
|
|
loraStack?: LoraStackItem[]
|
|
negative?: string
|
|
passes?: { prompt: string }[]
|
|
imagePipeline?: 'v1' | 'v2'
|
|
v2Mode?: string
|
|
v2Task?: string
|
|
engine?: string
|
|
snofsModel?: number
|
|
snofsClip?: number
|
|
consistencyModel?: number
|
|
consistencyClip?: number
|
|
refineStrength?: number
|
|
scaleToTotalPixels?: boolean
|
|
scaleMegapixels?: number
|
|
extensions?: QueuedShotDraft[]
|
|
tags?: string
|
|
lyrics?: string
|
|
instrumental?: boolean
|
|
lyricsStrength?: number
|
|
}
|
|
|
|
export type QueuedInspectJob = {
|
|
source: QueuedInspectSource
|
|
id: string
|
|
status: string
|
|
kind: QueuedInspectKind
|
|
name: string
|
|
prompt: string
|
|
shotCount: number
|
|
liveJobId?: string
|
|
shotQueueId?: string
|
|
lastError?: string
|
|
imagePipeline?: string
|
|
payload: QueuedInspectPayload
|
|
}
|
|
|
|
export function queuedJobKindLabel(job: { kind?: string; imagePipeline?: string; shotCount?: number }) {
|
|
if (job.kind === 'music') return 'Music'
|
|
if (job.kind === 'edit') {
|
|
if (job.imagePipeline === 'v2') return 'Image v2'
|
|
return (job.shotCount || 1) > 1 ? 'Image edit' : 'Image'
|
|
}
|
|
return 'Video'
|
|
}
|
|
|
|
export function queuedJobEditable(job: { source?: string; status?: string }) {
|
|
return job.source === 'studio' && (job.status === 'waiting' || job.status === 'held' || job.status === 'error')
|
|
}
|
|
|
|
export function queuedJobShots(payload: QueuedInspectPayload, kind: string): QueuedShotDraft[] {
|
|
if (kind === 'edit') {
|
|
const first = { prompt: payload.prompt || '', duration: 0 }
|
|
const rest = (payload.passes || []).map(item => ({ prompt: item.prompt || '', duration: 0 }))
|
|
return rest.length ? [first, ...rest] : [first]
|
|
}
|
|
if (kind === 'music') return []
|
|
const first = {
|
|
prompt: payload.prompt || '',
|
|
duration: Number(payload.duration) || 5,
|
|
loraName: payload.loraName,
|
|
loraStack: payload.loraStack
|
|
}
|
|
const rest = (payload.extensions || []).map(item => ({
|
|
prompt: item.prompt || '',
|
|
duration: Number(item.duration) || 5,
|
|
loraName: item.loraName,
|
|
loraStack: item.loraStack
|
|
}))
|
|
return [first, ...rest]
|
|
}
|
|
|
|
export function applyQueuedShots(kind: string, shots: QueuedShotDraft[], payload: QueuedInspectPayload) {
|
|
const next = { ...payload }
|
|
if (kind === 'music') {
|
|
next.prompt = payload.tags || payload.prompt || ''
|
|
return next
|
|
}
|
|
if (kind === 'edit') {
|
|
next.prompt = shots[0]?.prompt || ''
|
|
next.passes = shots.slice(1).map(item => ({ prompt: item.prompt || '' }))
|
|
return next
|
|
}
|
|
next.prompt = shots[0]?.prompt || ''
|
|
next.duration = Number(shots[0]?.duration) || next.duration || 5
|
|
next.extensions = shots.slice(1).map((item) => {
|
|
const fields = persistLoraFields(item.loraStack || item.loraName)
|
|
return {
|
|
prompt: item.prompt || '',
|
|
duration: Number(item.duration) || 5,
|
|
loraName: fields.loraName,
|
|
loraStack: fields.loraStack
|
|
}
|
|
})
|
|
return next
|
|
}
|
|
|
|
function line(label: string, value: unknown) {
|
|
const text = String(value ?? '').trim()
|
|
if (!text) return ''
|
|
return `${label}: ${text}`
|
|
}
|
|
|
|
export function queuedJobCopyText(job: QueuedInspectJob) {
|
|
const payload = job.payload || {}
|
|
const kind = queuedJobKindLabel(job)
|
|
const lines = [
|
|
line('Kind', kind),
|
|
line('Name', job.name || payload.name),
|
|
line('Status', job.status),
|
|
job.lastError ? line('Error', job.lastError) : '',
|
|
line('Workflow', payload.workflow),
|
|
line('Mode', payload.v2Mode),
|
|
line('Engine', payload.engine),
|
|
line('Task', payload.v2Task),
|
|
line('Aspect', payload.aspect),
|
|
payload.duration != null ? line('Duration', `${payload.duration}s`) : '',
|
|
payload.steps != null ? line('Steps', payload.steps) : '',
|
|
payload.cfg != null ? line('CFG', payload.cfg) : '',
|
|
payload.fps != null ? line('FPS', payload.fps) : '',
|
|
payload.seed != null ? line('Seed', payload.seed) : '',
|
|
payload.turbo != null ? line('Turbo', payload.turbo ? 'yes' : 'no') : '',
|
|
payload.sound != null && job.kind !== 'music' ? line('Sound', payload.sound ? 'on' : 'off') : '',
|
|
line('Sampler', payload.samplerName),
|
|
line('Scheduler', payload.scheduler),
|
|
line('Negative', payload.negative)
|
|
].filter(Boolean)
|
|
|
|
const stack = readLoraStack(payload)
|
|
if (stack.length) {
|
|
lines.push(`LoRAs: ${stack.map(item => `${item.name} ${item.strengthModel}/${item.strengthClip}`).join(', ')}`)
|
|
}
|
|
|
|
if (job.kind === 'music') {
|
|
lines.push(line('Tags', payload.tags || job.prompt))
|
|
if (payload.instrumental) lines.push('Instrumental: yes')
|
|
else lines.push(line('Lyrics', payload.lyrics))
|
|
} else {
|
|
if (payload.promptPre) lines.push(line('Prompt pre', payload.promptPre))
|
|
lines.push(line('Prompt', payload.prompt || job.prompt))
|
|
if (payload.promptPost) lines.push(line('Prompt post', payload.promptPost))
|
|
const shots = queuedJobShots(payload, job.kind)
|
|
if (shots.length > 1) {
|
|
lines.push('')
|
|
shots.forEach((shot, index) => {
|
|
const label = job.kind === 'edit' ? `Pass ${index + 1}` : `Shot ${index + 1}`
|
|
lines.push(`${label} (${shot.duration || 0}s): ${shot.prompt}`)
|
|
})
|
|
}
|
|
}
|
|
|
|
return lines.filter(Boolean).join('\n')
|
|
}
|
|
|
|
export function queuedJobPresetKind(job: { kind?: string; imagePipeline?: string }): 'video' | 'image' | 'imagev2' | 'music' {
|
|
if (job.kind === 'music') return 'music'
|
|
if (job.kind === 'edit') return job.imagePipeline === 'v2' ? 'imagev2' : 'image'
|
|
return 'video'
|
|
}
|
|
|
|
export function queuedJobPresetBody(job: QueuedInspectJob, name: string) {
|
|
const payload = job.payload || {}
|
|
const kind = queuedJobPresetKind(job)
|
|
const loraStack = readLoraStack(payload)
|
|
if (kind === 'music') {
|
|
return {
|
|
name,
|
|
kind,
|
|
overwrite: true,
|
|
loraStack: [],
|
|
settings: {
|
|
tags: sanitizeMusicTags(payload.tags || job.prompt),
|
|
lyrics: sanitizeMusicLyrics(payload.lyrics, payload.instrumental === true),
|
|
instrumental: payload.instrumental === true,
|
|
duration: clampMusicDuration(payload.duration),
|
|
steps: clampMusicSteps(payload.steps),
|
|
seed: payload.seed,
|
|
cfg: payload.cfg
|
|
}
|
|
}
|
|
}
|
|
if (kind === 'imagev2') {
|
|
return {
|
|
name,
|
|
kind,
|
|
overwrite: true,
|
|
loraStack,
|
|
settings: {
|
|
mode: payload.v2Mode,
|
|
engine: payload.engine,
|
|
task: payload.v2Task,
|
|
prompt: payload.prompt || job.prompt,
|
|
negative: payload.negative,
|
|
snofsModel: payload.snofsModel,
|
|
snofsClip: payload.snofsClip,
|
|
consistencyModel: payload.consistencyModel,
|
|
consistencyClip: payload.consistencyClip,
|
|
steps: payload.steps,
|
|
cfg: payload.cfg,
|
|
megapixels: payload.scaleMegapixels,
|
|
scaleToTotalPixels: payload.scaleToTotalPixels,
|
|
turbo: payload.turbo === true,
|
|
strength: payload.refineStrength,
|
|
aspect: payload.aspect,
|
|
width: payload.width,
|
|
height: payload.height
|
|
}
|
|
}
|
|
}
|
|
if (kind === 'image') {
|
|
return {
|
|
name,
|
|
kind,
|
|
overwrite: true,
|
|
loraStack,
|
|
settings: {
|
|
prompt: payload.prompt || job.prompt,
|
|
steps: payload.steps,
|
|
cfg: payload.cfg,
|
|
negative: payload.negative,
|
|
scaleToTotalPixels: payload.scaleToTotalPixels,
|
|
scaleMegapixels: payload.scaleMegapixels,
|
|
aspect: payload.aspect
|
|
}
|
|
}
|
|
}
|
|
return {
|
|
name,
|
|
kind,
|
|
overwrite: true,
|
|
loraStack,
|
|
settings: {
|
|
workflow: payload.workflow,
|
|
turbo: payload.turbo !== false,
|
|
cfg: payload.cfg,
|
|
fps: payload.fps,
|
|
samplerName: payload.samplerName,
|
|
scheduler: payload.scheduler,
|
|
duration: payload.duration,
|
|
aspect: payload.aspect,
|
|
sound: payload.sound !== false
|
|
}
|
|
}
|
|
}
|