Recover finished Comfy videos after relay restarts.
Persist queued jobs to disk, import leftover MP4s from Comfy history, and do not mark a run complete until the saved video exists. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+62
-9
@@ -27,6 +27,7 @@ export interface LibraryClip {
|
||||
seed: number
|
||||
hideThumbnail: boolean
|
||||
createdAt: number
|
||||
comfyFilename?: string
|
||||
}
|
||||
|
||||
export interface LibraryStill {
|
||||
@@ -460,10 +461,14 @@ export async function saveClip(params: {
|
||||
hideThumbnail: boolean
|
||||
video: Buffer
|
||||
thumb?: Buffer | null
|
||||
comfyFilename?: string
|
||||
}) {
|
||||
const catalog = readCatalog(params.ownerKey)
|
||||
const folder = catalog.folders.find(item => item.id === params.folderId) || catalog.folders[0]
|
||||
if (!folder) throw createError({ statusCode: 400, statusMessage: 'No library folder available' })
|
||||
if (params.comfyFilename && catalog.clips.some(clip => clip.comfyFilename === params.comfyFilename)) {
|
||||
return catalog.clips.find(clip => clip.comfyFilename === params.comfyFilename) as LibraryClip
|
||||
}
|
||||
const clip: LibraryClip = {
|
||||
id: crypto.randomUUID(),
|
||||
folderId: folder.id,
|
||||
@@ -475,7 +480,8 @@ export async function saveClip(params: {
|
||||
turbo: params.turbo,
|
||||
seed: params.seed,
|
||||
hideThumbnail: params.hideThumbnail,
|
||||
createdAt: Date.now()
|
||||
createdAt: Date.now(),
|
||||
comfyFilename: params.comfyFilename
|
||||
}
|
||||
mkdirSync(clipDir(params.ownerKey, clip.id), { recursive: true })
|
||||
writeFileSync(clipVideoPath(params.ownerKey, clip.id), params.video)
|
||||
@@ -515,12 +521,59 @@ export function deleteClip(owner: string, id: string) {
|
||||
}
|
||||
|
||||
export async function downloadComfyVideo(video: { filename: string; subfolder: string; type: string }) {
|
||||
const params = new URLSearchParams({
|
||||
filename: video.filename,
|
||||
subfolder: video.subfolder,
|
||||
type: video.type
|
||||
})
|
||||
const res = await comfyFetch(`/view?${params.toString()}`)
|
||||
if (!res.ok) throw new Error('Failed to fetch completed video from ComfyUI')
|
||||
return Buffer.from(await res.arrayBuffer())
|
||||
const subfolders = [...new Set([video.subfolder, 'video', ''])]
|
||||
let lastStatus = 0
|
||||
for (const subfolder of subfolders) {
|
||||
const params = new URLSearchParams({
|
||||
filename: video.filename,
|
||||
subfolder,
|
||||
type: video.type || 'output'
|
||||
})
|
||||
const res = await comfyFetch(`/view?${params.toString()}`)
|
||||
lastStatus = res.status
|
||||
if (res.ok) return Buffer.from(await res.arrayBuffer())
|
||||
}
|
||||
throw new Error(`Failed to fetch completed video from ComfyUI (${lastStatus})`)
|
||||
}
|
||||
|
||||
export async function importMissingComfyVideos(owner: string, folderId?: string) {
|
||||
const catalog = readCatalog(owner)
|
||||
const folder = catalog.folders.find(item => item.id === folderId) || catalog.folders[0]
|
||||
if (!folder) return []
|
||||
const known = new Set(catalog.clips.map(clip => clip.comfyFilename).filter(Boolean) as string[])
|
||||
const unlabeled = catalog.clips.filter(clip => !clip.comfyFilename).length
|
||||
const history = await fetchHistoryAll()
|
||||
const found: { promptId: string; video: { filename: string; subfolder: string; type: string }; prompt: string }[] = []
|
||||
for (const [promptId, entry] of Object.entries(history)) {
|
||||
const video = extractVideo({ [promptId]: entry as Record<string, unknown> }, promptId)
|
||||
if (!video || known.has(video.filename)) continue
|
||||
found.push({
|
||||
promptId,
|
||||
video,
|
||||
prompt: extractPromptFromHistory(entry) || 'Recovered from ComfyUI'
|
||||
})
|
||||
}
|
||||
found.sort((a, b) => a.video.filename.localeCompare(b.video.filename, undefined, { numeric: true }))
|
||||
const toImport = found.slice(unlabeled)
|
||||
const imported = []
|
||||
for (const item of toImport) {
|
||||
const buffer = await downloadComfyVideo(item.video)
|
||||
const clip = await saveClip({
|
||||
ownerKey: owner,
|
||||
folderId: folder.id,
|
||||
prompt: item.prompt,
|
||||
aspect: 'auto',
|
||||
width: 0,
|
||||
height: 0,
|
||||
steps: 0,
|
||||
turbo: true,
|
||||
seed: 0,
|
||||
hideThumbnail: false,
|
||||
video: buffer,
|
||||
thumb: null,
|
||||
comfyFilename: item.video.filename
|
||||
})
|
||||
imported.push(clip)
|
||||
}
|
||||
return imported
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user