Pause only the current shot batch so a new Generate can run when Beast is idle, without auto-resuming the held list.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+103
-35
@@ -52,11 +52,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-if="error" class="rounded-2xl border border-red-400/30 bg-red-400/10 px-4 py-3 text-sm text-red-200">{{ error }}</p>
|
<p v-if="error" class="rounded-2xl border border-red-400/30 bg-red-400/10 px-4 py-3 text-sm text-red-200">{{ error }}</p>
|
||||||
<p v-else-if="pauseArmed && anythingGenerating" class="rounded-2xl border border-amber-300/30 bg-amber-400/10 px-4 py-3 text-sm text-amber-100">
|
<p v-else-if="willPauseLatch" class="rounded-2xl border border-amber-300/30 bg-amber-400/10 px-4 py-3 text-sm text-amber-100">
|
||||||
Will pause after this video. The current clip keeps generating; the next queued job will not start until you resume.
|
Will pause after this video. The current clip keeps generating; this batch will stop after it finishes.
|
||||||
</p>
|
</p>
|
||||||
<p v-else-if="pauseArmed" class="rounded-2xl border border-amber-300/30 bg-amber-400/10 px-4 py-3 text-sm text-amber-100">
|
<p v-else-if="heldPaused" class="rounded-2xl border border-amber-300/30 bg-amber-400/10 px-4 py-3 text-sm text-amber-100">
|
||||||
Paused after current. Waiting jobs will not start until you press Resume queue.
|
This batch is paused. It will not continue until you resume.
|
||||||
</p>
|
</p>
|
||||||
<p v-else-if="!jobs.length && !orphans.length" class="glass rounded-3xl px-5 py-10 text-center text-sm text-zinc-400">
|
<p v-else-if="!jobs.length && !orphans.length" class="glass rounded-3xl px-5 py-10 text-center text-sm text-zinc-400">
|
||||||
Nothing queued. Generate from Studio — a 30-shot script takes one slot, and you can line up more jobs behind it.
|
Nothing queued. Generate from Studio — a 30-shot script takes one slot, and you can line up more jobs behind it.
|
||||||
@@ -86,10 +86,11 @@
|
|||||||
{{ job.cutIn ? 'Next after current shot' : 'Run next' }}
|
{{ job.cutIn ? 'Next after current shot' : 'Run next' }}
|
||||||
</button>
|
</button>
|
||||||
<template v-if="job.shots">
|
<template v-if="job.shots">
|
||||||
|
<template v-if="canProcessShots(job)">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="rounded-full bg-amber-400 px-3 py-1.5 text-xs font-semibold text-zinc-950 disabled:cursor-not-allowed disabled:opacity-50"
|
class="rounded-full bg-amber-400 px-3 py-1.5 text-xs font-semibold text-zinc-950 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
:disabled="busyId === job.shots.id || job.shots.status === 'running' || job.shots.status === 'complete' || job.shots.pendingCount === 0"
|
:disabled="busyId === job.shots.id || job.shots.status === 'complete' || job.shots.pendingCount === 0"
|
||||||
@click="process(job.shots, 1)"
|
@click="process(job.shots, 1)"
|
||||||
>
|
>
|
||||||
{{ continueLabel(job.shots) }}
|
{{ continueLabel(job.shots) }}
|
||||||
@@ -97,7 +98,7 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="rounded-full border border-white/10 px-3 py-1.5 text-xs text-zinc-200 hover:border-amber-300/40 disabled:cursor-not-allowed disabled:opacity-50"
|
class="rounded-full border border-white/10 px-3 py-1.5 text-xs text-zinc-200 hover:border-amber-300/40 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
:disabled="busyId === job.shots.id || job.shots.status === 'running' || job.shots.status === 'complete' || job.shots.pendingCount < 2"
|
:disabled="busyId === job.shots.id || job.shots.status === 'complete' || job.shots.pendingCount < 2"
|
||||||
@click="process(job.shots, Math.min(3, job.shots.pendingCount))"
|
@click="process(job.shots, Math.min(3, job.shots.pendingCount))"
|
||||||
>
|
>
|
||||||
Next {{ Math.min(3, job.shots.pendingCount) || 3 }}
|
Next {{ Math.min(3, job.shots.pendingCount) || 3 }}
|
||||||
@@ -105,11 +106,12 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="rounded-full border border-white/10 px-3 py-1.5 text-xs text-zinc-200 hover:border-amber-300/40 disabled:cursor-not-allowed disabled:opacity-50"
|
class="rounded-full border border-white/10 px-3 py-1.5 text-xs text-zinc-200 hover:border-amber-300/40 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
:disabled="busyId === job.shots.id || job.shots.status === 'running' || job.shots.status === 'complete' || job.shots.pendingCount === 0"
|
:disabled="busyId === job.shots.id || job.shots.status === 'complete' || job.shots.pendingCount === 0"
|
||||||
@click="process(job.shots, 'all')"
|
@click="process(job.shots, 'all')"
|
||||||
>
|
>
|
||||||
Run remaining
|
Run remaining
|
||||||
</button>
|
</button>
|
||||||
|
</template>
|
||||||
<button
|
<button
|
||||||
v-if="canPauseJob(job)"
|
v-if="canPauseJob(job)"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -117,10 +119,10 @@
|
|||||||
:class="pauseToggleClass(jobIsArmed(job))"
|
:class="pauseToggleClass(jobIsArmed(job))"
|
||||||
:disabled="pauseBusy"
|
:disabled="pauseBusy"
|
||||||
:aria-pressed="jobIsArmed(job)"
|
:aria-pressed="jobIsArmed(job)"
|
||||||
:title="jobIsArmed(job) ? 'Click to keep going after this video' : 'Finish the current video, then stop'"
|
:title="jobPauseTitle(job)"
|
||||||
@click="togglePause({ job })"
|
@click="togglePause({ job })"
|
||||||
>
|
>
|
||||||
{{ jobIsArmed(job) ? 'Will pause after this' : 'Pause after this' }}
|
{{ jobPauseLabel(job) }}
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
<button
|
<button
|
||||||
@@ -130,10 +132,10 @@
|
|||||||
:class="pauseToggleClass(jobIsArmed(job))"
|
:class="pauseToggleClass(jobIsArmed(job))"
|
||||||
:disabled="pauseBusy"
|
:disabled="pauseBusy"
|
||||||
:aria-pressed="jobIsArmed(job)"
|
:aria-pressed="jobIsArmed(job)"
|
||||||
:title="jobIsArmed(job) ? 'Click to keep going after this video' : 'Finish the current video, then stop'"
|
:title="jobPauseTitle(job)"
|
||||||
@click="togglePause({ job })"
|
@click="togglePause({ job })"
|
||||||
>
|
>
|
||||||
{{ jobIsArmed(job) ? 'Will pause after this' : 'Pause after this' }}
|
{{ jobPauseLabel(job) }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="job.status === 'waiting' || job.status === 'held'"
|
v-if="job.status === 'waiting' || job.status === 'held'"
|
||||||
@@ -225,10 +227,11 @@
|
|||||||
<p v-if="queue.lastError" class="mt-1 text-xs text-red-300">{{ queue.lastError }}</p>
|
<p v-if="queue.lastError" class="mt-1 text-xs text-red-300">{{ queue.lastError }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<template v-if="canProcessQueue(queue)">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="rounded-full bg-amber-400 px-3 py-1.5 text-xs font-semibold text-zinc-950 disabled:cursor-not-allowed disabled:opacity-50"
|
class="rounded-full bg-amber-400 px-3 py-1.5 text-xs font-semibold text-zinc-950 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
:disabled="busyId === queue.id || queue.status === 'running' || queue.status === 'complete' || queue.pendingCount === 0"
|
:disabled="busyId === queue.id || queue.status === 'complete' || queue.pendingCount === 0"
|
||||||
@click="process(queue, 1)"
|
@click="process(queue, 1)"
|
||||||
>
|
>
|
||||||
{{ continueLabel(queue) }}
|
{{ continueLabel(queue) }}
|
||||||
@@ -236,7 +239,7 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="rounded-full border border-white/10 px-3 py-1.5 text-xs text-zinc-200 hover:border-amber-300/40 disabled:cursor-not-allowed disabled:opacity-50"
|
class="rounded-full border border-white/10 px-3 py-1.5 text-xs text-zinc-200 hover:border-amber-300/40 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
:disabled="busyId === queue.id || queue.status === 'running' || queue.status === 'complete' || queue.pendingCount < 2"
|
:disabled="busyId === queue.id || queue.status === 'complete' || queue.pendingCount < 2"
|
||||||
@click="process(queue, Math.min(3, queue.pendingCount))"
|
@click="process(queue, Math.min(3, queue.pendingCount))"
|
||||||
>
|
>
|
||||||
Next {{ Math.min(3, queue.pendingCount) || 3 }}
|
Next {{ Math.min(3, queue.pendingCount) || 3 }}
|
||||||
@@ -244,22 +247,23 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="rounded-full border border-white/10 px-3 py-1.5 text-xs text-zinc-200 hover:border-amber-300/40 disabled:cursor-not-allowed disabled:opacity-50"
|
class="rounded-full border border-white/10 px-3 py-1.5 text-xs text-zinc-200 hover:border-amber-300/40 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
:disabled="busyId === queue.id || queue.status === 'running' || queue.status === 'complete' || queue.pendingCount === 0"
|
:disabled="busyId === queue.id || queue.status === 'complete' || queue.pendingCount === 0"
|
||||||
@click="process(queue, 'all')"
|
@click="process(queue, 'all')"
|
||||||
>
|
>
|
||||||
Run remaining
|
Run remaining
|
||||||
</button>
|
</button>
|
||||||
|
</template>
|
||||||
<button
|
<button
|
||||||
v-if="queue.status === 'running' || queue.stopAfterCurrent"
|
v-if="queue.status === 'running' || queue.stopAfterCurrent"
|
||||||
type="button"
|
type="button"
|
||||||
class="min-h-10 rounded-full px-3 py-2 text-xs font-semibold disabled:cursor-not-allowed disabled:opacity-50"
|
class="min-h-10 rounded-full px-3 py-2 text-xs font-semibold disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
:class="pauseToggleClass(Boolean(queue.stopAfterCurrent) || queuePaused)"
|
:class="pauseToggleClass(queueIsArmed(queue))"
|
||||||
:disabled="pauseBusy || (queue.status !== 'running' && !queue.stopAfterCurrent && !queuePaused)"
|
:disabled="pauseBusy"
|
||||||
:aria-pressed="Boolean(queue.stopAfterCurrent) || queuePaused"
|
:aria-pressed="queueIsArmed(queue)"
|
||||||
:title="(queue.stopAfterCurrent || queuePaused) ? 'Click to keep going after this video' : 'Finish the current video, then stop'"
|
:title="queuePauseTitle(queue)"
|
||||||
@click="togglePause({ queue })"
|
@click="togglePause({ queue })"
|
||||||
>
|
>
|
||||||
{{ queue.stopAfterCurrent || queuePaused ? (queue.status === 'running' ? 'Will pause after this' : 'Paused after current') : 'Pause after this' }}
|
{{ queuePauseLabel(queue) }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="queue.status !== 'running'"
|
v-if="queue.status !== 'running'"
|
||||||
@@ -387,22 +391,26 @@ let pollTimer: ReturnType<typeof setInterval> | null = null
|
|||||||
const canCutIn = computed(() => jobs.value.some(job => job.status === 'running' || job.status === 'held'))
|
const canCutIn = computed(() => jobs.value.some(job => job.status === 'running' || job.status === 'held'))
|
||||||
const pauseBusy = computed(() => busyId.value === 'pause')
|
const pauseBusy = computed(() => busyId.value === 'pause')
|
||||||
const anythingGenerating = computed(() => (
|
const anythingGenerating = computed(() => (
|
||||||
jobs.value.some(job => job.status === 'running' || job.shots?.status === 'running')
|
jobs.value.some(job => jobIsGenerating(job))
|
||||||
|| orphans.value.some(queue => queue.status === 'running')
|
|| orphans.value.some(queue => queue.status === 'running')
|
||||||
))
|
))
|
||||||
const pauseArmed = computed(() => (
|
const willPauseLatch = computed(() => (
|
||||||
queuePaused.value
|
jobs.value.some(job => jobIsLatched(job))
|
||||||
|| jobs.value.some(job => job.pauseAfterCurrent || job.pausedByUser || job.shots?.stopAfterCurrent)
|
|| orphans.value.some(queue => queueIsLatched(queue))
|
||||||
|| orphans.value.some(queue => queue.stopAfterCurrent)
|
|
||||||
))
|
))
|
||||||
|
const heldPaused = computed(() => (
|
||||||
|
jobs.value.some(job => jobIsHeldPaused(job))
|
||||||
|
|| orphans.value.some(queue => queueIsHeldPaused(queue))
|
||||||
|
))
|
||||||
|
const pauseArmed = computed(() => willPauseLatch.value || heldPaused.value)
|
||||||
const pauseToggleLabel = computed(() => {
|
const pauseToggleLabel = computed(() => {
|
||||||
if (pauseArmed.value && anythingGenerating.value) return 'Will pause after this'
|
if (willPauseLatch.value) return 'Will pause after this'
|
||||||
if (pauseArmed.value) return 'Resume queue'
|
if (heldPaused.value) return 'Resume queue'
|
||||||
return 'Pause after current'
|
return 'Pause after current'
|
||||||
})
|
})
|
||||||
const pauseToggleTitle = computed(() => {
|
const pauseToggleTitle = computed(() => {
|
||||||
if (pauseArmed.value && anythingGenerating.value) return 'Click to keep going after this video'
|
if (willPauseLatch.value) return 'Click to keep going after this video'
|
||||||
if (pauseArmed.value) return 'Start the next waiting job'
|
if (heldPaused.value) return 'Resume the paused batch'
|
||||||
return 'Finish the current video, then stop. Does not cancel the one that’s generating.'
|
return 'Finish the current video, then stop. Does not cancel the one that’s generating.'
|
||||||
})
|
})
|
||||||
const pauseDisabledReason = computed(() => {
|
const pauseDisabledReason = computed(() => {
|
||||||
@@ -435,6 +443,9 @@ function progressLine(queue: Queue) {
|
|||||||
const pauseNote = queue.stopAfterCurrent ? ' Will pause after this shot.' : ''
|
const pauseNote = queue.stopAfterCurrent ? ' Will pause after this shot.' : ''
|
||||||
return `Generating shot ${next.index + 1} of ${queue.totalCount}. ${queue.completedCount} saved.${pauseNote}`
|
return `Generating shot ${next.index + 1} of ${queue.totalCount}. ${queue.completedCount} saved.${pauseNote}`
|
||||||
}
|
}
|
||||||
|
if (queueIsHeldPaused(queue) && next) {
|
||||||
|
return `Paused. ${queue.completedCount} of ${queue.totalCount} saved. Next is shot ${next.index + 1}.`
|
||||||
|
}
|
||||||
if (queue.completedCount === 0) return `Not started · ${queue.totalCount} shots`
|
if (queue.completedCount === 0) return `Not started · ${queue.totalCount} shots`
|
||||||
if (next) return `Left off after shot ${queue.completedCount} of ${queue.totalCount}. Next is shot ${next.index + 1}.`
|
if (next) return `Left off after shot ${queue.completedCount} of ${queue.totalCount}. Next is shot ${next.index + 1}.`
|
||||||
return `Shot ${queue.completedCount} of ${queue.totalCount}`
|
return `Shot ${queue.completedCount} of ${queue.totalCount}`
|
||||||
@@ -448,18 +459,17 @@ function jobLine(job: StudioJobRow) {
|
|||||||
}
|
}
|
||||||
if (job.shots) {
|
if (job.shots) {
|
||||||
const line = progressLine(job.shots)
|
const line = progressLine(job.shots)
|
||||||
if (job.pauseAfterCurrent && job.shots.status === 'running' && !job.shots.stopAfterCurrent) {
|
if (jobIsLatched(job) && !job.shots.stopAfterCurrent) {
|
||||||
return `${line} Will pause after this shot.`
|
return `${line} Will pause after this shot.`
|
||||||
}
|
}
|
||||||
if (job.pausedByUser) return `${line} Paused after current.`
|
|
||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
if (job.status === 'held') {
|
if (job.status === 'held') {
|
||||||
if (job.pausedByUser) return 'Paused after current. Next jobs will not start until you resume.'
|
if (jobIsHeldPaused(job)) return 'Paused. This batch will not continue until you resume.'
|
||||||
return job.holdForCutIn ? 'Paused so another job can cut in' : 'Paused'
|
return job.holdForCutIn ? 'Paused so another job can cut in' : 'Paused'
|
||||||
}
|
}
|
||||||
if (job.status === 'running') {
|
if (job.status === 'running') {
|
||||||
if (job.pauseAfterCurrent) return `Generating · will pause after this · ${job.shotCount} shot${job.shotCount === 1 ? '' : 's'}`
|
if (jobIsLatched(job)) return `Generating · will pause after this · ${job.shotCount} shot${job.shotCount === 1 ? '' : 's'}`
|
||||||
return `Generating · ${job.shotCount} shot${job.shotCount === 1 ? '' : 's'}`
|
return `Generating · ${job.shotCount} shot${job.shotCount === 1 ? '' : 's'}`
|
||||||
}
|
}
|
||||||
return job.status
|
return job.status
|
||||||
@@ -630,24 +640,82 @@ function pauseToggleClass(pressed: boolean) {
|
|||||||
: 'border border-amber-300/50 text-amber-100 hover:border-amber-200 hover:bg-amber-400/10'
|
: 'border border-amber-300/50 text-amber-100 hover:border-amber-200 hover:bg-amber-400/10'
|
||||||
}
|
}
|
||||||
|
|
||||||
function canPauseJob(job: StudioJobRow) {
|
function jobIsGenerating(job: StudioJobRow) {
|
||||||
return job.status === 'running' || job.shots?.status === 'running'
|
return job.status === 'running' || job.shots?.status === 'running'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function jobIsLatched(job: StudioJobRow) {
|
||||||
|
return jobIsGenerating(job) && Boolean(job.pauseAfterCurrent || job.shots?.stopAfterCurrent)
|
||||||
|
}
|
||||||
|
|
||||||
|
function jobIsHeldPaused(job: StudioJobRow) {
|
||||||
|
if (jobIsGenerating(job)) return false
|
||||||
|
return Boolean(job.pausedByUser || job.shots?.stopAfterCurrent)
|
||||||
|
}
|
||||||
|
|
||||||
|
function queueIsLatched(queue: Queue) {
|
||||||
|
return queue.status === 'running' && Boolean(queue.stopAfterCurrent)
|
||||||
|
}
|
||||||
|
|
||||||
|
function queueIsHeldPaused(queue: Queue) {
|
||||||
|
return queue.status !== 'running' && Boolean(queue.stopAfterCurrent)
|
||||||
|
}
|
||||||
|
|
||||||
|
function queueIsArmed(queue: Queue) {
|
||||||
|
return Boolean(queue.stopAfterCurrent)
|
||||||
|
}
|
||||||
|
|
||||||
|
function canProcessShots(job: StudioJobRow) {
|
||||||
|
return Boolean(job.shots) && !jobIsGenerating(job) && !jobIsHeldPaused(job)
|
||||||
|
}
|
||||||
|
|
||||||
|
function canProcessQueue(queue: Queue) {
|
||||||
|
return queue.status !== 'running' && !queueIsHeldPaused(queue)
|
||||||
|
}
|
||||||
|
|
||||||
|
function canPauseJob(job: StudioJobRow) {
|
||||||
|
return jobIsGenerating(job) || jobIsHeldPaused(job)
|
||||||
|
}
|
||||||
|
|
||||||
function jobIsArmed(job: StudioJobRow) {
|
function jobIsArmed(job: StudioJobRow) {
|
||||||
return Boolean(job.pauseAfterCurrent || job.shots?.stopAfterCurrent || (queuePaused.value && job.status === 'running'))
|
return jobIsLatched(job) || jobIsHeldPaused(job)
|
||||||
|
}
|
||||||
|
|
||||||
|
function jobPauseLabel(job: StudioJobRow) {
|
||||||
|
if (jobIsLatched(job)) return 'Will pause after this'
|
||||||
|
if (jobIsHeldPaused(job)) return 'Paused'
|
||||||
|
return 'Pause after this'
|
||||||
|
}
|
||||||
|
|
||||||
|
function jobPauseTitle(job: StudioJobRow) {
|
||||||
|
if (jobIsLatched(job)) return 'Click to keep going after this video'
|
||||||
|
if (jobIsHeldPaused(job)) return 'Resume this batch'
|
||||||
|
return 'Finish the current video, then stop'
|
||||||
|
}
|
||||||
|
|
||||||
|
function queuePauseLabel(queue: Queue) {
|
||||||
|
if (queueIsLatched(queue)) return 'Will pause after this'
|
||||||
|
if (queueIsHeldPaused(queue)) return 'Paused'
|
||||||
|
return 'Pause after this'
|
||||||
|
}
|
||||||
|
|
||||||
|
function queuePauseTitle(queue: Queue) {
|
||||||
|
if (queueIsLatched(queue)) return 'Click to keep going after this video'
|
||||||
|
if (queueIsHeldPaused(queue)) return 'Resume this batch'
|
||||||
|
return 'Finish the current video, then stop'
|
||||||
}
|
}
|
||||||
|
|
||||||
async function togglePause(target?: { job?: StudioJobRow; queue?: Queue }) {
|
async function togglePause(target?: { job?: StudioJobRow; queue?: Queue }) {
|
||||||
const armed = target?.job
|
const armed = target?.job
|
||||||
? jobIsArmed(target.job)
|
? jobIsArmed(target.job)
|
||||||
: target?.queue
|
: target?.queue
|
||||||
? Boolean(target.queue.stopAfterCurrent || queuePaused.value)
|
? queueIsArmed(target.queue)
|
||||||
: pauseArmed.value
|
: pauseArmed.value
|
||||||
const resume = armed
|
const resume = armed
|
||||||
if (target?.job) {
|
if (target?.job) {
|
||||||
target.job.pauseAfterCurrent = !resume
|
target.job.pauseAfterCurrent = !resume
|
||||||
if (target.job.shots) target.job.shots.stopAfterCurrent = !resume
|
if (target.job.shots) target.job.shots.stopAfterCurrent = !resume
|
||||||
|
if (resume) target.job.pausedByUser = false
|
||||||
}
|
}
|
||||||
if (target?.queue) target.queue.stopAfterCurrent = !resume
|
if (target?.queue) target.queue.stopAfterCurrent = !resume
|
||||||
if (!target?.job && !target?.queue) queuePaused.value = !resume
|
if (!target?.job && !target?.queue) queuePaused.value = !resume
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { listShotQueues, summarizeQueue } from '~/server/utils/shotQueue'
|
import { listShotQueues, summarizeQueue } from '~/server/utils/shotQueue'
|
||||||
import { isStudioQueuePaused, listStudioJobs, summarizeStudioJob } from '~/server/utils/studioQueue'
|
import { listStudioJobs, summarizeStudioJob } from '~/server/utils/studioQueue'
|
||||||
|
|
||||||
export default defineEventHandler((event) => {
|
export default defineEventHandler((event) => {
|
||||||
const { owner } = assertLibraryOwner(event)
|
const { owner } = assertLibraryOwner(event)
|
||||||
@@ -28,14 +28,20 @@ export default defineEventHandler((event) => {
|
|||||||
pendingCount: shotSummary?.pendingCount ?? job.shotCount
|
pendingCount: shotSummary?.pendingCount ?? job.shotCount
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return {
|
const orphans = full
|
||||||
jobs,
|
|
||||||
orphans: full
|
|
||||||
? queues
|
? queues
|
||||||
.filter(queue => !claimed.has(queue.id) && queue.status !== 'complete')
|
.filter(queue => !claimed.has(queue.id) && queue.status !== 'complete')
|
||||||
.map(summarizeQueue)
|
.map(summarizeQueue)
|
||||||
: [],
|
: []
|
||||||
|
return {
|
||||||
|
jobs,
|
||||||
|
orphans,
|
||||||
waitingCount: jobs.filter(job => job.status === 'waiting').length,
|
waitingCount: jobs.filter(job => job.status === 'waiting').length,
|
||||||
paused: isStudioQueuePaused(owner)
|
paused: jobs.some(job => job.pausedByUser === true)
|
||||||
|
|| orphans.some(queue => queue.stopAfterCurrent === true && queue.status !== 'running'),
|
||||||
|
willPause: jobs.some(job => (
|
||||||
|
job.pauseAfterCurrent === true
|
||||||
|
|| (job.status === 'running' && Boolean(job.shotQueueId && byId.get(job.shotQueueId)?.stopAfterCurrent))
|
||||||
|
))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { listPendingJobs } from '~/server/utils/pending'
|
import { listPendingJobs } from '~/server/utils/pending'
|
||||||
import { listOwnersWithQueues, listShotQueues, pendingSegmentCount } from '~/server/utils/shotQueue'
|
import { listOwnersWithQueues, listShotQueues, pendingSegmentCount } from '~/server/utils/shotQueue'
|
||||||
import { isStudioQueuePaused, kickStudioQueue, listOwnersWithStudioQueues, listStudioJobs } from '~/server/utils/studioQueue'
|
import { kickStudioQueue, listOwnersWithStudioQueues, listStudioJobs } from '~/server/utils/studioQueue'
|
||||||
import { startQueueBurst } from '~/server/utils/videoChain'
|
import { startQueueBurst } from '~/server/utils/videoChain'
|
||||||
|
|
||||||
export default defineNitroPlugin(() => {
|
export default defineNitroPlugin(() => {
|
||||||
@@ -15,7 +15,6 @@ export default defineNitroPlugin(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const owner of listOwnersWithQueues()) {
|
for (const owner of listOwnersWithQueues()) {
|
||||||
if (isStudioQueuePaused(owner)) continue
|
|
||||||
for (const queue of listShotQueues(owner)) {
|
for (const queue of listShotQueues(owner)) {
|
||||||
if (claimedQueueIds.has(queue.id)) continue
|
if (claimedQueueIds.has(queue.id)) continue
|
||||||
if (!queue.autoRun) continue
|
if (!queue.autoRun) continue
|
||||||
|
|||||||
+101
-31
@@ -139,8 +139,28 @@ function mutateStore<T>(owner: string, fn: (store: StudioQueueStore) => T): Prom
|
|||||||
return run
|
return run
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function jobIsUserPaused(job: StudioJob) {
|
||||||
|
return job.pauseAfterCurrent === true || job.pausedByUser === true
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncPausedFlag(store: StudioQueueStore) {
|
||||||
|
store.paused = store.jobs.some(jobIsUserPaused)
|
||||||
|
}
|
||||||
|
|
||||||
|
function migrateGlobalPause(store: StudioQueueStore) {
|
||||||
|
const anyJobFlag = store.jobs.some(jobIsUserPaused)
|
||||||
|
if (store.paused && !anyJobFlag) {
|
||||||
|
for (const job of store.jobs) {
|
||||||
|
if (job.status === 'running') job.pauseAfterCurrent = true
|
||||||
|
else if (job.status === 'held' && !job.holdForCutIn) job.pausedByUser = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
syncPausedFlag(store)
|
||||||
|
}
|
||||||
|
|
||||||
export function isStudioQueuePaused(owner: string) {
|
export function isStudioQueuePaused(owner: string) {
|
||||||
return readStore(owner).paused === true
|
const store = readStore(owner)
|
||||||
|
return store.jobs.some(jobIsUserPaused)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listOwnersWithStudioQueues() {
|
export function listOwnersWithStudioQueues() {
|
||||||
@@ -196,11 +216,9 @@ export function applyPersistedPauseToJob(job: Job) {
|
|||||||
const row = store.jobs.find(item => item.liveJobId === job.id)
|
const row = store.jobs.find(item => item.liveJobId === job.id)
|
||||||
|| (library.queueId ? store.jobs.find(item => item.shotQueueId === library.queueId) : undefined)
|
|| (library.queueId ? store.jobs.find(item => item.shotQueueId === library.queueId) : undefined)
|
||||||
const queue = library.queueId ? getShotQueue(library.ownerKey, library.queueId) : null
|
const queue = library.queueId ? getShotQueue(library.ownerKey, library.queueId) : null
|
||||||
const paused = store.paused === true
|
const paused = row?.pauseAfterCurrent === true
|
||||||
|| row?.pauseAfterCurrent === true
|
|
||||||
|| row?.pausedByUser === true
|
|| row?.pausedByUser === true
|
||||||
|| queue?.stopAfterCurrent === true
|
|| queue?.stopAfterCurrent === true
|
||||||
|| queue?.status === 'paused'
|
|
||||||
if (paused) {
|
if (paused) {
|
||||||
library.stopAfterCurrent = true
|
library.stopAfterCurrent = true
|
||||||
library.queueAutoRun = false
|
library.queueAutoRun = false
|
||||||
@@ -385,7 +403,12 @@ function pickHeld(jobs: StudioJob[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function pickWaiting(jobs: StudioJob[]) {
|
function pickWaiting(jobs: StudioJob[]) {
|
||||||
return jobs.find(job => job.status === 'waiting') || null
|
const waiting = jobs.filter(job => job.status === 'waiting')
|
||||||
|
if (!waiting.length) return null
|
||||||
|
if (jobs.some(job => job.pausedByUser === true)) {
|
||||||
|
return waiting.reduce((latest, job) => job.createdAt >= latest.createdAt ? job : latest)
|
||||||
|
}
|
||||||
|
return waiting[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function kickStudioQueue() {
|
export function kickStudioQueue() {
|
||||||
@@ -404,13 +427,13 @@ function pendingAlive(job: StudioJob) {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
function repairStaleJobs(jobs: StudioJob[], paused: boolean) {
|
function repairStaleJobs(jobs: StudioJob[]) {
|
||||||
for (const job of jobs) {
|
for (const job of jobs) {
|
||||||
if (job.status !== 'running') continue
|
if (job.status !== 'running') continue
|
||||||
const live = job.liveJobId ? getJob(job.liveJobId) : undefined
|
const live = job.liveJobId ? getJob(job.liveJobId) : undefined
|
||||||
const liveBusy = live && (live.status === 'queued' || live.status === 'uploading' || live.status === 'running')
|
const liveBusy = live && (live.status === 'queued' || live.status === 'uploading' || live.status === 'running')
|
||||||
if (liveBusy || pendingAlive(job)) continue
|
if (liveBusy || pendingAlive(job)) continue
|
||||||
const holdPause = paused || job.pauseAfterCurrent === true || job.pausedByUser === true
|
const holdPause = job.pauseAfterCurrent === true || job.pausedByUser === true
|
||||||
if (job.shotQueueId) {
|
if (job.shotQueueId) {
|
||||||
job.status = 'held'
|
job.status = 'held'
|
||||||
job.liveJobId = undefined
|
job.liveJobId = undefined
|
||||||
@@ -439,15 +462,16 @@ async function dispatchStudioQueue() {
|
|||||||
if (videoJobsBusy()) return
|
if (videoJobsBusy()) return
|
||||||
for (const owner of listOwnersWithStudioQueues()) {
|
for (const owner of listOwnersWithStudioQueues()) {
|
||||||
const store = await mutateStore(owner, (current) => {
|
const store = await mutateStore(owner, (current) => {
|
||||||
repairStaleJobs(current.jobs, current.paused === true)
|
migrateGlobalPause(current)
|
||||||
|
repairStaleJobs(current.jobs)
|
||||||
const next = pruneDone(current.jobs)
|
const next = pruneDone(current.jobs)
|
||||||
current.jobs.splice(0, current.jobs.length, ...next)
|
current.jobs.splice(0, current.jobs.length, ...next)
|
||||||
|
syncPausedFlag(current)
|
||||||
return {
|
return {
|
||||||
paused: current.paused === true,
|
paused: current.paused === true,
|
||||||
jobs: current.jobs.map(item => structuredClone(item))
|
jobs: current.jobs.map(item => structuredClone(item))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (store.paused) return
|
|
||||||
const cutIn = pickCutIn(store.jobs)
|
const cutIn = pickCutIn(store.jobs)
|
||||||
if (cutIn) {
|
if (cutIn) {
|
||||||
await startStudioJob(cutIn)
|
await startStudioJob(cutIn)
|
||||||
@@ -654,10 +678,10 @@ export async function onLiveVideoSettled(job: Job) {
|
|||||||
const rowNow = snapshot.jobs.find(item => item.liveJobId === job.id)
|
const rowNow = snapshot.jobs.find(item => item.liveJobId === job.id)
|
||||||
|| snapshot.jobs.find(item => item.status === 'running' && item.shotQueueId && item.shotQueueId === job.library?.queueId)
|
|| snapshot.jobs.find(item => item.status === 'running' && item.shotQueueId && item.shotQueueId === job.library?.queueId)
|
||||||
|| snapshot.jobs.find(item => item.status === 'running' && !job.library?.queueId)
|
|| snapshot.jobs.find(item => item.status === 'running' && !job.library?.queueId)
|
||||||
const userPause = !failed && (
|
const userPause = !failed && rowNow?.holdForCutIn !== true && (
|
||||||
rowNow?.pauseAfterCurrent === true
|
rowNow?.pauseAfterCurrent === true
|
||||||
|| (snapshot.paused && !rowNow?.holdForCutIn)
|
|| rowNow?.pausedByUser === true
|
||||||
|| (job.library?.stopAfterCurrent === true && !rowNow?.holdForCutIn)
|
|| job.library?.stopAfterCurrent === true
|
||||||
)
|
)
|
||||||
const cutInHold = !failed && remaining > 0 && rowNow?.holdForCutIn === true && !userPause
|
const cutInHold = !failed && remaining > 0 && rowNow?.holdForCutIn === true && !userPause
|
||||||
const interrupted = remaining > 0 && (userPause || cutInHold)
|
const interrupted = remaining > 0 && (userPause || cutInHold)
|
||||||
@@ -666,9 +690,11 @@ export async function onLiveVideoSettled(job: Job) {
|
|||||||
}
|
}
|
||||||
if (userPause && remaining > 0) {
|
if (userPause && remaining > 0) {
|
||||||
await mutateStore(owner, (store) => {
|
await mutateStore(owner, (store) => {
|
||||||
store.paused = true
|
|
||||||
const row = store.jobs.find(item => item.liveJobId === job.id) || store.jobs.find(item => item.status === 'running')
|
const row = store.jobs.find(item => item.liveJobId === job.id) || store.jobs.find(item => item.status === 'running')
|
||||||
if (!row) return
|
if (!row) {
|
||||||
|
syncPausedFlag(store)
|
||||||
|
return
|
||||||
|
}
|
||||||
row.status = 'held'
|
row.status = 'held'
|
||||||
row.liveJobId = undefined
|
row.liveJobId = undefined
|
||||||
row.pauseAfterCurrent = false
|
row.pauseAfterCurrent = false
|
||||||
@@ -676,6 +702,7 @@ export async function onLiveVideoSettled(job: Job) {
|
|||||||
row.holdForCutIn = false
|
row.holdForCutIn = false
|
||||||
row.resumeAutoRun = row.payload.queueAutoRun === true || row.resumeAutoRun === true
|
row.resumeAutoRun = row.payload.queueAutoRun === true || row.resumeAutoRun === true
|
||||||
row.updatedAt = Date.now()
|
row.updatedAt = Date.now()
|
||||||
|
syncPausedFlag(store)
|
||||||
})
|
})
|
||||||
} else if (cutInHold) {
|
} else if (cutInHold) {
|
||||||
await mutate(owner, (jobs) => {
|
await mutate(owner, (jobs) => {
|
||||||
@@ -689,9 +716,11 @@ export async function onLiveVideoSettled(job: Job) {
|
|||||||
})
|
})
|
||||||
} else if (userPause) {
|
} else if (userPause) {
|
||||||
await mutateStore(owner, (store) => {
|
await mutateStore(owner, (store) => {
|
||||||
store.paused = true
|
|
||||||
const row = store.jobs.find(item => item.liveJobId === job.id) || store.jobs.find(item => item.status === 'running')
|
const row = store.jobs.find(item => item.liveJobId === job.id) || store.jobs.find(item => item.status === 'running')
|
||||||
if (!row) return
|
if (!row) {
|
||||||
|
syncPausedFlag(store)
|
||||||
|
return
|
||||||
|
}
|
||||||
row.status = job.status === 'cancelled'
|
row.status = job.status === 'cancelled'
|
||||||
? 'cancelled'
|
? 'cancelled'
|
||||||
: job.status === 'complete'
|
: job.status === 'complete'
|
||||||
@@ -704,6 +733,7 @@ export async function onLiveVideoSettled(job: Job) {
|
|||||||
row.pausedByUser = false
|
row.pausedByUser = false
|
||||||
row.lastError = job.error
|
row.lastError = job.error
|
||||||
row.updatedAt = Date.now()
|
row.updatedAt = Date.now()
|
||||||
|
syncPausedFlag(store)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const status = job.status === 'cancelled'
|
const status = job.status === 'cancelled'
|
||||||
@@ -737,11 +767,17 @@ export async function toggleStudioQueuePause(owner: string, options: {
|
|||||||
const running = storeNow.jobs.find(job => job.status === 'running')
|
const running = storeNow.jobs.find(job => job.status === 'running')
|
||||||
const target = options.jobId
|
const target = options.jobId
|
||||||
? storeNow.jobs.find(job => job.id === options.jobId)
|
? storeNow.jobs.find(job => job.id === options.jobId)
|
||||||
|
: options.queueId
|
||||||
|
? storeNow.jobs.find(job => job.shotQueueId === options.queueId)
|
||||||
: running || storeNow.jobs.find(job => job.pauseAfterCurrent || job.pausedByUser)
|
: running || storeNow.jobs.find(job => job.pauseAfterCurrent || job.pausedByUser)
|
||||||
const queueArmed = options.queueId
|
const queueArmed = options.queueId
|
||||||
? listShotQueues(owner).some(queue => queue.id === options.queueId && queue.stopAfterCurrent === true)
|
? listShotQueues(owner).some(queue => queue.id === options.queueId && queue.stopAfterCurrent === true)
|
||||||
|
: target?.shotQueueId
|
||||||
|
? listShotQueues(owner).some(queue => queue.id === target.shotQueueId && queue.stopAfterCurrent === true)
|
||||||
: false
|
: false
|
||||||
const armed = target?.pauseAfterCurrent === true || queueArmed || (storeNow.paused && !running)
|
const armed = target?.pauseAfterCurrent === true
|
||||||
|
|| target?.pausedByUser === true
|
||||||
|
|| queueArmed
|
||||||
const shouldPause = options.resume === true ? false : options.resume === false ? true : !armed
|
const shouldPause = options.resume === true ? false : options.resume === false ? true : !armed
|
||||||
|
|
||||||
if (!shouldPause) {
|
if (!shouldPause) {
|
||||||
@@ -750,55 +786,75 @@ export async function toggleStudioQueuePause(owner: string, options: {
|
|||||||
&& Boolean(job.shotQueueId)
|
&& Boolean(job.shotQueueId)
|
||||||
&& job.pausedByUser === true
|
&& job.pausedByUser === true
|
||||||
&& (!options.jobId || job.id === options.jobId)
|
&& (!options.jobId || job.id === options.jobId)
|
||||||
|
&& (!options.queueId || job.shotQueueId === options.queueId)
|
||||||
))
|
))
|
||||||
const restored = await mutateStore(owner, (store) => {
|
const restored = await mutateStore(owner, (store) => {
|
||||||
store.paused = false
|
|
||||||
for (const job of store.jobs) {
|
for (const job of store.jobs) {
|
||||||
if (options.jobId && job.id !== options.jobId) continue
|
if (options.jobId && job.id !== options.jobId) continue
|
||||||
|
if (options.queueId && job.shotQueueId !== options.queueId) continue
|
||||||
job.pauseAfterCurrent = false
|
job.pauseAfterCurrent = false
|
||||||
job.pausedByUser = false
|
job.pausedByUser = false
|
||||||
}
|
}
|
||||||
if (!options.jobId) {
|
syncPausedFlag(store)
|
||||||
for (const job of store.jobs) {
|
|
||||||
job.pauseAfterCurrent = false
|
|
||||||
job.pausedByUser = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return structuredClone(store)
|
return structuredClone(store)
|
||||||
})
|
})
|
||||||
const rows = options.jobId
|
const rows = options.jobId
|
||||||
? restored.jobs.filter(job => job.id === options.jobId)
|
? restored.jobs.filter(job => job.id === options.jobId)
|
||||||
|
: options.queueId
|
||||||
|
? restored.jobs.filter(job => job.shotQueueId === options.queueId)
|
||||||
: restored.jobs
|
: restored.jobs
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
if (row.liveJobId) applyLiveJobPause(getJob(row.liveJobId), false, row.payload.queueAutoRun === true || row.resumeAutoRun === true)
|
if (row.liveJobId) applyLiveJobPause(getJob(row.liveJobId), false, row.payload.queueAutoRun === true || row.resumeAutoRun === true)
|
||||||
if (row.shotQueueId) await setShotQueuePause(owner, row.shotQueueId, false).catch(() => null)
|
if (row.shotQueueId) await setShotQueuePause(owner, row.shotQueueId, false).catch(() => null)
|
||||||
}
|
}
|
||||||
if (!options.jobId) {
|
if (!options.jobId && !options.queueId) {
|
||||||
for (const queue of listShotQueues(owner)) {
|
for (const queue of listShotQueues(owner)) {
|
||||||
if (queue.stopAfterCurrent) await setShotQueuePause(owner, queue.id, false).catch(() => null)
|
if (queue.stopAfterCurrent) await setShotQueuePause(owner, queue.id, false).catch(() => null)
|
||||||
}
|
}
|
||||||
|
} else if (options.queueId) {
|
||||||
|
const rowHasQueue = rows.some(job => job.shotQueueId === options.queueId)
|
||||||
|
if (!rowHasQueue) await setShotQueuePause(owner, options.queueId, false).catch(() => null)
|
||||||
}
|
}
|
||||||
if (heldPaused[0] && !videoJobsBusy()) {
|
if (heldPaused[0] && !videoJobsBusy()) {
|
||||||
await resumeHeldStudioJob(heldPaused[0]).catch(() => null)
|
await resumeHeldStudioJob(heldPaused[0]).catch(() => null)
|
||||||
} else {
|
} else {
|
||||||
|
if (heldPaused.length && videoJobsBusy()) {
|
||||||
|
await mutate(owner, (jobs) => {
|
||||||
|
for (const job of jobs) {
|
||||||
|
if (!heldPaused.some(item => item.id === job.id)) continue
|
||||||
|
job.holdForCutIn = true
|
||||||
|
job.pausedByUser = false
|
||||||
|
job.pauseAfterCurrent = false
|
||||||
|
job.updatedAt = Date.now()
|
||||||
|
}
|
||||||
|
}).catch(() => null)
|
||||||
|
}
|
||||||
kickStudioQueue()
|
kickStudioQueue()
|
||||||
}
|
}
|
||||||
return { paused: false, willPause: false }
|
return { paused: false, willPause: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
const updated = await mutateStore(owner, (store) => {
|
const updated = await mutateStore(owner, (store) => {
|
||||||
store.paused = true
|
|
||||||
const row = options.jobId
|
const row = options.jobId
|
||||||
? store.jobs.find(job => job.id === options.jobId)
|
? store.jobs.find(job => job.id === options.jobId)
|
||||||
|
: options.queueId
|
||||||
|
? store.jobs.find(job => job.shotQueueId === options.queueId)
|
||||||
: store.jobs.find(job => job.status === 'running')
|
: store.jobs.find(job => job.status === 'running')
|
||||||
if (row) {
|
if (row) {
|
||||||
if (row.status !== 'running' && row.status !== 'held') {
|
if (row.status !== 'running' && row.status !== 'held') {
|
||||||
throw createError({ statusCode: 409, statusMessage: 'Nothing is generating on that job' })
|
throw createError({ statusCode: 409, statusMessage: 'Nothing is generating on that job' })
|
||||||
}
|
}
|
||||||
row.pauseAfterCurrent = row.status === 'running'
|
if (row.status === 'running') {
|
||||||
|
row.pauseAfterCurrent = true
|
||||||
|
} else {
|
||||||
|
row.pausedByUser = true
|
||||||
|
row.pauseAfterCurrent = false
|
||||||
|
row.holdForCutIn = false
|
||||||
|
}
|
||||||
row.resumeAutoRun = row.payload.queueAutoRun === true || row.resumeAutoRun === true
|
row.resumeAutoRun = row.payload.queueAutoRun === true || row.resumeAutoRun === true
|
||||||
row.updatedAt = Date.now()
|
row.updatedAt = Date.now()
|
||||||
}
|
}
|
||||||
|
syncPausedFlag(store)
|
||||||
return {
|
return {
|
||||||
paused: store.paused,
|
paused: store.paused,
|
||||||
job: row ? structuredClone(row) : null
|
job: row ? structuredClone(row) : null
|
||||||
@@ -812,7 +868,13 @@ export async function toggleStudioQueuePause(owner: string, options: {
|
|||||||
}
|
}
|
||||||
for (const pending of listPendingJobs()) {
|
for (const pending of listPendingJobs()) {
|
||||||
if (pending.ownerKey !== owner) continue
|
if (pending.ownerKey !== owner) continue
|
||||||
if (queueId && pending.queueId && pending.queueId !== queueId) continue
|
if (queueId) {
|
||||||
|
if (pending.queueId !== queueId) continue
|
||||||
|
} else if (updated.job?.liveJobId) {
|
||||||
|
if (pending.jobId !== updated.job.liveJobId) continue
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
patchPendingJob(pending.jobId, { stopAfterCurrent: true, queueAutoRun: false })
|
patchPendingJob(pending.jobId, { stopAfterCurrent: true, queueAutoRun: false })
|
||||||
}
|
}
|
||||||
const queued = queueId ? getShotQueue(owner, queueId) : null
|
const queued = queueId ? getShotQueue(owner, queueId) : null
|
||||||
@@ -832,19 +894,27 @@ export async function toggleStudioQueuePause(owner: string, options: {
|
|||||||
if (!updated.job && !queueId && !anyGenerating) {
|
if (!updated.job && !queueId && !anyGenerating) {
|
||||||
throw createError({ statusCode: 409, statusMessage: 'Nothing is generating, so there is nothing to pause after' })
|
throw createError({ statusCode: 409, statusMessage: 'Nothing is generating, so there is nothing to pause after' })
|
||||||
}
|
}
|
||||||
return { paused: true, willPause: true, jobId: updated.job?.id, queueId }
|
return {
|
||||||
|
paused: true,
|
||||||
|
willPause: updated.job?.status === 'running' || Boolean(queued?.status === 'running') || anyGenerating,
|
||||||
|
jobId: updated.job?.id,
|
||||||
|
queueId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function onStudioQueueBurstStarted(owner: string, queueId: string, liveJobId: string) {
|
export async function onStudioQueueBurstStarted(owner: string, queueId: string, liveJobId: string) {
|
||||||
await mutateStore(owner, (store) => {
|
await mutateStore(owner, (store) => {
|
||||||
const row = store.jobs.find(job => job.shotQueueId === queueId)
|
const row = store.jobs.find(job => job.shotQueueId === queueId)
|
||||||
if (!row) return
|
if (!row) {
|
||||||
store.paused = false
|
syncPausedFlag(store)
|
||||||
|
return
|
||||||
|
}
|
||||||
row.status = 'running'
|
row.status = 'running'
|
||||||
row.liveJobId = liveJobId
|
row.liveJobId = liveJobId
|
||||||
row.pauseAfterCurrent = false
|
row.pauseAfterCurrent = false
|
||||||
row.pausedByUser = false
|
row.pausedByUser = false
|
||||||
row.holdForCutIn = false
|
row.holdForCutIn = false
|
||||||
row.updatedAt = Date.now()
|
row.updatedAt = Date.now()
|
||||||
|
syncPausedFlag(store)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { isLtxWorkflow, isTextToVideo, parseVideoWorkflow, workflowForExtension,
|
|||||||
import { clipVideoPath, clipTitle, deleteRetryDraft, extendTempDir, getClip, nextClipPartName, removeExtendTemp, stillPath } from '~/server/utils/library'
|
import { clipVideoPath, clipTitle, deleteRetryDraft, extendTempDir, getClip, nextClipPartName, removeExtendTemp, stillPath } from '~/server/utils/library'
|
||||||
import { extractLastFrame, probeHasAudio } from '~/server/utils/ffmpeg'
|
import { extractLastFrame, probeHasAudio } from '~/server/utils/ffmpeg'
|
||||||
import { ensureComfyReady } from '~/server/utils/comfyLifecycle'
|
import { ensureComfyReady } from '~/server/utils/comfyLifecycle'
|
||||||
import { onLiveVideoSettled, onStudioQueueBurstStarted, isStudioQueuePaused } from '~/server/utils/studioQueue'
|
import { onLiveVideoSettled, onStudioQueueBurstStarted } from '~/server/utils/studioQueue'
|
||||||
import {
|
import {
|
||||||
finishQueueBurst,
|
finishQueueBurst,
|
||||||
getShotQueue,
|
getShotQueue,
|
||||||
@@ -241,7 +241,6 @@ function seedCurrentVideo(job: Job) {
|
|||||||
function chainShouldHold(job: Job) {
|
function chainShouldHold(job: Job) {
|
||||||
if (!job.library) return false
|
if (!job.library) return false
|
||||||
if (job.library.stopAfterCurrent) return true
|
if (job.library.stopAfterCurrent) return true
|
||||||
if (isStudioQueuePaused(job.library.ownerKey)) return true
|
|
||||||
const queue = job.library.queueId
|
const queue = job.library.queueId
|
||||||
? getShotQueue(job.library.ownerKey, job.library.queueId)
|
? getShotQueue(job.library.ownerKey, job.library.queueId)
|
||||||
: null
|
: null
|
||||||
|
|||||||
Reference in New Issue
Block a user