From 7d783bd97e9195971fa86bb1c2e80aa6178843f7 Mon Sep 17 00:00:00 2001 From: Towsty Date: Fri, 28 Aug 2026 08:15:52 -0500 Subject: [PATCH] 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 --- components/ImagePickerModal.vue | 25 +++++-- pages/index.vue | 28 ++++++++ server/api/library/clips/[id]/video.get.ts | 11 ++-- server/api/media.get.ts | 10 +-- server/plugins/resume-queues.ts | 5 +- server/utils/ffmpeg.ts | 56 +++++++++++++++- server/utils/httpRange.ts | 69 +++++++++++++++++++ server/utils/library.ts | 3 +- server/utils/pending.ts | 11 ++++ server/utils/studioQueue.ts | 77 +++++++++++++++++++--- server/utils/videoChain.ts | 49 +++++++++----- server/utils/watch.ts | 43 +++++++++++- 12 files changed, 338 insertions(+), 49 deletions(-) create mode 100644 server/utils/httpRange.ts diff --git a/components/ImagePickerModal.vue b/components/ImagePickerModal.vue index ca7af6c..2b2c0a2 100644 --- a/components/ImagePickerModal.vue +++ b/components/ImagePickerModal.vue @@ -90,7 +90,8 @@
{{ still.name || still.filename }} - +
@@ -190,6 +191,7 @@ const activeFolderId = ref(props.folderId) const tab = ref<'input' | 'output'>('input') const revealAll = ref(false) const revealedIds = ref([]) +const picking = ref(false) function onKey(event: KeyboardEvent) { if (event.key === 'Escape') emit('close') @@ -266,7 +268,10 @@ async function ingest(file: File) { try { const result = await $fetch<{ still?: { id: string; filename: string; hideInput?: boolean } }>('/api/library/stills', { method: 'POST', body }) emit('updated', result) - if (result.still?.id) emit('pick', result.still) + if (props.stillOnly) { + if (result.still?.id) emit('pick', result.still) + return + } emit('select', file, props.hideInputPreview === true, result.still?.id) } catch (error: any) { emit('error', error?.data?.statusMessage || 'Could not save image') @@ -287,14 +292,20 @@ function onDrop(event: DragEvent) { } async function pickStill(still: PickerStill) { - emit('pick', { id: still.id, filename: still.filename, hideInput: still.hideInput }) - if (props.stillOnly) return + if (picking.value) return + picking.value = true try { + if (props.stillOnly) { + emit('pick', { id: still.id, filename: still.filename, hideInput: still.hideInput }) + return + } const blob = await $fetch(`/api/library/stills/${still.id}`, { responseType: 'blob' }) const file = new File([blob], still.filename, { type: blob.type || 'image/png' }) emit('select', file, still.hideInput === true, still.id) } catch (error: any) { emit('error', error?.data?.statusMessage || 'Could not load that still') + } finally { + picking.value = false } } diff --git a/pages/index.vue b/pages/index.vue index f568b86..d343d10 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -874,7 +874,10 @@ :autoplay="autoplayEnabled && !awaitingReveal" loop playsinline + preload="metadata" @play="onPlayed" + @loadedmetadata="onVideoMeta" + @durationchange="onVideoMeta" />