openvibers/openvibe.media Commits

Clips: recover cuts interrupted by a restart

OP OpenVibers committed 18 Aug 2026 · 8f94390b58 · 1 file, +18 −0

A clip sits at status='processing' while ffmpeg runs. Restart the service
mid-cut and that ffmpeg dies with nothing left to advance the row, so it stays
'processing' forever — which the player renders as a permanent "the server is
cutting your clip". Two clips landed in exactly that state during this fix.

Mark them failed on boot. Failed is honest, visible, and re-cuttable;
'processing' is a claim with no process behind it.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Xw6Sf5zzSALw8m93XNu5JE

Changes

ddiff --git a/server/db/database.js b/server/db/database.js
iindex aa398a8..f6c2d7f 100644
---- a/server/db/database.js
++++ b/server/db/database.js
@@@ -30,9 +30,27 @@ function getDb() {
migrateColumns();
normalizeThumbnailUrls();
seedSettings();
++ recoverInterruptedClips();
return database;
}
++/**
++ * A clip is marked 'processing' while ffmpeg cuts it. If the service restarts mid-cut
++ * that ffmpeg dies, and nothing ever moves the row on — it stays 'processing' forever,
++ * which the player renders as a permanent "the server is cutting your clip".
++ *
++ * Mark them failed on boot instead: failed is honest, visible, and re-cuttable, whereas
++ * 'processing' is a lie that no longer has a process behind it.
++ */
++function recoverInterruptedClips() {
++ try {
++ const r = database.prepare(
++ "UPDATE clips SET status = 'failed' WHERE status = 'processing'"
++ ).run();
++ if (r.changes) console.log(`[DB] Recovered ${r.changes} clip(s) left mid-cut by a restart → failed (re-cuttable)`);
++ } catch { /* non-fatal */ }
++}
++
// One-time normalization: migrated rows stored old-stack thumbnail URLs
// (/api/thumbnails/<name> or absolute variants). Canonical form is /t/<name>
// — the legacy redirect route covers stragglers, but direct URLs cache better.