Add retry logic with timeout to extractLastFrame to handle temporary file access issues in shot scripts

This commit is contained in:
Towsty
2026-09-08 20:07:19 -05:00
parent 1e0cff9dea
commit da8d0e4c38
+30 -17
View File
@@ -70,29 +70,42 @@ export async function extractLastFrame(inputPath: string, outputPath: string) {
throw new Error('Source video is missing') throw new Error('Source video is missing')
} }
const attempts: string[][] = [] const maxTotalTimeMs = 5000
try { const start = Date.now()
const duration = await probeDuration(inputPath) let delayMs = 100
const seek = Math.max(0, duration - 0.08)
attempts.push(['-y', '-ss', seek.toFixed(3), '-i', inputPath, '-frames:v', '1', '-an', '-q:v', '1', outputPath])
} catch {
// Truncated files often have no duration index; fall through to decode attempts.
}
attempts.push(
['-y', '-sseof', '-0.04', '-i', inputPath, '-update', '1', '-frames:v', '1', '-an', '-q:v', '1', outputPath],
['-y', '-i', inputPath, '-update', '1', '-an', '-q:v', '1', outputPath]
)
for (const args of attempts) { while (Date.now() - start < maxTotalTimeMs) {
const attempts: string[][] = []
try { try {
await runFfmpeg(args, 60000) const duration = await probeDuration(inputPath)
if (existsSync(outputPath) && statSync(outputPath).size >= 64) return const seek = Math.max(0, duration - 0.08)
attempts.push(['-y', '-ss', seek.toFixed(3), '-i', inputPath, '-frames:v', '1', '-an', '-q:v', '1', outputPath])
} catch { } catch {
// Try the next seek strategy. // Truncated files often have no duration index; fall through to decode attempts.
} }
attempts.push(
['-y', '-sseof', '-0.04', '-i', inputPath, '-update', '1', '-frames:v', '1', '-an', '-q:v', '1', outputPath],
['-y', '-i', inputPath, '-update', '1', '-an', '-q:v', '1', outputPath]
)
for (const args of attempts) {
try {
await runFfmpeg(args, 60000)
if (existsSync(outputPath) && statSync(outputPath).size >= 64) return
} catch {
// Try next strategy
}
}
const elapsed = Date.now() - start
if (elapsed >= maxTotalTimeMs) break
const wait = Math.min(delayMs, maxTotalTimeMs - elapsed)
await new Promise(resolve => setTimeout(resolve, wait))
delayMs = Math.min(delayMs * 2, maxTotalTimeMs - (Date.now() - start))
} }
throw new Error('Could not extract the last frame. The video looks incomplete — interrupting Comfy mid-encode usually causes this. Earlier saved parts are still in the library.') throw new Error('Could not extract the last frame within the timeout. The video may still be encoding or is incomplete.')
} }
export async function extractFirstFrame(inputPath: string, outputPath: string) { export async function extractFirstFrame(inputPath: string, outputPath: string) {