Add Library Cleanup admin page — missing files, orphaned records, stuck jobs, duplicates

New read-only scan (GET /api/library/cleanup/scan) across movies, episodes,
tracks, subtitles, HLS output, transcode queue, and watchlist/progress rows:
- Missing files: DB record exists but the file is gone from disk
- Orphaned episodes: show_id no longer exists
- Orphaned HLS output: transcoded folders left behind by deleted content
- Stuck transcode jobs: "running" 2+ hours with no completion (backend-restart artifact)
- Duplicate imports: same file imported into the library more than once
- Orphaned subtitles / dangling watchlist+progress rows

Nothing is deleted until an admin clicks Fix (per-item or Fix All per
category) via POST /api/library/cleanup/fix.
This commit is contained in:
Myron Blair
2026-07-26 21:13:16 -05:00
parent 3c47894628
commit c174ac60b1
5 changed files with 301 additions and 1 deletions
+24
View File
@@ -36,6 +36,7 @@ import radarr as radarr_client
import sonarr as sonarr_client
import services as services_client
import library_scan
import library_cleanup
import musicbrainz as musicbrainz_client
import trakt as trakt_client
import opensubtitles as opensubs_client
@@ -1510,6 +1511,29 @@ async def scan_import(payload: dict, user: dict = Depends(require_admin)):
return {"ok": True, "imported": created}
# ============ LIBRARY CLEANUP (admin) ============
@api.get("/library/cleanup/scan")
async def library_cleanup_scan(user: dict = Depends(require_admin)):
return await library_cleanup.scan(db, VIDEOS_DIR, HLS_DIR)
@api.post("/library/cleanup/fix")
async def library_cleanup_fix(payload: dict, user: dict = Depends(require_admin)):
"""Body: {category: str, items: [<finding dicts as returned by GET /library/cleanup/scan>]}"""
category = payload.get("category")
items = payload.get("items") or []
if not items:
raise HTTPException(status_code=400, detail="No items provided")
fixed = 0
for item in items:
try:
await library_cleanup.fix_one(db, VIDEOS_DIR, HLS_DIR, category, item)
fixed += 1
except Exception as e:
logger.warning(f"Library cleanup fix failed for {category}/{item.get('id')}: {e}")
return {"ok": True, "fixed": fixed}
# ============ MUSIC ============
@api.get("/tracks", response_model=List[Track])
async def list_tracks(user: dict = Depends(get_current_user)):