library: rename Scan nav/page to Library, show audio codecs per row
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m4s

Per-row audio codec summary (distinct lowercased codecs across an
item's audio streams) via scalar subquery on media_streams, rendered
as "ac3 · aac" in a new monospace Audio column.

v2026.04.15.9

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-15 19:10:00 +02:00
parent a2bdecd298
commit 7d30e6c1a6
4 changed files with 34 additions and 15 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "netfelix-audio-fix",
"version": "2026.04.15.8",
"version": "2026.04.15.9",
"scripts": {
"dev:server": "NODE_ENV=development bun --hot server/index.tsx",
"dev:client": "vite",

View File

@@ -163,7 +163,11 @@ app.get("/items", (c) => {
`
SELECT id, jellyfin_id, name, type, series_name, season_number, episode_number,
scan_status, original_language, orig_lang_source, container, file_size, file_path,
last_scanned_at, ingest_source
last_scanned_at, ingest_source,
(SELECT GROUP_CONCAT(DISTINCT LOWER(codec))
FROM media_streams
WHERE item_id = media_items.id AND type = 'Audio' AND codec IS NOT NULL
) AS audio_codecs
FROM media_items
${where.sql}
ORDER BY COALESCE(last_scanned_at, created_at) DESC, id DESC
@@ -186,6 +190,7 @@ app.get("/items", (c) => {
file_path: string;
last_scanned_at: string | null;
ingest_source: string | null;
audio_codecs: string | null;
}>;
const total = (db.prepare(`SELECT COUNT(*) as n FROM media_items ${where.sql}`).get(...where.args) as { n: number }).n;
return c.json({ rows, total, hasMore: query.offset + rows.length < total, query });

View File

@@ -37,6 +37,7 @@ interface ScanItemsRow {
file_path: string;
last_scanned_at: string | null;
ingest_source: "scan" | "webhook" | null;
audio_codecs: string | null;
}
interface ScanItemsResponse {
@@ -397,7 +398,7 @@ export function ScanPage() {
return (
<div>
<div className="flex items-center justify-between mb-4">
<h1 className="text-xl font-bold m-0">Scan</h1>
<h1 className="text-xl font-bold m-0">Library</h1>
<MqttBadge />
</div>
@@ -581,22 +582,32 @@ export function ScanPage() {
<table className="w-full border-collapse text-[0.8rem]">
<thead>
<tr>
{["Scanned", "Name", "Type", "Series / Ep", "Language", "Container", "Size", "Source", "Status", "Path"].map(
(h) => (
<th
key={h}
className="text-left text-[0.66rem] font-bold uppercase tracking-[0.05em] text-gray-500 py-1 px-2 border-b border-gray-200 whitespace-nowrap"
>
{h}
</th>
),
)}
{[
"Scanned",
"Name",
"Type",
"Series / Ep",
"Language",
"Audio",
"Container",
"Size",
"Source",
"Status",
"Path",
].map((h) => (
<th
key={h}
className="text-left text-[0.66rem] font-bold uppercase tracking-[0.05em] text-gray-500 py-1 px-2 border-b border-gray-200 whitespace-nowrap"
>
{h}
</th>
))}
</tr>
</thead>
<tbody>
{itemsRows.length === 0 && !itemsLoading && (
<tr>
<td colSpan={10} className="py-3 px-2 text-gray-400">
<td colSpan={11} className="py-3 px-2 text-gray-400">
No items match the current filters.
</td>
</tr>
@@ -616,6 +627,9 @@ export function ScanPage() {
<div>{row.original_language ?? "—"}</div>
<div className="text-[0.68rem] text-gray-500">{row.orig_lang_source ?? "—"}</div>
</td>
<td className="py-1.5 px-2 border-b border-gray-100 font-mono text-[0.72rem]">
{row.audio_codecs ? row.audio_codecs.split(",").join(" · ") : "—"}
</td>
<td className="py-1.5 px-2 border-b border-gray-100">{row.container ?? "—"}</td>
<td className="py-1.5 px-2 border-b border-gray-100 whitespace-nowrap">{formatFileSize(row.file_size)}</td>
<td className="py-1.5 px-2 border-b border-gray-100">

View File

@@ -65,7 +65,7 @@ function RootLayout() {
<VersionBadge />
<div className="flex flex-wrap items-center gap-0.5">
<NavLink to="/" exact>
Scan
Library
</NavLink>
<NavLink to="/pipeline">Pipeline</NavLink>
<NavLink to="/review/subtitles">Subtitles</NavLink>