webhook: PascalCase payload + ItemAdded only, switch ✓✓ signal to ffprobe
Build and Push Docker Image / build (push) Successful in 1m56s
Build and Push Docker Image / build (push) Successful in 1m56s
monitoring the mqtt broker revealed two bugs and one design dead-end: 1. the jellyfin-plugin-webhook publishes pascalcase fields (NotificationType, ItemId, ItemType) and we were reading camelcase (event, itemId, itemType). every real payload was rejected by the first guard — the mqtt path never ingested anything. 2. the plugin has no ItemUpdated / Library.* notifications. file rewrites on existing items produce zero broker traffic (observed: transcode + manual refresh metadata + 'recently added' appearance → no mqtt messages). ✓✓ via webhook is structurally impossible. fix the webhook path so brand-new library items actually get ingested, and narrow ACCEPTED_EVENTS to just 'ItemAdded' (the only library-side event the plugin emits). move the ✓✓ signal from webhook-corroboration to post-execute ffprobe via the existing verifyDesiredState helper: after ffmpeg returns 0 we probe the output file ourselves and flip verified=1 on match. the preflight-skipped path sets verified=1 too. renamed the db column webhook_verified → verified (via idempotent RENAME COLUMN migration) since the signal is no longer webhook-sourced, and updated the Done column tooltip to reflect that ffprobe is doing the verification. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+23
-1
@@ -377,7 +377,9 @@ async function runJob(job: Job): Promise<void> {
|
||||
"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);
|
||||
// Preflight matched → file is already correct, so the plan is
|
||||
// both done AND independently verified. ✓✓ in the Done column.
|
||||
db.prepare("UPDATE review_plans SET status = 'done', verified = 1 WHERE item_id = ?").run(job.item_id);
|
||||
})();
|
||||
emitJobUpdate(job.id, "done", msg);
|
||||
return;
|
||||
@@ -488,6 +490,26 @@ async function runJob(job: Job): Promise<void> {
|
||||
log(`Job ${job.id} completed successfully`);
|
||||
emitJobUpdate(job.id, "done", fullOutput);
|
||||
|
||||
// Post-execute verification: ffprobe the output file and compare to
|
||||
// the plan. Independent check that ffmpeg actually produced what we
|
||||
// asked for — ffmpeg can exit 0 while having dropped a stream or
|
||||
// muxed into an unexpected layout. Sets verified=1 (the ✓✓ signal)
|
||||
// on match. If the probe disagrees, we leave verified=0 and log the
|
||||
// reason; the plan is still 'done' (the job technically succeeded)
|
||||
// but the UI will surface it as unverified so the user notices.
|
||||
if (item) {
|
||||
verifyDesiredState(db, job.item_id, item.file_path)
|
||||
.then((v) => {
|
||||
if (v.matches) {
|
||||
db.prepare("UPDATE review_plans SET verified = 1 WHERE item_id = ?").run(job.item_id);
|
||||
log(`Job ${job.id} post-verify: ${v.reason}`);
|
||||
} else {
|
||||
warn(`Job ${job.id} post-verify FAILED: ${v.reason}`);
|
||||
}
|
||||
})
|
||||
.catch((err) => warn(`Job ${job.id} post-verify errored: ${String(err)}`));
|
||||
}
|
||||
|
||||
// Fire-and-forget: tell Jellyfin to rescan the file. The MQTT subscriber
|
||||
// will pick up Jellyfin's resulting Library event and re-analyze the
|
||||
// item — flipping the plan back to 'pending' if the on-disk streams
|
||||
|
||||
Reference in New Issue
Block a user