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:
Towsty
2026-08-28 08:15:52 -05:00
co-authored by Cursor
parent 870150f052
commit 7d783bd97e
12 changed files with 338 additions and 49 deletions
+34 -15
View File
@@ -9,7 +9,7 @@ import { isLtxWorkflow, isTextToVideo, parseVideoWorkflow, workflowForExtension,
import { clipVideoPath, clipTitle, deleteRetryDraft, extendTempDir, getClip, nextClipPartName, removeExtendTemp, stillPath } from '~/server/utils/library'
import { extractLastFrame, probeHasAudio } from '~/server/utils/ffmpeg'
import { ensureComfyReady } from '~/server/utils/comfyLifecycle'
import { onLiveVideoSettled, onStudioQueueBurstStarted } from '~/server/utils/studioQueue'
import { onLiveVideoSettled, onStudioQueueBurstStarted, isStudioQueuePaused } from '~/server/utils/studioQueue'
import {
finishQueueBurst,
getShotQueue,
@@ -238,6 +238,17 @@ function seedCurrentVideo(job: Job) {
return { tmpDir, currentPath, part1Path: join(tmpDir, 'part1.mp4'), framePath: join(tmpDir, 'last_frame.png') }
}
function chainShouldHold(job: Job) {
if (!job.library) return false
if (job.library.stopAfterCurrent) return true
if (isStudioQueuePaused(job.library.ownerKey)) return true
const queue = job.library.queueId
? getShotQueue(job.library.ownerKey, job.library.queueId)
: null
if (queue?.stopAfterCurrent || queue?.status === 'paused') return true
return false
}
export async function continueQueuedExtensions(
job: Job,
params: VideoChainParams,
@@ -269,14 +280,22 @@ export async function continueQueuedExtensions(
for (let i = startFrom; i < extensions.length; i++) {
if (shotsLeft <= 0) break
assertJobActive(job)
if (job.library.stopAfterCurrent) break
if (chainShouldHold(job)) {
job.library.stopAfterCurrent = true
break
}
const liveQueue = job.library.queueId
? getShotQueue(job.library.ownerKey, job.library.queueId)
: null
if (liveQueue?.stopAfterCurrent) job.library.stopAfterCurrent = true
if (liveQueue?.autoRun === false) job.library.queueAutoRun = false
if (job.library.stopAfterCurrent) break
if (liveQueue?.stopAfterCurrent || liveQueue?.autoRun === false) {
if (liveQueue.stopAfterCurrent) job.library.stopAfterCurrent = true
if (liveQueue.autoRun === false) job.library.queueAutoRun = false
}
if (chainShouldHold(job)) {
job.library.stopAfterCurrent = true
break
}
const live = liveQueue ? liveSegmentPrompt(liveQueue, i) : extensions[i]
const ext = {
prompt: live.prompt || extensions[i].prompt,
@@ -289,10 +308,7 @@ export async function continueQueuedExtensions(
emitChainJob(job, { type: 'status', message: 'Waiting 3s buffer...', progress: 1 })
await sleep(3000)
assertJobActive(job)
const pausedQueue = job.library.queueId
? getShotQueue(job.library.ownerKey, job.library.queueId)
: null
if (job.library.stopAfterCurrent || pausedQueue?.stopAfterCurrent) {
if (chainShouldHold(job)) {
job.library.stopAfterCurrent = true
break
}
@@ -376,7 +392,10 @@ export async function continueQueuedExtensions(
job.library.extendPart1Path = undefined
}
if (shotsLeft <= 0 || job.library.stopAfterCurrent) break
if (shotsLeft <= 0 || chainShouldHold(job)) {
if (chainShouldHold(job)) job.library.stopAfterCurrent = true
break
}
}
} finally {
if (job.library) job.library.chainContinuing = false
@@ -398,7 +417,10 @@ export async function continueQueuedExtensions(
export async function continueQueuedExtensionsIfNeeded(job: Job) {
if (!job.library) return
if (job.status === 'error' || job.status === 'cancelled' || job.status === 'complete') return
if (job.library.stopAfterCurrent) return
if (chainShouldHold(job)) {
job.library.stopAfterCurrent = true
return
}
const remaining = remainingAfterCurrentShot(job.library)
if (!remaining.length) return
const queue = job.library.queueId
@@ -440,10 +462,7 @@ export async function runGeneration(job: Job, params: VideoChainParams) {
persist: extensions.length === 0 || !autoRun
})
const stopNow = job.library?.stopAfterCurrent === true
|| (job.library?.queueId
? getShotQueue(job.library.ownerKey, job.library.queueId)?.stopAfterCurrent === true
: false)
const stopNow = chainShouldHold(job)
const keepGoing = Boolean(autoRun && !stopNow && extensions.length && job.library)
if (job.library?.queueId && !keepGoing) {
await finishQueueBurst(job.library.ownerKey, job.library.queueId, true).catch(() => null)