diff --git a/server/api/scan.ts b/server/api/scan.ts index 83654ab..6f02745 100644 --- a/server/api/scan.ts +++ b/server/api/scan.ts @@ -164,14 +164,20 @@ async function runScan(limit: number | null = null): Promise { ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); const upsertPlan = db.prepare(` - INSERT INTO review_plans (item_id, status, is_noop, notes) - VALUES (?, 'pending', ?, ?) - ON CONFLICT(item_id) DO UPDATE SET is_noop = excluded.is_noop, notes = excluded.notes + INSERT INTO review_plans (item_id, status, is_noop, confidence, apple_compat, job_type, notes) + VALUES (?, 'pending', ?, ?, ?, ?, ?) + ON CONFLICT(item_id) DO UPDATE SET + status = CASE WHEN review_plans.status IN ('done','error') THEN 'pending' ELSE review_plans.status END, + is_noop = excluded.is_noop, + confidence = excluded.confidence, + apple_compat = excluded.apple_compat, + job_type = excluded.job_type, + notes = excluded.notes `); const upsertDecision = db.prepare(` - INSERT INTO stream_decisions (plan_id, stream_id, action, target_index) - VALUES (?, ?, ?, ?) - ON CONFLICT(plan_id, stream_id) DO UPDATE SET action = excluded.action, target_index = excluded.target_index + INSERT INTO stream_decisions (plan_id, stream_id, action, target_index, transcode_codec) + VALUES (?, ?, ?, ?, ?) + ON CONFLICT(plan_id, stream_id) DO UPDATE SET action = excluded.action, target_index = excluded.target_index, transcode_codec = excluded.transcode_codec `); const getItemByJellyfinId = db.prepare('SELECT id FROM media_items WHERE jellyfin_id = ?'); const getPlanByItemId = db.prepare('SELECT id FROM review_plans WHERE item_id = ?'); @@ -213,6 +219,16 @@ async function runScan(limit: number | null = null): Promise { if (lang) { if (origLang && normalizeLanguage(origLang) !== normalizeLanguage(lang)) needsReview = 1; origLang = lang; origLangSource = 'sonarr'; } } + // Compute confidence from source agreement + let confidence: 'high' | 'low' = 'low'; + if (!origLang) { + confidence = 'low'; // unknown language + } else if (needsReview) { + confidence = 'low'; // sources disagree + } else { + confidence = 'high'; // language known, no conflicts + } + upsertItem.run( jellyfinItem.Id, jellyfinItem.Type === 'Episode' ? 'Episode' : 'Movie', jellyfinItem.Name, jellyfinItem.SeriesName ?? null, jellyfinItem.SeriesId ?? null, @@ -233,10 +249,12 @@ async function runScan(limit: number | null = null): Promise { } const streams = getStreamsByItemId.all(itemId) as MediaStream[]; - const analysis = analyzeItem({ original_language: origLang, needs_review: needsReview }, streams, { subtitleLanguages, audioLanguages }); - upsertPlan.run(itemId, analysis.is_noop ? 1 : 0, analysis.notes); + const analysis = analyzeItem({ original_language: origLang, needs_review: needsReview, container: jellyfinItem.Container ?? null }, streams, { subtitleLanguages, audioLanguages }); + // Override base confidence with scan-computed value + const finalConfidence = confidence; + upsertPlan.run(itemId, analysis.is_noop ? 1 : 0, finalConfidence, analysis.apple_compat, analysis.job_type, analysis.notes.length > 0 ? analysis.notes.join('\n') : null); const planRow = getPlanByItemId.get(itemId) as { id: number }; - for (const dec of analysis.decisions) upsertDecision.run(planRow.id, dec.stream_id, dec.action, dec.target_index); + for (const dec of analysis.decisions) upsertDecision.run(planRow.id, dec.stream_id, dec.action, dec.target_index, dec.transcode_codec); emitSse('log', { name: jellyfinItem.Name, type: jellyfinItem.Type, status: 'scanned', file: jellyfinItem.Path }); } catch (err) {