diff --git a/package.json b/package.json index 9a6d434..7e198e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "netfelix-audio-fix", - "version": "2026.04.21.7", + "version": "2026.04.21.8", "scripts": { "dev:server": "NODE_ENV=development bun --hot server/index.tsx", "dev:client": "vite", diff --git a/server/api/review.ts b/server/api/review.ts index 0208ea2..26d16a9 100644 --- a/server/api/review.ts +++ b/server/api/review.ts @@ -2,6 +2,9 @@ import { unlinkSync } from "node:fs"; import { Hono } from "hono"; import { getAllConfig, getConfig, getDb } from "../db/index"; import { log, error as logError } from "../lib/log"; +import { parsePath } from "../services/path-parser"; +import { probeFile } from "../services/probe"; +import { upsertScannedItem } from "../services/rescan"; import { isOneOf, parseId } from "../lib/validate"; import { analyzeItem, assignTargetOrder } from "../services/analyzer"; import { buildCommand, LANG_NAMES } from "../services/ffmpeg"; @@ -1215,6 +1218,26 @@ export function sendToInbox(db: ReturnType, itemId: number): void db.prepare("DELETE FROM jobs WHERE item_id = ? AND status IN ('pending', 'done', 'error')").run(itemId); } +/** Re-probe a single file and update its media_items + media_streams. */ +async function rescanFile(db: ReturnType, itemId: number): Promise { + const item = db.prepare("SELECT file_path FROM media_items WHERE id = ?").get(itemId) as + | { file_path: string } + | undefined; + if (!item?.file_path) return; + const cfg = getAllConfig(); + const moviesRoot = cfg.movies_root || "/movies"; + const tvRoot = cfg.tv_root || "/tv"; + const parsed = parsePath(item.file_path, moviesRoot, tvRoot); + if (!parsed) return; + try { + const probe = await probeFile(item.file_path); + upsertScannedItem(db, item.file_path, parsed, probe); + } catch (err) { + logError(`Rescan failed for ${item.file_path}:`, err); + db.prepare("UPDATE media_items SET scan_status = 'error', scan_error = ? WHERE id = ?").run(String(err), itemId); + } +} + /** Send all items in Review back to inbox. */ export function unsortAll(db: ReturnType): number { const rows = db @@ -1429,17 +1452,18 @@ app.post("/:id/retry", (c) => { // running plan), this handles the post-job states and drops the lingering // job row so the pipeline doesn't show leftover history for an item that's // about to be re-sorted. -app.post("/:id/reopen", (c) => { +app.post("/:id/reopen", async (c) => { const db = getDb(); const id = parseId(c.req.param("id")); if (id == null) return c.json({ error: "invalid id" }, 400); const plan = db.prepare("SELECT * FROM review_plans WHERE item_id = ?").get(id) as ReviewPlan | undefined; if (!plan) return c.notFound(); sendToInbox(db, id); + await rescanFile(db, id); return c.json({ ok: true }); }); -app.post("/:id/unapprove", (c) => { +app.post("/:id/unapprove", async (c) => { const db = getDb(); const id = parseId(c.req.param("id")); if (id == null) return c.json({ error: "invalid id" }, 400); @@ -1451,6 +1475,7 @@ app.post("/:id/unapprove", (c) => { | undefined; if (job?.status === "running") return c.json({ ok: false, error: "Job is running — cannot send back to inbox" }, 409); sendToInbox(db, id); + await rescanFile(db, id); return c.json({ ok: true }); });