Add retry logic with timeout to extractLastFrame to handle temporary file access issues in shot scripts
This commit is contained in:
+30
-17
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user