Ship extension queues, library extend, and smoother seam stitching.
Users can pre-queue extensions, extend from the library with p2 titles, blend seams with xfade, and a unreadable catalog is no longer replaced with an empty one. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+25
-3
@@ -57,6 +57,11 @@ export interface PublicFolder {
|
||||
clipCount: number
|
||||
}
|
||||
|
||||
export interface QueuedExtension {
|
||||
prompt: string
|
||||
duration: number
|
||||
}
|
||||
|
||||
export interface RetryDraft {
|
||||
id: string
|
||||
createdAt: number
|
||||
@@ -79,6 +84,7 @@ export interface RetryDraft {
|
||||
fps?: number
|
||||
samplerName?: string
|
||||
scheduler?: string
|
||||
extensions?: QueuedExtension[]
|
||||
}
|
||||
|
||||
interface Catalog {
|
||||
@@ -198,11 +204,15 @@ function ensureOwner(owner: string) {
|
||||
|
||||
function readCatalog(owner: string): Catalog {
|
||||
ensureOwner(owner)
|
||||
const path = catalogPath(owner)
|
||||
try {
|
||||
return normalizeCatalog(JSON.parse(readFileSync(catalogPath(owner), 'utf8')))
|
||||
return normalizeCatalog(JSON.parse(readFileSync(path, 'utf8')))
|
||||
} catch {
|
||||
if (existsSync(path)) {
|
||||
throw createError({ statusCode: 500, statusMessage: 'Library catalog could not be read' })
|
||||
}
|
||||
const created = emptyCatalog()
|
||||
writeFileSync(catalogPath(owner), JSON.stringify(created, null, 2))
|
||||
writeFileSync(path, JSON.stringify(created, null, 2))
|
||||
return created
|
||||
}
|
||||
}
|
||||
@@ -233,6 +243,16 @@ export function clipTitle(clip: Pick<LibraryClip, 'name' | 'prompt'>) {
|
||||
return fromPrompt || 'Untitled clip'
|
||||
}
|
||||
|
||||
export function nextClipPartName(name: string) {
|
||||
const trimmed = (name || '').trim()
|
||||
if (!trimmed) return ''
|
||||
const match = trimmed.match(/^(.*)\s+p(\d+)$/i)
|
||||
const base = (match ? match[1] : trimmed).trim() || trimmed
|
||||
const part = match ? Number(match[2]) + 1 : 2
|
||||
const suffix = ` p${part}`
|
||||
return `${base.slice(0, Math.max(1, 80 - suffix.length))}${suffix}`
|
||||
}
|
||||
|
||||
export function safeDownloadName(name: string) {
|
||||
const cleaned = name.replace(/[<>:"/\\|?*'`\u0000-\u001f]/g, '-').replace(/\s+/g, ' ').trim()
|
||||
return cleaned.slice(0, 80) || 'aigen'
|
||||
@@ -689,6 +709,7 @@ export async function saveRetryDraft(params: {
|
||||
fps?: number
|
||||
samplerName?: string
|
||||
scheduler?: string
|
||||
extensions?: QueuedExtension[]
|
||||
}) {
|
||||
return mutate(params.ownerKey, (catalog) => {
|
||||
const existing = params.id ? catalog.drafts.find(item => item.id === params.id) : undefined
|
||||
@@ -713,7 +734,8 @@ export async function saveRetryDraft(params: {
|
||||
cfg: params.cfg,
|
||||
fps: params.fps,
|
||||
samplerName: params.samplerName,
|
||||
scheduler: params.scheduler
|
||||
scheduler: params.scheduler,
|
||||
extensions: params.extensions?.length ? params.extensions : undefined
|
||||
}
|
||||
catalog.drafts = [draft, ...catalog.drafts.filter(item => item.id !== draft.id)].slice(0, 20)
|
||||
return draft
|
||||
|
||||
Reference in New Issue
Block a user