From 6b93ee951cedf12fd7bfab7fa4e70c000087e765 Mon Sep 17 00:00:00 2001 From: Towsty Date: Tue, 1 Sep 2026 21:20:09 -0500 Subject: [PATCH] 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 --- server/api/generate/recover.post.ts | 44 +++++++++++++++++++++-------- server/utils/library.ts | 3 +- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/server/api/generate/recover.post.ts b/server/api/generate/recover.post.ts index c3a6444..453011e 100644 --- a/server/api/generate/recover.post.ts +++ b/server/api/generate/recover.post.ts @@ -1,3 +1,21 @@ +const RECENT_CLIP_MS = 3 * 60 * 60 * 1000 + +function recentLibraryClip(event: Parameters[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) => { const body = await readBody<{ jobId?: string }>(event).catch(() => ({} as { jobId?: string })) const jobId = String(body?.jobId || '') @@ -11,6 +29,8 @@ export default defineEventHandler(async (event) => { if (done) return done return jobSnapshot(ensurePendingWatch(pending)) } + const saved = recentLibraryClip(event) + if (saved) return saved throw createError({ statusCode: 404, statusMessage: 'Job not found' }) } @@ -19,16 +39,18 @@ export default defineEventHandler(async (event) => { const folderId = library.folders.find(folder => folder.unlocked)?.id const imported = folderId ? await importMissingComfyVideos(owner, folderId) : [] const newest = imported[imported.length - 1] - if (!newest) { - throw createError({ statusCode: 404, statusMessage: 'No finished ComfyUI video to recover' }) - } - return { - type: 'complete', - status: 'complete', - message: 'Recovered video from ComfyUI', - progress: 100, - clipId: newest.id, - filename: newest.comfyFilename, - hideThumbnail: newest.hideThumbnail + if (newest) { + return { + type: 'complete', + status: 'complete', + message: 'Recovered video from ComfyUI', + progress: 100, + clipId: newest.id, + filename: newest.comfyFilename, + hideThumbnail: newest.hideThumbnail + } } + const saved = recentLibraryClip(event) + if (saved) return saved + throw createError({ statusCode: 404, statusMessage: 'No finished ComfyUI video to recover' }) }) diff --git a/server/utils/library.ts b/server/utils/library.ts index 31525a2..a2c651b 100644 --- a/server/utils/library.ts +++ b/server/utils/library.ts @@ -1,5 +1,6 @@ import { createHash } from 'node:crypto' 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 { spawn } from 'node:child_process' import { randomBytes, scrypt, timingSafeEqual } from 'node:crypto' @@ -1609,7 +1610,7 @@ export async function saveClip(params: { } mkdirSync(clipDir(params.ownerKey, clip.id), { recursive: true }) const videoPath = clipVideoPath(params.ownerKey, clip.id) - writeFileSync(videoPath, params.video) + await writeFile(videoPath, params.video) await remuxFaststart(videoPath) if (params.segmentFirstFrame?.length && params.segmentFirstFrame.length >= 64) { const firstPath = clipFirstFramePath(params.ownerKey, clip.id)