Pause the queue after the current clip for real, and carry global locks plus labeled permanence stills through generate and shot prompts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-08-28 07:48:09 -05:00
co-authored by Cursor
parent 010ef6add2
commit efef409537
21 changed files with 1196 additions and 115 deletions
+49 -2
View File
@@ -6,6 +6,7 @@ import { randomBytes, scrypt, timingSafeEqual } from 'node:crypto'
import { promisify } from 'node:util'
import type { H3Event } from 'h3'
import { extractFirstFrame, extractLastFrame } from '~/server/utils/ffmpeg'
import { normalizePermanenceRefs, type PermanenceRef } from '~/utils/globalLocks'
const scryptAsync = promisify(scrypt)
@@ -43,6 +44,8 @@ export interface LibraryClip {
stillId?: string
sound?: boolean
hideInput?: boolean
globalLocks?: string
permanenceRefs?: PermanenceRef[]
}
export type StillRole = 'input' | 'output'
@@ -78,6 +81,7 @@ export interface PublicFolder {
export interface QueuedExtension {
prompt: string
duration: number
permanenceRefs?: PermanenceRef[]
}
export interface RetryDraft {
@@ -104,6 +108,9 @@ export interface RetryDraft {
scheduler?: string
extensions?: QueuedExtension[]
workflow?: import('~/utils/videoModels').VideoWorkflowId
globalLocks?: string
permanenceRefs?: PermanenceRef[]
shotPermanenceRefs?: PermanenceRef[][]
}
interface Catalog {
@@ -1120,6 +1127,9 @@ export async function saveRetryDraft(params: {
scheduler?: string
extensions?: QueuedExtension[]
workflow?: import('~/utils/videoModels').VideoWorkflowId
globalLocks?: string
permanenceRefs?: PermanenceRef[]
shotPermanenceRefs?: PermanenceRef[][]
}) {
return mutate(params.ownerKey, (catalog) => {
const existing = params.id ? catalog.drafts.find(item => item.id === params.id) : undefined
@@ -1146,7 +1156,10 @@ export async function saveRetryDraft(params: {
samplerName: params.samplerName,
scheduler: params.scheduler,
extensions: params.extensions?.length ? params.extensions : undefined,
workflow: params.workflow
workflow: params.workflow,
globalLocks: params.globalLocks,
permanenceRefs: params.permanenceRefs,
shotPermanenceRefs: params.shotPermanenceRefs
}
catalog.drafts = [draft, ...catalog.drafts.filter(item => item.id !== draft.id)].slice(0, 20)
return draft
@@ -1298,6 +1311,8 @@ export async function saveClip(params: {
workflow?: import('~/utils/videoModels').VideoWorkflowId
stillId?: string
sound?: boolean
globalLocks?: string
permanenceRefs?: PermanenceRef[]
}) {
const catalog = readCatalog(params.ownerKey)
const folder = catalog.folders.find(item => item.id === params.folderId) || catalog.folders[0]
@@ -1326,7 +1341,9 @@ export async function saveClip(params: {
workflow: params.workflow,
stillId: params.stillId,
sound: params.sound,
hideInput: params.hideInput === true
hideInput: params.hideInput === true,
globalLocks: params.globalLocks,
permanenceRefs: params.permanenceRefs?.length ? normalizePermanenceRefs(params.permanenceRefs) : undefined
}
mkdirSync(clipDir(params.ownerKey, clip.id), { recursive: true })
const videoPath = clipVideoPath(params.ownerKey, clip.id)
@@ -1413,6 +1430,36 @@ export function moveClipFamily(owner: string, id: string, folderId: string) {
})
}
export function patchClipMeta(owner: string, id: string, patch: {
globalLocks?: string
permanenceRefs?: PermanenceRef[]
family?: boolean
}) {
return mutate(owner, (catalog) => {
const clip = catalog.clips.find(item => item.id === id)
if (!clip) throw createError({ statusCode: 404, statusMessage: 'Clip not found' })
const family = patch.family ? clipsInSameFamily(catalog, clip) : [clip]
if (typeof patch.globalLocks === 'string') {
const locks = patch.globalLocks.trim()
for (const item of family) {
if (locks) item.globalLocks = locks
else delete item.globalLocks
}
}
if (patch.permanenceRefs) {
const refs = normalizePermanenceRefs(patch.permanenceRefs)
if (patch.family) {
for (const item of family) {
if (refs.length) item.permanenceRefs = refs
else delete item.permanenceRefs
}
} else if (refs.length) clip.permanenceRefs = refs
else delete clip.permanenceRefs
}
return patch.family ? family : clip
})
}
export function renameClip(owner: string, id: string, name: string) {
const trimmed = name.trim().slice(0, 80)
if (!trimmed) throw createError({ statusCode: 400, statusMessage: 'A name is required' })