Add local queued video upscale with Real-ESRGAN and RIFE

This commit is contained in:
Towsty
2026-09-08 17:55:02 -05:00
parent 4c97980646
commit 1e0cff9dea
24 changed files with 747 additions and 16 deletions
+15 -7
View File
@@ -16,6 +16,7 @@ export type StudioJobStatus = 'waiting' | 'running' | 'held' | 'complete' | 'err
export type StudioJobKind = 'video' | 'edit' | 'music'
export interface StudioJobPayload {
upscale?: { sourceId: string; scale: 2 | 4; target: string; fps: string | number; enhance: string }
prompt: string
promptMid?: string
promptPre?: string
@@ -284,7 +285,7 @@ function failZombieLiveJob(job: Job, error: string) {
function sweepStaleLiveJobs() {
const now = Date.now()
for (const job of listJobs()) {
if (job.yueGp) continue
if (job.yueGp || job.upscale) continue
if (job.saving) continue
if (job.status === 'queued' && !job.promptId && now - job.startedAt >= QUEUED_GRACE_MS) {
failZombieLiveJob(job, 'Job never started')
@@ -304,7 +305,7 @@ async function reapZombieLiveJobs() {
const { fetchHistory } = await import('~/server/utils/comfy')
for (const job of listJobs()) {
// Never interrupt download/stitch/library save — Comfy is idle then by design.
if (job.yueGp) continue
if (job.yueGp || job.upscale) continue
if (job.saving) continue
if (job.library?.chainContinuing) continue
if (job.status !== 'running' && job.status !== 'uploading' && job.status !== 'queued') continue
@@ -349,7 +350,7 @@ async function reapZombieLiveJobs() {
}
function liveJobOwnsGpu(job: Job) {
if (job.yueGp && ['running', 'queued', 'uploading'].includes(job.status)) return true
if ((job.yueGp || job.upscale) && ['running', 'queued', 'uploading'].includes(job.status)) return true
if (job.library?.stopAfterCurrent) return false
if (job.library?.chainContinuing) return true
if (job.saving) return true
@@ -369,7 +370,7 @@ function liveJobOwnsGpu(job: Job) {
*/
function clearDeadGpuClaimsForForceStart() {
for (const live of listJobs()) {
if (live.yueGp) continue
if (live.yueGp || live.upscale) continue
if (live.saving) continue
if (jobIsLocallySubmitting(live)) continue
if (live.status !== 'running' && live.status !== 'queued' && live.status !== 'uploading') {
@@ -591,6 +592,7 @@ export async function clearStuckStudioWork(owner: string) {
if (job.library?.ownerKey !== owner) continue
if (job.status === 'complete' || job.status === 'error' || job.status === 'cancelled') continue
liveIds.add(job.id)
if (job.upscale) { const { cancelUpscaleJob } = await import('./videoUpscale'); await cancelUpscaleJob(job) }
if (job.yueGp) {
const { cancelYueGpJob } = await import('./yueGp')
await cancelYueGpJob(job)
@@ -656,6 +658,7 @@ async function stopLiveGeneration(liveJobId?: string, shotQueueId?: string) {
}
if (!liveJobId) return
const live = getJob(liveJobId)
if (live?.upscale) { const { cancelUpscaleJob } = await import('./videoUpscale'); await cancelUpscaleJob(live); return }
if (live?.yueGp) {
const { cancelYueGpJob } = await import('./yueGp')
await cancelYueGpJob(live)
@@ -910,7 +913,7 @@ function repairStaleJobs(jobs: StudioJob[]) {
job.updatedAt = Date.now()
continue
}
if (job.payload.musicEngine !== 'yue' && job.status === 'error' && isTransientComfyError(job.lastError)) {
if (!job.payload.upscale && job.payload.musicEngine !== 'yue' && job.status === 'error' && isTransientComfyError(job.lastError)) {
job.status = 'waiting'
job.liveJobId = undefined
job.lastError = undefined
@@ -919,7 +922,7 @@ function repairStaleJobs(jobs: StudioJob[]) {
job.updatedAt = Date.now()
continue
}
if (job.payload.musicEngine !== 'yue' && job.status === 'held' && isTransientComfyError(job.lastError)) {
if (!job.payload.upscale && job.payload.musicEngine !== 'yue' && job.status === 'held' && isTransientComfyError(job.lastError)) {
job.status = 'waiting'
job.liveJobId = undefined
job.lastError = undefined
@@ -1419,6 +1422,11 @@ export async function startStudioJob(item: StudioJob) {
}
async function startStudioJobReserved(item: StudioJob) {
if (item.payload.upscale) {
try { const { startUpscaleJob } = await import('./videoUpscale'); await startUpscaleJob(item) }
catch (error) { await patchStudioJob(item.ownerKey, item.id, row => { row.status = 'error'; row.lastError = error instanceof Error ? error.message : String(error) }); scheduleKickRetry() }
return
}
if (studioJobKind(item) === 'music') {
await startStudioMusicJob(item)
return
@@ -1665,7 +1673,7 @@ export async function onLiveVideoSettled(job: Job) {
return
}
const remaining = remainingStudioShots(job)
const wakeFail = !job.yueGp && job.status === 'error' && remaining > 0 && isTransientComfyError(job.error)
const wakeFail = !job.yueGp && !job.upscale && job.status === 'error' && remaining > 0 && isTransientComfyError(job.error)
const failed = (job.status === 'error' || job.status === 'cancelled') && !wakeFail
await mutateStore(owner, (store) => {