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:
@@ -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,16 +39,18 @@ 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 {
|
||||||
}
|
type: 'complete',
|
||||||
return {
|
status: 'complete',
|
||||||
type: 'complete',
|
message: 'Recovered video from ComfyUI',
|
||||||
status: 'complete',
|
progress: 100,
|
||||||
message: 'Recovered video from ComfyUI',
|
clipId: newest.id,
|
||||||
progress: 100,
|
filename: newest.comfyFilename,
|
||||||
clipId: newest.id,
|
hideThumbnail: newest.hideThumbnail
|
||||||
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' })
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user