queue column: reuse review card read-only, back-to-review instead of approve
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m52s
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m52s
Queued jobs now render the full pipeline card with locked-in audio stream checkboxes and transcode badges, so the rationale for queuing stays visible. The primary action becomes "Back to review" which unapproves the plan and moves the item back to the Review column.
This commit is contained in:
@@ -290,10 +290,16 @@ app.get("/pipeline", (c) => {
|
||||
db.prepare("SELECT COUNT(*) as n FROM review_plans WHERE status = 'pending' AND is_noop = 0").get() as { n: number }
|
||||
).n;
|
||||
|
||||
// Queued gets the same enrichment as review so the card can render
|
||||
// streams + transcode reasons read-only (with a "Back to review" button).
|
||||
const queued = db
|
||||
.prepare(`
|
||||
SELECT j.*, mi.name, mi.series_name, mi.type,
|
||||
rp.job_type, rp.apple_compat
|
||||
SELECT j.id, j.item_id, j.status, j.started_at, j.completed_at,
|
||||
mi.name, mi.series_name, mi.series_jellyfin_id, mi.jellyfin_id,
|
||||
mi.season_number, mi.episode_number, mi.type, mi.container,
|
||||
mi.original_language, mi.orig_lang_source, mi.file_path,
|
||||
rp.id as plan_id, rp.job_type, rp.apple_compat,
|
||||
rp.confidence, rp.is_noop
|
||||
FROM jobs j
|
||||
JOIN media_items mi ON mi.id = j.item_id
|
||||
JOIN review_plans rp ON rp.item_id = j.item_id
|
||||
@@ -336,35 +342,35 @@ app.get("/pipeline", (c) => {
|
||||
};
|
||||
const doneCount = noopRow.n + doneRow.n;
|
||||
|
||||
// Batch transcode reasons for all review plans in one query (avoids N+1)
|
||||
const planIds = (review as { id: number }[]).map((r) => r.id);
|
||||
const reasonsByPlan = new Map<number, string[]>();
|
||||
if (planIds.length > 0) {
|
||||
const placeholders = planIds.map(() => "?").join(",");
|
||||
// Enrich rows that have (plan_id, item_id) with the transcode-reason
|
||||
// badges and pre-checked audio streams. Used for both review and queued
|
||||
// columns so the queued card can render read-only with the same info.
|
||||
type EnrichableRow = { id?: number; plan_id?: number; item_id: number } & {
|
||||
transcode_reasons?: string[];
|
||||
audio_streams?: PipelineAudioStream[];
|
||||
};
|
||||
const enrichWithStreamsAndReasons = (rows: EnrichableRow[]) => {
|
||||
if (rows.length === 0) return;
|
||||
const planIdFor = (r: EnrichableRow): number => (r.plan_id ?? r.id) as number;
|
||||
const planIds = rows.map(planIdFor);
|
||||
const itemIds = rows.map((r) => r.item_id);
|
||||
|
||||
const reasonPh = planIds.map(() => "?").join(",");
|
||||
const allReasons = db
|
||||
.prepare(`
|
||||
SELECT DISTINCT sd.plan_id, ms.codec, sd.transcode_codec
|
||||
FROM stream_decisions sd
|
||||
JOIN media_streams ms ON ms.id = sd.stream_id
|
||||
WHERE sd.plan_id IN (${placeholders}) AND sd.transcode_codec IS NOT NULL
|
||||
WHERE sd.plan_id IN (${reasonPh}) AND sd.transcode_codec IS NOT NULL
|
||||
`)
|
||||
.all(...planIds) as { plan_id: number; codec: string | null; transcode_codec: string }[];
|
||||
const reasonsByPlan = new Map<number, string[]>();
|
||||
for (const r of allReasons) {
|
||||
if (!reasonsByPlan.has(r.plan_id)) reasonsByPlan.set(r.plan_id, []);
|
||||
reasonsByPlan.get(r.plan_id)!.push(`${(r.codec ?? "").toUpperCase()} → ${r.transcode_codec.toUpperCase()}`);
|
||||
}
|
||||
}
|
||||
for (const item of review as { id: number; transcode_reasons?: string[] }[]) {
|
||||
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 streamPh = itemIds.map(() => "?").join(",");
|
||||
const streamRows = db
|
||||
.prepare(`
|
||||
SELECT ms.id, ms.item_id, ms.language, ms.codec, ms.channels, ms.title,
|
||||
@@ -372,7 +378,7 @@ app.get("/pipeline", (c) => {
|
||||
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'
|
||||
WHERE ms.item_id IN (${streamPh}) AND ms.type = 'Audio'
|
||||
ORDER BY ms.item_id, ms.stream_index
|
||||
`)
|
||||
.all(...itemIds) as {
|
||||
@@ -385,6 +391,7 @@ app.get("/pipeline", (c) => {
|
||||
is_default: number;
|
||||
action: "keep" | "remove" | null;
|
||||
}[];
|
||||
const streamsByItem = new Map<number, PipelineAudioStream[]>();
|
||||
for (const r of streamRows) {
|
||||
if (!streamsByItem.has(r.item_id)) streamsByItem.set(r.item_id, []);
|
||||
streamsByItem.get(r.item_id)!.push({
|
||||
@@ -397,10 +404,15 @@ app.get("/pipeline", (c) => {
|
||||
action: r.action ?? "keep",
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const item of review as { item_id: number; audio_streams?: PipelineAudioStream[] }[]) {
|
||||
item.audio_streams = streamsByItem.get(item.item_id) ?? [];
|
||||
}
|
||||
|
||||
for (const r of rows) {
|
||||
r.transcode_reasons = reasonsByPlan.get(planIdFor(r)) ?? [];
|
||||
r.audio_streams = streamsByItem.get(r.item_id) ?? [];
|
||||
}
|
||||
};
|
||||
|
||||
enrichWithStreamsAndReasons(review as EnrichableRow[]);
|
||||
enrichWithStreamsAndReasons(queued as EnrichableRow[]);
|
||||
|
||||
return c.json({ review, reviewTotal, queued, processing, done, doneCount, jellyfinUrl });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user