Recover a finished clip after the site drops the live job.

Saving the MP4 used to freeze Node, which made xAIGen unreachable and left the player empty even though the library already had the video.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-09-01 21:20:09 -05:00
co-authored by Cursor
parent a705610721
commit 6b93ee951c
2 changed files with 35 additions and 12 deletions
+25 -3
View File
@@ -1,3 +1,21 @@
const RECENT_CLIP_MS = 3 * 60 * 60 * 1000
function recentLibraryClip(event: Parameters<typeof assertLibraryOwner>[0]) {
const { catalog } = assertLibraryOwner(event)
const newest = catalog.clips[0]
if (!newest || Date.now() - newest.createdAt > RECENT_CLIP_MS) return null
return {
type: 'complete' as const,
status: 'complete' as const,
message: newest.hideThumbnail ? 'Video ready. Click the player to reveal it.' : 'Video ready',
progress: 100,
clipId: newest.id,
filename: newest.comfyFilename,
hideThumbnail: newest.hideThumbnail,
folderLocked: false
}
}
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const body = await readBody<{ jobId?: string }>(event).catch(() => ({} as { jobId?: string })) const body = await readBody<{ jobId?: string }>(event).catch(() => ({} as { jobId?: string }))
const jobId = String(body?.jobId || '') const jobId = String(body?.jobId || '')
@@ -11,6 +29,8 @@ export default defineEventHandler(async (event) => {
if (done) return done if (done) return done
return jobSnapshot(ensurePendingWatch(pending)) return jobSnapshot(ensurePendingWatch(pending))
} }
const saved = recentLibraryClip(event)
if (saved) return saved
throw createError({ statusCode: 404, statusMessage: 'Job not found' }) throw createError({ statusCode: 404, statusMessage: 'Job not found' })
} }
@@ -19,9 +39,7 @@ export default defineEventHandler(async (event) => {
const folderId = library.folders.find(folder => folder.unlocked)?.id const folderId = library.folders.find(folder => folder.unlocked)?.id
const imported = folderId ? await importMissingComfyVideos(owner, folderId) : [] const imported = folderId ? await importMissingComfyVideos(owner, folderId) : []
const newest = imported[imported.length - 1] const newest = imported[imported.length - 1]
if (!newest) { if (newest) {
throw createError({ statusCode: 404, statusMessage: 'No finished ComfyUI video to recover' })
}
return { return {
type: 'complete', type: 'complete',
status: 'complete', status: 'complete',
@@ -31,4 +49,8 @@ export default defineEventHandler(async (event) => {
filename: newest.comfyFilename, filename: newest.comfyFilename,
hideThumbnail: newest.hideThumbnail hideThumbnail: newest.hideThumbnail
} }
}
const saved = recentLibraryClip(event)
if (saved) return saved
throw createError({ statusCode: 404, statusMessage: 'No finished ComfyUI video to recover' })
}) })
+2 -1
View File
@@ -1,5 +1,6 @@
import { createHash } from 'node:crypto' import { createHash } from 'node:crypto'
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync, statSync, createReadStream, openSync, readSync, closeSync } from 'node:fs' import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync, statSync, createReadStream, openSync, readSync, closeSync } from 'node:fs'
import { writeFile } from 'node:fs/promises'
import { join } from 'node:path' import { join } from 'node:path'
import { spawn } from 'node:child_process' import { spawn } from 'node:child_process'
import { randomBytes, scrypt, timingSafeEqual } from 'node:crypto' import { randomBytes, scrypt, timingSafeEqual } from 'node:crypto'
@@ -1609,7 +1610,7 @@ export async function saveClip(params: {
} }
mkdirSync(clipDir(params.ownerKey, clip.id), { recursive: true }) mkdirSync(clipDir(params.ownerKey, clip.id), { recursive: true })
const videoPath = clipVideoPath(params.ownerKey, clip.id) const videoPath = clipVideoPath(params.ownerKey, clip.id)
writeFileSync(videoPath, params.video) await writeFile(videoPath, params.video)
await remuxFaststart(videoPath) await remuxFaststart(videoPath)
if (params.segmentFirstFrame?.length && params.segmentFirstFrame.length >= 64) { if (params.segmentFirstFrame?.length && params.segmentFirstFrame.length >= 64) {
const firstPath = clipFirstFramePath(params.ownerKey, clip.id) const firstPath = clipFirstFramePath(params.ownerKey, clip.id)