pipeline card: checkboxes over actual audio streams, not a language dropdown
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m3s
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m3s
The dropdown showed every language known to LANG_NAMES — not useful because you can only keep streams that actually exist on the file. The right tool is checkboxes, one per track, pre-checked per analyzer decisions. - /api/review/pipeline now returns audio_streams[] per review item with id, language, codec, channels, title, is_default, and the current keep/remove action - PipelineCard renders one line per audio track: checkbox (bound to PATCH /:id/stream/:streamId), language, codec·channels, default badge, title, and '(Original Language)' when the stream's normalized language matches the item's OG (which itself comes from radarr/sonarr/jellyfin via the scan flow) - ReviewColumn + SeriesCard swap onLanguageChange → onToggleStream - new shared normalizeLanguageClient mirrors the server's normalize so en/eng compare equal on the client
This commit is contained in:
@@ -252,6 +252,16 @@ function recomputePlanAfterToggle(db: ReturnType<typeof getDb>, itemId: number):
|
||||
|
||||
// ─── Pipeline: summary ───────────────────────────────────────────────────────
|
||||
|
||||
interface PipelineAudioStream {
|
||||
id: number;
|
||||
language: string | null;
|
||||
codec: string | null;
|
||||
channels: number | null;
|
||||
title: string | null;
|
||||
is_default: number;
|
||||
action: "keep" | "remove";
|
||||
}
|
||||
|
||||
app.get("/pipeline", (c) => {
|
||||
const db = getDb();
|
||||
const jellyfinUrl = getConfig("jellyfin_url") ?? "";
|
||||
@@ -348,6 +358,50 @@ app.get("/pipeline", (c) => {
|
||||
item.transcode_reasons = reasonsByPlan.get(item.id) ?? [];
|
||||
}
|
||||
|
||||
// Batch-load audio streams + their current decisions so each card can
|
||||
// render pre-checked checkboxes without an extra fetch. Only audio
|
||||
// streams (video/subtitle aren't user-toggleable from the card).
|
||||
const itemIds = (review as { item_id: number }[]).map((r) => r.item_id);
|
||||
const streamsByItem = new Map<number, PipelineAudioStream[]>();
|
||||
if (itemIds.length > 0) {
|
||||
const placeholders = itemIds.map(() => "?").join(",");
|
||||
const streamRows = db
|
||||
.prepare(`
|
||||
SELECT ms.id, ms.item_id, ms.language, ms.codec, ms.channels, ms.title,
|
||||
ms.is_default, sd.action
|
||||
FROM media_streams ms
|
||||
JOIN review_plans rp ON rp.item_id = ms.item_id
|
||||
LEFT JOIN stream_decisions sd ON sd.plan_id = rp.id AND sd.stream_id = ms.id
|
||||
WHERE ms.item_id IN (${placeholders}) AND ms.type = 'Audio'
|
||||
ORDER BY ms.item_id, ms.stream_index
|
||||
`)
|
||||
.all(...itemIds) as {
|
||||
id: number;
|
||||
item_id: number;
|
||||
language: string | null;
|
||||
codec: string | null;
|
||||
channels: number | null;
|
||||
title: string | null;
|
||||
is_default: number;
|
||||
action: "keep" | "remove" | null;
|
||||
}[];
|
||||
for (const r of streamRows) {
|
||||
if (!streamsByItem.has(r.item_id)) streamsByItem.set(r.item_id, []);
|
||||
streamsByItem.get(r.item_id)!.push({
|
||||
id: r.id,
|
||||
language: r.language,
|
||||
codec: r.codec,
|
||||
channels: r.channels,
|
||||
title: r.title,
|
||||
is_default: r.is_default,
|
||||
action: r.action ?? "keep",
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const item of review as { item_id: number; audio_streams?: PipelineAudioStream[] }[]) {
|
||||
item.audio_streams = streamsByItem.get(item.item_id) ?? [];
|
||||
}
|
||||
|
||||
return c.json({ review, reviewTotal, queued, processing, done, doneCount, jellyfinUrl });
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user