Keep pause across Coolify restarts, apply the chosen still from the image picker, and remux clips with Range/faststart so finished video can be scrubbed.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+40
-3
@@ -4,6 +4,7 @@ import { getJob, restoreJob } from '~/server/utils/jobs'
|
||||
import type { PendingJob } from '~/server/utils/pending'
|
||||
import { isLastChainShot, libraryFromPending, pendingFromJob, remainingAfterCurrentShot, writePendingJob, deletePendingJob } from '~/server/utils/pending'
|
||||
import { syncQueueFromJob } from '~/server/utils/shotQueue'
|
||||
import { isEncodingNode, NODE_LABELS } from '~/server/utils/workflow'
|
||||
import { mergePermanenceRefs, type PermanenceRef } from '~/utils/globalLocks'
|
||||
|
||||
function clipRefsFromJob(library: NonNullable<Job['library']>): PermanenceRef[] | undefined {
|
||||
@@ -52,6 +53,9 @@ export function mapChainProgress(job: Job, localPct: number) {
|
||||
export function formatChainMessage(job: Job, message: string, samplePct?: number) {
|
||||
const { chained, step, total, label } = chainMeta(job)
|
||||
if (!chained) return message
|
||||
if (/checkpoint saved|segment saved/i.test(message)) {
|
||||
return `Shot ${step}/${total} saved`
|
||||
}
|
||||
if (typeof samplePct === 'number') {
|
||||
return `Step ${step}/${total}: Generating ${label} (${samplePct}%)`
|
||||
}
|
||||
@@ -78,6 +82,13 @@ export function emitChainJob(job: Job, event: JobEvent, samplePct?: number) {
|
||||
emitJob(job, next)
|
||||
}
|
||||
|
||||
function isNonSamplerProgress(node: string) {
|
||||
if (!node) return false
|
||||
if (isEncodingNode(node)) return true
|
||||
const label = NODE_LABELS[node] || ''
|
||||
return /save|checkpoint|video combine|vhs/i.test(label) && !/sampler/i.test(label)
|
||||
}
|
||||
|
||||
export function watchComfyJob(job: Job, options: { persist?: boolean } = {}): Promise<void> {
|
||||
const persist = options.persist !== false
|
||||
job.socketReady = false
|
||||
@@ -350,6 +361,11 @@ export function watchComfyJob(job: Job, options: { persist?: boolean } = {}): Pr
|
||||
if (job.promptId && data.prompt_id && data.prompt_id !== job.promptId) return
|
||||
|
||||
if (payload.type === 'progress') {
|
||||
const node = data.node ? String(data.node) : ''
|
||||
if (isNonSamplerProgress(node)) {
|
||||
markActivity()
|
||||
return
|
||||
}
|
||||
const value = Number(data.value || 0)
|
||||
const max = Number(data.max || 1)
|
||||
const pct = 12 + Math.round((value / Math.max(max, 1)) * 70)
|
||||
@@ -360,7 +376,7 @@ export function watchComfyJob(job: Job, options: { persist?: boolean } = {}): Pr
|
||||
step: value,
|
||||
maxStep: max,
|
||||
progress: pct,
|
||||
node: data.node ? String(data.node) : null,
|
||||
node: node || null,
|
||||
message: `Sampling step ${value}/${max} (${samplePct}%)`
|
||||
}, samplePct)
|
||||
}
|
||||
@@ -424,20 +440,41 @@ export function ensurePendingWatch(pending: PendingJob) {
|
||||
pendingWatches.add(job.id)
|
||||
const resume = async () => {
|
||||
try {
|
||||
const { applyPersistedPauseToJob, onLiveVideoSettled } = await import('~/server/utils/studioQueue')
|
||||
applyPersistedPauseToJob(job)
|
||||
const holdNow = () => job.library?.stopAfterCurrent === true
|
||||
if (pending.promptId) {
|
||||
await watchComfyJob(job, { persist: lastShot })
|
||||
await watchComfyJob(job, { persist: lastShot || holdNow() })
|
||||
}
|
||||
applyPersistedPauseToJob(job)
|
||||
if (job.status === 'error' || job.status === 'cancelled') {
|
||||
await onLiveVideoSettled(job)
|
||||
return
|
||||
}
|
||||
if (holdNow()) {
|
||||
await onLiveVideoSettled(job)
|
||||
return
|
||||
}
|
||||
if (job.status === 'complete') {
|
||||
await onLiveVideoSettled(job)
|
||||
return
|
||||
}
|
||||
if (job.status === 'error' || job.status === 'cancelled' || job.status === 'complete') return
|
||||
if (!lastShot) {
|
||||
const { continueQueuedExtensionsIfNeeded } = await import('~/server/utils/videoChain')
|
||||
await continueQueuedExtensionsIfNeeded(job)
|
||||
}
|
||||
applyPersistedPauseToJob(job)
|
||||
if (job.status === 'error' || job.status === 'cancelled' || job.status === 'complete' || holdNow()) {
|
||||
await onLiveVideoSettled(job)
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
if (job.status === 'error' || job.status === 'cancelled') return
|
||||
job.status = 'error'
|
||||
job.error = message
|
||||
emitChainJob(job, { type: 'error', error: message, message })
|
||||
const { onLiveVideoSettled } = await import('~/server/utils/studioQueue')
|
||||
await onLiveVideoSettled(job).catch(() => null)
|
||||
} finally {
|
||||
pendingWatches.delete(job.id)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user