diff --git a/src/public/index.html b/src/public/index.html index 2d23771..ccce0c5 100644 --- a/src/public/index.html +++ b/src/public/index.html @@ -49,6 +49,7 @@ .badge-episode_remediated{background:rgba(239,68,68,.2);color:var(--red);font-weight:700;} .badge-episode_corrupt{background:rgba(249,115,22,.2);color:var(--orange);font-weight:700;} .badge-integrity_checked{background:rgba(59,130,246,.15);color:var(--blue);} + .badge-integrity_verifying{background:rgba(59,130,246,.08);color:var(--blue);opacity:.75;} .activity{background:var(--surface);border:1px solid var(--border);border-radius:10px;overflow:hidden;} .activity table{width:100%;border-collapse:collapse;} .activity th{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.8px;color:var(--muted);padding:10px 16px;text-align:left;border-bottom:1px solid var(--border);} @@ -199,6 +200,9 @@

EpisodeGuardGrabbed + + + @@ -484,7 +488,7 @@

EpisodeGuard/g,'>');} diff --git a/src/sonarr.js b/src/sonarr.js index 15866cf..1d29cd3 100644 --- a/src/sonarr.js +++ b/src/sonarr.js @@ -1,5 +1,5 @@ import { env } from './config.js'; -import { getSetting, isEpisodeFileVerified, markEpisodeFileVerified, clearVerifiedFile } from './db.js'; +import { getSetting, logEvent, isEpisodeFileVerified, markEpisodeFileVerified, clearVerifiedFile } from './db.js'; import { resolveLocalPath, probeMediaFile, evaluateIntegrity } from './prober.js'; async function sonarrReq(method, path, body) { @@ -88,7 +88,7 @@ export async function getTwoSeasonEpisodes(seriesId, season) { } -export async function ensureUpcomingEpisodes(seriesId, season, episode, preloaded, queuedEpisodeIds = new Set()) { +export async function ensureUpcomingEpisodes(seriesId, season, episode, preloaded, queuedEpisodeIds = new Set(), seriesTitle = null) { const lookahead = parseInt(getSetting('lookahead_episodes'), 10); const allEps = preloaded ? [...preloaded.current, ...preloaded.next] @@ -143,6 +143,21 @@ export async function ensureUpcomingEpisodes(seriesId, season, episode, preloade console.warn(`[sonarr] Could not fetch episode file ${ep.episodeFileId}:`, err.message); } + const verifyingDetails = { + message: 'Verifying file integrity (ffprobe + tail decode)', + file: epFile?.path || null, + }; + if (logIntegrityChecks) { + logEvent({ + event_type: 'integrity_verifying', + show_title: seriesTitle || null, + season: ep.seasonNumber, + episode: ep.episodeNumber, + details: verifyingDetails, + }); + } + console.log(`[integrity] Verifying S${pad(ep.seasonNumber)}E${pad(ep.episodeNumber)}: ${epFile?.path || '(no file path)'}`); + // Determine expected runtime from episode or series (minutes to seconds) const expectedMinutes = ep.runtime || (epFile && epFile.mediaInfo && epFile.mediaInfo.runTime) || null; const expectedRuntimeSec = expectedMinutes ? expectedMinutes * 60 : null; diff --git a/src/watcher.js b/src/watcher.js index f4387b6..a191796 100644 --- a/src/watcher.js +++ b/src/watcher.js @@ -198,7 +198,7 @@ async function processEpisode(ep, series, trigger, queuedEpisodeIds = new Set()) // 1. Ensure upcoming episodes console.log('[watcher] Checking upcoming for ' + label); - const result = await ensureUpcomingEpisodes(series.id, season, episode, preloaded, queuedEpisodeIds); + const result = await ensureUpcomingEpisodes(series.id, season, episode, preloaded, queuedEpisodeIds, series.title); // Log one row per upcoming episode action for (const a of result.actions) {