remove review badges, add help page, auto-rename noop items
Build and Push Docker Image / build (push) Successful in 1m52s
Build and Push Docker Image / build (push) Successful in 1m52s
Three threads: 1. Drop the "⚡ Auto-approve" / "✋ Needs decision" pills on PipelineCard and the "N auto · M need decisions" subtitle on ReviewColumn — noise for a workflow that wants to be unattended. Card amber tint stays as a softer cue. Remove the now-unused reviewManualCount field on the pipeline payload. 2. New /help route in the nav. Documents what netfelix actually does end-to-end, the folder/SxxExx/ID brackets we require, and that the codec/quality/audio brackets are *arr's job — we trigger their rename API instead of parsing them ourselves. Links to TRaSH guides. 3. Refactor triggerMovieRename / triggerSeriesRename to return a basename → new-basename map instead of one path. Add a batched triggerRenameFor in execute.ts that dedupes by movie and by series (one Sonarr call covers every episode of a series). Hook into processInbox: when an item becomes noop, fire a rename pass so lying filenames on already-clean files self-heal. Idempotent — *arr returns no work to do when names already match. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+64
-21
@@ -593,37 +593,80 @@ export function parseFFmpegProgress(line: string): number | null {
|
||||
return h * 3600 + m * 60 + s;
|
||||
}
|
||||
|
||||
// ─── Post-job Radarr/Sonarr rename ───────────────────────────────────────────
|
||||
// ─── Radarr/Sonarr rename ─────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* After a successful FFmpeg job, tell Radarr/Sonarr to rescan the file and
|
||||
* rename it according to its naming convention. If the path changes, update
|
||||
* our media_items row so subsequent operations use the correct path.
|
||||
* Trigger Radarr/Sonarr rename for the given items, deduplicated: one call
|
||||
* per movie, one call per series (covers every episode of that series in
|
||||
* one shot). After each call, updates file_path on every media_items row
|
||||
* whose basename was renamed.
|
||||
*
|
||||
* Idempotent — *arr returns no work to do when filenames already match.
|
||||
* Used after a job completes (fix-up post-transcode) and after processInbox
|
||||
* classifies items as noop (catch lying filenames on already-clean files).
|
||||
*/
|
||||
async function triggerPostJobRename(itemId: number): Promise<void> {
|
||||
async function triggerRenameFor(itemIds: number[]): Promise<void> {
|
||||
if (itemIds.length === 0) return;
|
||||
const db = getDb();
|
||||
const item = db.prepare("SELECT * FROM media_items WHERE id = ?").get(itemId) as MediaItem | undefined;
|
||||
if (!item) return;
|
||||
const placeholders = itemIds.map(() => "?").join(",");
|
||||
const items = db
|
||||
.prepare(`SELECT id, type, file_path, tmdb_id, imdb_id, tvdb_id FROM media_items WHERE id IN (${placeholders})`)
|
||||
.all(...itemIds) as Pick<MediaItem, "id" | "type" | "file_path" | "tmdb_id" | "imdb_id" | "tvdb_id">[];
|
||||
|
||||
let result: { ok: boolean; newPath?: string; error?: string };
|
||||
|
||||
if (item.type === "Movie") {
|
||||
const cfg: radarr.RadarrConfig = { url: getConfig("radarr_url") ?? "", apiKey: getConfig("radarr_api_key") ?? "" };
|
||||
result = await radarr.triggerMovieRename(cfg, { tmdbId: item.tmdb_id, imdbId: item.imdb_id }, item.file_path);
|
||||
} else {
|
||||
const cfg: sonarr.SonarrConfig = { url: getConfig("sonarr_url") ?? "", apiKey: getConfig("sonarr_api_key") ?? "" };
|
||||
result = await sonarr.triggerSeriesRename(cfg, { tvdbId: item.tvdb_id }, item.file_path);
|
||||
const movies = items.filter((i) => i.type === "Movie");
|
||||
const seriesByTvdb = new Map<string, typeof items>();
|
||||
for (const ep of items.filter((i) => i.type === "Episode")) {
|
||||
if (!ep.tvdb_id) continue;
|
||||
const list = seriesByTvdb.get(ep.tvdb_id) ?? [];
|
||||
list.push(ep);
|
||||
seriesByTvdb.set(ep.tvdb_id, list);
|
||||
}
|
||||
|
||||
if (!result.ok) {
|
||||
warn(`Rename for item ${itemId}: ${result.error}`);
|
||||
return;
|
||||
const radarrCfg: radarr.RadarrConfig = { url: getConfig("radarr_url") ?? "", apiKey: getConfig("radarr_api_key") ?? "" };
|
||||
const sonarrCfg: sonarr.SonarrConfig = { url: getConfig("sonarr_url") ?? "", apiKey: getConfig("sonarr_api_key") ?? "" };
|
||||
|
||||
for (const movie of movies) {
|
||||
const result = await radarr.triggerMovieRename(radarrCfg, { tmdbId: movie.tmdb_id, imdbId: movie.imdb_id });
|
||||
if (!result.ok) {
|
||||
warn(`Rename for movie ${movie.id}: ${result.error}`);
|
||||
continue;
|
||||
}
|
||||
applyRenamesToDb(db, result.renames);
|
||||
}
|
||||
|
||||
if (result.newPath && result.newPath !== item.file_path) {
|
||||
db.prepare("UPDATE media_items SET file_path = ? WHERE id = ?").run(result.newPath, itemId);
|
||||
log(`Item ${itemId} renamed: ${item.file_path} → ${result.newPath}`);
|
||||
for (const tvdbId of seriesByTvdb.keys()) {
|
||||
const result = await sonarr.triggerSeriesRename(sonarrCfg, { tvdbId });
|
||||
if (!result.ok) {
|
||||
warn(`Rename for series tvdb=${tvdbId}: ${result.error}`);
|
||||
continue;
|
||||
}
|
||||
applyRenamesToDb(db, result.renames);
|
||||
}
|
||||
}
|
||||
|
||||
function applyRenamesToDb(db: ReturnType<typeof getDb>, renames: Map<string, string>): void {
|
||||
if (renames.size === 0) return;
|
||||
const sel = db.prepare("SELECT id, file_path FROM media_items WHERE file_path LIKE ?");
|
||||
const upd = db.prepare("UPDATE media_items SET file_path = ? WHERE id = ?");
|
||||
for (const [oldName, newName] of renames) {
|
||||
if (oldName === newName) continue;
|
||||
const rows = sel.all(`%/${oldName}`) as { id: number; file_path: string }[];
|
||||
for (const r of rows) {
|
||||
const newPath = `${r.file_path.slice(0, r.file_path.length - oldName.length)}${newName}`;
|
||||
upd.run(newPath, r.id);
|
||||
log(`Item ${r.id} renamed: ${r.file_path} → ${newPath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Fire-and-forget rename trigger. Errors are logged, never thrown. */
|
||||
export function triggerRenameForItems(itemIds: number[]): void {
|
||||
triggerRenameFor(itemIds).catch((e) => warn(`Rename batch failed: ${e}`));
|
||||
}
|
||||
|
||||
/** Single-item helper used by the per-job post-success path. */
|
||||
function triggerPostJobRename(itemId: number): Promise<void> {
|
||||
return triggerRenameFor([itemId]);
|
||||
}
|
||||
|
||||
export default app;
|
||||
|
||||
Reference in New Issue
Block a user