make done plans terminal, add ffprobe preflight to skip already-processed files
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m34s

root cause of duplicate pipeline entries: rescan.ts flipped done plans
back to pending whenever a post-job jellyfin refresh returned stale
metadata, putting the item back in review and letting a second jobs row
pile up in done. done is now sticky across rescans (error still
re-opens for retries).

second line of defense: before spawning ffmpeg, ffprobe the file and
compare audio count/language/codec order + embedded subtitle count
against the plan. if it already matches, mark the job done with the
reason in jobs.output and skip the spawn. prevents corrupting a
post-processed file with a stale stream-index command.
This commit is contained in:
2026-04-13 21:43:10 +02:00
parent c5ea37aab9
commit a06ab34b98
4 changed files with 168 additions and 2 deletions

View File

@@ -16,6 +16,7 @@ import {
waitForProcessWindow,
} from "../services/scheduler";
import { loadLibrary as loadSonarrLibrary, isUsable as sonarrUsable } from "../services/sonarr";
import { verifyDesiredState } from "../services/verify";
import type { Job, MediaItem, MediaStream } from "../types";
function parseLanguageList(raw: string | null | undefined, fallback: string[]): string[] {
@@ -403,6 +404,31 @@ async function runJob(job: Job): Promise<void> {
db.prepare("UPDATE review_plans SET status = 'error' WHERE item_id = ?").run(job.item_id);
return;
}
// Preflight: if the file already matches the plan, skip ffmpeg. Cheap
// guard against re-running a stream-index-based command against a file
// that's already been processed — which would either error out or
// silently corrupt the file.
try {
const verify = await verifyDesiredState(db, job.item_id, itemRow.file_path);
if (verify.matches) {
const msg = `Preflight check: ${verify.reason}\nSkipping FFmpeg — no work needed.`;
log(`Job ${job.id} ${msg.replace(/\n/g, " ")}`);
db.transaction(() => {
db
.prepare(
"UPDATE jobs SET status = 'done', exit_code = 0, output = ?, completed_at = datetime('now') WHERE id = ?",
)
.run(msg, job.id);
db.prepare("UPDATE review_plans SET status = 'done' WHERE item_id = ?").run(job.item_id);
})();
emitJobUpdate(job.id, "done", msg);
return;
}
log(`Job ${job.id} preflight: ${verify.reason} — running FFmpeg`);
} catch (err) {
warn(`Job ${job.id} preflight check errored: ${String(err)} — proceeding with FFmpeg`);
}
}
emitJobUpdate(job.id, "running");