From 6d8a8fa6d6ba72128009542586ce5037e52b5a81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20F=C3=B6rtsch?= Date: Mon, 13 Apr 2026 15:26:48 +0200 Subject: [PATCH] drop the subtitle-languages setting, it never influenced extraction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit analyzer removes every subtitle unconditionally (see case 'Subtitle' in decideAction) and the pipeline extracts all of them to sidecars — the config was purely informational and only subtitles.ts echoed it back as 'keepLanguages' for a subtitle-manager ui that doesn't exist yet. we'll revive language preferences inside that manager when it ships. removes: the settings card + ui state, POST /api/settings/subtitle-languages, the config default, the SUBTITLE_LANGUAGES env mapping, AnalyzerConfig's subtitleLanguages field, RescanConfig's subtitleLanguages field, every caller site (scan.ts / execute.ts / review.ts), and the keepLanguages surface in subtitles.ts. Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/memory/MEMORY.md | 3 +++ package.json | 2 +- server/api/execute.ts | 1 - server/api/review.ts | 7 +------ server/api/scan.ts | 2 -- server/api/settings.ts | 8 -------- server/api/subtitles.ts | 13 ++----------- server/db/index.ts | 4 +--- server/db/schema.ts | 1 - server/services/__tests__/analyzer.test.ts | 14 -------------- server/services/analyzer.ts | 1 - server/services/rescan.ts | 3 +-- src/features/settings/SettingsPage.tsx | 22 ---------------------- 13 files changed, 9 insertions(+), 72 deletions(-) diff --git a/.claude/memory/MEMORY.md b/.claude/memory/MEMORY.md index 3f77306..7f808ad 100644 --- a/.claude/memory/MEMORY.md +++ b/.claude/memory/MEMORY.md @@ -75,6 +75,9 @@ mise exec bun -- bun run build # build frontend to dist/ mise exec bun -- bun start # production: Hono serves dist/ + API on :3000 ``` +## Forward-looking rules +- [Schema changes need migrations going forward](feedback_schema_migrations.md) — resurrect the try/catch ALTER TABLE pattern in `server/db/index.ts` whenever touching table columns + ## Workflow rules - **Always bump version** in `package.json` before committing/pushing. CalVer with dot-suffix per global AGENTS.md (`YYYY.MM.DD.N`). `.gitea/workflows/build.yml` tags a Docker image with this version, so the `+N` form breaks CI with `invalid reference format`. diff --git a/package.json b/package.json index 4f6f75d..260869d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "netfelix-audio-fix", - "version": "2026.04.13.6", + "version": "2026.04.13.7", "scripts": { "dev:server": "NODE_ENV=development bun --hot server/index.tsx", "dev:client": "vite", diff --git a/server/api/execute.ts b/server/api/execute.ts index 25770f4..77de049 100644 --- a/server/api/execute.ts +++ b/server/api/execute.ts @@ -71,7 +71,6 @@ async function refreshItemFromJellyfin(itemId: number): Promise { db, fresh, { - subtitleLanguages: parseLanguageList(cfg.subtitle_languages, ["eng", "deu", "spa"]), audioLanguages: parseLanguageList(cfg.audio_languages, []), radarr: radarrEnabled ? radarrCfg : null, sonarr: sonarrEnabled ? sonarrCfg : null, diff --git a/server/api/review.ts b/server/api/review.ts index 9e07eb6..964f447 100644 --- a/server/api/review.ts +++ b/server/api/review.ts @@ -10,10 +10,6 @@ const app = new Hono(); // ─── Helpers ────────────────────────────────────────────────────────────────── -function getSubtitleLanguages(): string[] { - return parseLanguageList(getConfig("subtitle_languages"), ["eng", "deu", "spa"]); -} - function getAudioLanguages(): string[] { return parseLanguageList(getConfig("audio_languages"), []); } @@ -127,12 +123,11 @@ function reanalyze(db: ReturnType, itemId: number, preservedTitles const streams = db .prepare("SELECT * FROM media_streams WHERE item_id = ? ORDER BY stream_index") .all(itemId) as MediaStream[]; - const subtitleLanguages = getSubtitleLanguages(); const audioLanguages = getAudioLanguages(); const analysis = analyzeItem( { original_language: item.original_language, needs_review: item.needs_review, container: item.container }, streams, - { subtitleLanguages, audioLanguages }, + { audioLanguages }, ); db diff --git a/server/api/scan.ts b/server/api/scan.ts index d4ff264..1c6e9e4 100644 --- a/server/api/scan.ts +++ b/server/api/scan.ts @@ -150,7 +150,6 @@ async function runScan(limit: number | null = null): Promise { const cfg = getAllConfig(); const jellyfinCfg = { url: cfg.jellyfin_url, apiKey: cfg.jellyfin_api_key, userId: cfg.jellyfin_user_id }; - const subtitleLanguages = parseLanguageList(cfg.subtitle_languages ?? null, ["eng", "deu", "spa"]); const audioLanguages = parseLanguageList(cfg.audio_languages ?? null, []); const radarrCfg = { url: cfg.radarr_url, apiKey: cfg.radarr_api_key }; const sonarrCfg = { url: cfg.sonarr_url, apiKey: cfg.sonarr_api_key }; @@ -183,7 +182,6 @@ async function runScan(limit: number | null = null): Promise { let total = 0; const rescanCfg = { - subtitleLanguages, audioLanguages, radarr: radarrEnabled ? radarrCfg : null, sonarr: sonarrEnabled ? sonarrCfg : null, diff --git a/server/api/settings.ts b/server/api/settings.ts index 50688ff..055e758 100644 --- a/server/api/settings.ts +++ b/server/api/settings.ts @@ -83,14 +83,6 @@ app.post("/sonarr", async (c) => { return c.json({ ok: result.ok, saved: true, testError: result.ok ? undefined : result.error }); }); -app.post("/subtitle-languages", async (c) => { - const body = await c.req.json<{ langs: string[] }>(); - if (body.langs?.length > 0) { - setConfig("subtitle_languages", JSON.stringify(body.langs)); - } - return c.json({ ok: true }); -}); - app.post("/audio-languages", async (c) => { const body = await c.req.json<{ langs: string[] }>(); setConfig("audio_languages", JSON.stringify(body.langs ?? [])); diff --git a/server/api/subtitles.ts b/server/api/subtitles.ts index beb15d7..04b6a86 100644 --- a/server/api/subtitles.ts +++ b/server/api/subtitles.ts @@ -1,7 +1,7 @@ import { unlinkSync } from "node:fs"; import { dirname, resolve as resolvePath, sep } from "node:path"; import { Hono } from "hono"; -import { getAllConfig, getConfig, getDb } from "../db/index"; +import { getAllConfig, getDb } from "../db/index"; import { error as logError } from "../lib/log"; import { parseId } from "../lib/validate"; import { getItem, mapStream, normalizeLanguage, refreshItem } from "../services/jellyfin"; @@ -288,16 +288,7 @@ app.get("/summary", (c) => { isCanonical: canonicalByLang.get(r.language) === r.title, })); - // Keep languages from config - const raw = getConfig("subtitle_languages"); - let keepLanguages: string[] = []; - try { - keepLanguages = JSON.parse(raw ?? "[]"); - } catch { - /* empty */ - } - - return c.json({ embeddedCount, categories, titles, keepLanguages }); + return c.json({ embeddedCount, categories, titles }); }); // ─── Detail ────────────────────────────────────────────────────────────────── diff --git a/server/db/index.ts b/server/db/index.ts index 804dcea..7d5f5f6 100644 --- a/server/db/index.ts +++ b/server/db/index.ts @@ -21,7 +21,6 @@ const ENV_MAP: Record = { sonarr_url: "SONARR_URL", sonarr_api_key: "SONARR_API_KEY", sonarr_enabled: "SONARR_ENABLED", - subtitle_languages: "SUBTITLE_LANGUAGES", audio_languages: "AUDIO_LANGUAGES", }; @@ -32,8 +31,7 @@ function envValue(key: string): string | null { const val = process.env[envKey]; if (!val) return null; if (key.endsWith("_enabled")) return val === "1" || val.toLowerCase() === "true" ? "1" : "0"; - if (key === "subtitle_languages" || key === "audio_languages") - return JSON.stringify(val.split(",").map((s) => s.trim())); + if (key === "audio_languages") return JSON.stringify(val.split(",").map((s) => s.trim())); if (key.endsWith("_url")) return val.replace(/\/$/, ""); return val; } diff --git a/server/db/schema.ts b/server/db/schema.ts index acf5ff5..a333455 100644 --- a/server/db/schema.ts +++ b/server/db/schema.ts @@ -133,7 +133,6 @@ export const DEFAULT_CONFIG: Record = { sonarr_url: "", sonarr_api_key: "", sonarr_enabled: "0", - subtitle_languages: JSON.stringify(["eng", "deu", "spa"]), audio_languages: "[]", scan_running: "0", diff --git a/server/services/__tests__/analyzer.test.ts b/server/services/__tests__/analyzer.test.ts index 9cacaa6..7992267 100644 --- a/server/services/__tests__/analyzer.test.ts +++ b/server/services/__tests__/analyzer.test.ts @@ -35,7 +35,6 @@ describe("analyzeItem — audio keep rules", () => { stream({ id: 4, type: "Audio", stream_index: 3, codec: "aac", language: "fra" }), ]; const result = analyzeItem({ ...ITEM_DEFAULTS, original_language: "eng" }, streams, { - subtitleLanguages: [], audioLanguages: ["deu"], }); const actions = Object.fromEntries(result.decisions.map((d) => [d.stream_id, d.action])); @@ -49,7 +48,6 @@ describe("analyzeItem — audio keep rules", () => { stream({ id: 3, type: "Audio", stream_index: 2, language: "fra" }), ]; const result = analyzeItem({ ...ITEM_DEFAULTS, original_language: null, needs_review: 1 }, streams, { - subtitleLanguages: [], audioLanguages: ["deu"], }); expect(result.decisions.every((d) => d.action === "keep")).toBe(true); @@ -62,7 +60,6 @@ describe("analyzeItem — audio keep rules", () => { stream({ id: 2, type: "Audio", stream_index: 1, language: null }), ]; const result = analyzeItem({ ...ITEM_DEFAULTS, original_language: "eng" }, streams, { - subtitleLanguages: [], audioLanguages: [], }); const byId = Object.fromEntries(result.decisions.map((d) => [d.stream_id, d.action])); @@ -72,7 +69,6 @@ describe("analyzeItem — audio keep rules", () => { test("normalizes language codes (ger → deu)", () => { const streams = [stream({ id: 1, type: "Audio", stream_index: 0, language: "ger" })]; const result = analyzeItem({ ...ITEM_DEFAULTS, original_language: "deu" }, streams, { - subtitleLanguages: [], audioLanguages: [], }); expect(result.decisions[0].action).toBe("keep"); @@ -87,7 +83,6 @@ describe("analyzeItem — audio ordering", () => { stream({ id: 12, type: "Audio", stream_index: 2, codec: "aac", language: "spa" }), ]; const result = analyzeItem({ ...ITEM_DEFAULTS, original_language: "eng" }, streams, { - subtitleLanguages: [], audioLanguages: ["deu", "spa"], }); const byId = Object.fromEntries(result.decisions.map((d) => [d.stream_id, d.target_index])); @@ -103,7 +98,6 @@ describe("analyzeItem — audio ordering", () => { stream({ id: 3, type: "Audio", stream_index: 2, language: "eng" }), ]; const result = analyzeItem({ ...ITEM_DEFAULTS, original_language: "eng" }, streams, { - subtitleLanguages: [], audioLanguages: ["deu"], }); expect(result.is_noop).toBe(false); @@ -116,7 +110,6 @@ describe("analyzeItem — audio ordering", () => { stream({ id: 3, type: "Audio", stream_index: 2, codec: "aac", language: "deu" }), ]; const result = analyzeItem({ ...ITEM_DEFAULTS, original_language: "eng" }, streams, { - subtitleLanguages: [], audioLanguages: ["deu"], }); expect(result.is_noop).toBe(true); @@ -128,7 +121,6 @@ describe("analyzeItem — audio ordering", () => { stream({ id: 2, type: "Audio", stream_index: 1, codec: "aac", language: "fra" }), ]; const result = analyzeItem({ ...ITEM_DEFAULTS, original_language: "eng" }, streams, { - subtitleLanguages: [], audioLanguages: [], }); expect(result.is_noop).toBe(false); @@ -142,7 +134,6 @@ describe("analyzeItem — subtitles & is_noop", () => { stream({ id: 2, type: "Subtitle", stream_index: 1, codec: "subrip", language: "eng" }), ]; const result = analyzeItem({ ...ITEM_DEFAULTS, original_language: "eng" }, streams, { - subtitleLanguages: ["eng"], audioLanguages: [], }); const subDec = result.decisions.find((d) => d.stream_id === 2); @@ -156,7 +147,6 @@ describe("analyzeItem — subtitles & is_noop", () => { stream({ id: 2, type: "Audio", stream_index: 1, codec: "aac", language: "eng", is_default: 1 }), ]; const result = analyzeItem({ ...ITEM_DEFAULTS, original_language: "eng" }, streams, { - subtitleLanguages: [], audioLanguages: [], }); expect(result.is_noop).toBe(true); @@ -168,7 +158,6 @@ describe("analyzeItem — subtitles & is_noop", () => { stream({ id: 2, type: "Audio", stream_index: 1, codec: "aac", language: "eng", is_default: 0 }), ]; const result = analyzeItem({ ...ITEM_DEFAULTS, original_language: "eng" }, streams, { - subtitleLanguages: [], audioLanguages: [], }); expect(result.is_noop).toBe(false); @@ -180,7 +169,6 @@ describe("analyzeItem — subtitles & is_noop", () => { stream({ id: 2, type: "Audio", stream_index: 1, codec: "aac", language: "en", is_default: 1 }), ]; const result = analyzeItem({ ...ITEM_DEFAULTS, original_language: "eng" }, streams, { - subtitleLanguages: [], audioLanguages: [], }); expect(result.is_noop).toBe(false); @@ -191,7 +179,6 @@ describe("analyzeItem — transcode targets", () => { test("DTS on mp4 → transcode to eac3", () => { const streams = [stream({ id: 1, type: "Audio", stream_index: 0, codec: "dts", language: "eng" })]; const result = analyzeItem({ original_language: "eng", needs_review: 0, container: "mp4" }, streams, { - subtitleLanguages: [], audioLanguages: [], }); expect(result.decisions[0].transcode_codec).toBe("eac3"); @@ -202,7 +189,6 @@ describe("analyzeItem — transcode targets", () => { test("AAC passes through without transcode", () => { const streams = [stream({ id: 1, type: "Audio", stream_index: 0, codec: "aac", language: "eng" })]; const result = analyzeItem({ original_language: "eng", needs_review: 0, container: "mp4" }, streams, { - subtitleLanguages: [], audioLanguages: [], }); expect(result.decisions[0].transcode_codec).toBe(null); diff --git a/server/services/analyzer.ts b/server/services/analyzer.ts index 69d6489..e9bf027 100644 --- a/server/services/analyzer.ts +++ b/server/services/analyzer.ts @@ -3,7 +3,6 @@ import { computeAppleCompat, transcodeTarget } from "./apple-compat"; import { normalizeLanguage } from "./jellyfin"; export interface AnalyzerConfig { - subtitleLanguages: string[]; audioLanguages: string[]; // additional languages to keep (after OG) } diff --git a/server/services/rescan.ts b/server/services/rescan.ts index 7a25bde..8663358 100644 --- a/server/services/rescan.ts +++ b/server/services/rescan.ts @@ -6,7 +6,6 @@ import { type RadarrLibrary, getOriginalLanguage as radarrLang } from "./radarr" import { type SonarrLibrary, getOriginalLanguage as sonarrLang } from "./sonarr"; export interface RescanConfig { - subtitleLanguages: string[]; audioLanguages: string[]; radarr: { url: string; apiKey: string } | null; sonarr: { url: string; apiKey: string } | null; @@ -217,7 +216,7 @@ export async function upsertJellyfinItem( const analysis = analyzeItem( { original_language: origLang, needs_review: needsReview, container: jellyfinItem.Container ?? null }, streams, - { subtitleLanguages: cfg.subtitleLanguages, audioLanguages: cfg.audioLanguages }, + { audioLanguages: cfg.audioLanguages }, ); db diff --git a/src/features/settings/SettingsPage.tsx b/src/features/settings/SettingsPage.tsx index 2b2370a..46cfbbd 100644 --- a/src/features/settings/SettingsPage.tsx +++ b/src/features/settings/SettingsPage.tsx @@ -342,8 +342,6 @@ export function SettingsPage() { const [data, setData] = useState(settingsCache); const [loading, setLoading] = useState(settingsCache === null); const [clearStatus, setClearStatus] = useState(""); - const [subLangs, setSubLangs] = useState([]); - const [subSaved, setSubSaved] = useState(""); const [audLangs, setAudLangs] = useState([]); const [audSaved, setAudSaved] = useState(""); const langsLoadedRef = useRef(false); @@ -356,7 +354,6 @@ export function SettingsPage() { settingsCache = d; setData(d); if (!langsLoadedRef.current) { - setSubLangs(JSON.parse(d.config.subtitle_languages ?? '["eng","deu","spa"]')); setAudLangs(JSON.parse(d.config.audio_languages ?? "[]")); langsLoadedRef.current = true; } @@ -379,11 +376,6 @@ export function SettingsPage() { const saveSonarr = (url: string, apiKey: string) => api.post("/api/settings/sonarr", { url, api_key: apiKey }); - const saveSubtitleLangs = async () => { - await api.post("/api/settings/subtitle-languages", { langs: subLangs }); - setSubSaved("Saved."); - setTimeout(() => setSubSaved(""), 2000); - }; const saveAudioLangs = async () => { await api.post("/api/settings/audio-languages", { langs: audLangs }); setAudSaved("Saved."); @@ -481,20 +473,6 @@ export function SettingsPage() { - {/* Subtitle languages */} - - -
- - {subSaved && {subSaved}} -
-
- {/* Danger zone */}
Danger Zone