Add sonarr.sh + skill for TV library manual-import migration
claude-homelab's Sonarr skill only covers search/add/remove, not the
manual-import workflow the Jellyfin migration depends on. Wraps
lookup/add/scan/import into a script matching this repo's
portainer.sh conventions, replacing repetitive raw curl calls.
Also documents a naming-token gotcha hit while migrating the first
two pilot shows: Sonarr's series folder format needs the combined
{Series TitleYear} token, not {Series Title} ({Year}) - the latter
silently drops the year.
Claude-Session: https://claude.ai/code/session_01HZQK6jHmdTpFjFZM8FUnqA
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
---
|
||||
name: sonarr
|
||||
description: Use Sonarr to add TV shows and manually import existing raw-named folders into the Show Name (Year)/Season NN/... layout Jellyfin and Plex expect. Use when migrating the TV library, importing a newly-downloaded show, or otherwise interacting with this repo's Sonarr instance.
|
||||
---
|
||||
|
||||
# Sonarr library migration/import
|
||||
|
||||
This repo's Sonarr manages TV show organization for both Jellyfin and Plex
|
||||
(they share the same `nas_media` library at `/data/video/tv`). The
|
||||
`claude-homelab` plugin's Sonarr skill (if installed) only covers
|
||||
search/add/remove — it has no manual-import support. This skill's `sonarr.sh`
|
||||
covers that gap: matching an existing raw-named folder to the right
|
||||
show/episodes and importing it into place.
|
||||
|
||||
Always use `./sonarr.sh` (repo root) for this — never hand-roll the Sonarr API
|
||||
calls, the folder-naming token gotcha below has already bitten this workflow
|
||||
once.
|
||||
|
||||
## Workflow: migrate one existing show
|
||||
|
||||
1. **Find the TVDB match**: `./sonarr.sh lookup "<show name>"` — prints
|
||||
candidates as `tvdb:<id> Title (Year) status`. Pick the right one (check
|
||||
year/status against what's actually in the folder).
|
||||
2. **Add it**: `./sonarr.sh add tvdb:<id>` — adds unmonitored, no search, root
|
||||
folder defaults to `/data/video/tv`. Skip if already in
|
||||
`./sonarr.sh list`.
|
||||
3. **Preview the import**: `./sonarr.sh scan "<raw folder path>"` — shows how
|
||||
each file will map to season/episode, and flags anything Sonarr couldn't
|
||||
parse (`!! <rejection reason>`). **Read this before importing** — don't
|
||||
skip straight to step 4.
|
||||
4. **Apply it**: `./sonarr.sh import "<raw folder path>"` — re-scans and
|
||||
imports only if every file matched cleanly (refuses and tells you to check
|
||||
`scan` output otherwise, rather than partially importing). Files are
|
||||
hardlinked (not copied) into
|
||||
`/data/video/tv/Show Name (Year)/Season NN/Show Name - SxxExx - Title Quality.ext`
|
||||
— no extra disk usage, original folder is left behind but empty.
|
||||
5. **Clean up the empty source folder**:
|
||||
`docker exec jellyfin rmdir "<raw folder path>"` (any container with the
|
||||
`nas_media` mount works).
|
||||
6. **Verify Plex picks it up correctly** — this is not automatic, see below.
|
||||
|
||||
## Verifying the Plex side (do this every time, not just once)
|
||||
|
||||
Moving a file changes its path. Plex's database still points at the *old*
|
||||
path until it rescans — it does not watch the filesystem live here. After
|
||||
every import:
|
||||
|
||||
```bash
|
||||
TOKEN=$(docker exec plex grep -o 'PlexOnlineToken="[^"]*"' \
|
||||
"/config/Library/Application Support/Plex Media Server/Preferences.xml" | cut -d'"' -f2)
|
||||
curl -s "http://localhost:32400/library/sections/2/refresh?X-Plex-Token=$TOKEN"
|
||||
```
|
||||
|
||||
(Section `2` is "TV Shows" in this Plex instance — confirm with
|
||||
`curl -s "http://localhost:32400/library/sections?X-Plex-Token=$TOKEN"` if
|
||||
unsure.) Then spot-check that the show's episodes still show the correct
|
||||
`viewCount`/watch state pointing at the new file path — Plex re-matches by
|
||||
its own metadata agent (title/GUID), not by the old path, so watched status
|
||||
normally survives a move, but confirm it rather than assuming, especially for
|
||||
messier shows (duplicate releases, specials, date-based naming).
|
||||
|
||||
## Known gotcha: `{Year}` is not a valid Sonarr token
|
||||
|
||||
The series folder format must use the combined token `{Series TitleYear}`
|
||||
(produces `Title (Year)` as one unit) — `{Series Title} ({Year})` silently
|
||||
produces `Title ()` with no year, because Sonarr has no standalone `{Year}`
|
||||
token for series-level naming. This bit the first two shows migrated
|
||||
(`Andor`, `3 Body Problem`) before being caught — check
|
||||
`docker exec sonarr curl -s http://localhost:8989/api/v3/config/naming/examples -H "X-Api-Key: $SONARR_API_KEY"`
|
||||
(or `curl "$SONARR_URL/api/v3/config/naming/examples" -H "X-Api-Key: $SONARR_API_KEY"`
|
||||
from the host) any time naming config changes — `seriesFolderExample` should
|
||||
show a real year, not `()`.
|
||||
|
||||
## Edge cases that will need manual handling (not automatable via scan/import)
|
||||
|
||||
- **Date-based shows** (e.g. Jeopardy) — no season/episode numbers in
|
||||
filenames. Set the show's Series Type to "Daily" in the Sonarr UI before
|
||||
scanning; `scan` will otherwise reject every file.
|
||||
- **Specials mixed into a season folder** (e.g. Lower Decks `S00E501`-style
|
||||
files) — Sonarr should route these to `Season 00` correctly; verify rather
|
||||
than assume.
|
||||
- **Duplicate episodes from two releases** (e.g. Poker Face) — decide which
|
||||
release to keep before importing; `scan` will show both, `import` will
|
||||
refuse until resolved since it won't guess.
|
||||
- **Combined/ambiguous episode files** (e.g. `e01-02.` with no season number)
|
||||
— `scan` will likely reject these; use the Sonarr web UI's Manual Import
|
||||
screen instead, which allows manually assigning an episode range per file
|
||||
(the CLI script only handles clean auto-matches by design, so it can't
|
||||
silently mis-import something ambiguous).
|
||||
|
||||
## Other commands
|
||||
|
||||
- `./sonarr.sh list` — series currently in Sonarr
|
||||
- `./sonarr.sh rootfolders` — root folders + how many unmapped (not-yet-added)
|
||||
folders each has
|
||||
- `./sonarr.sh queue` — current download queue
|
||||
- `./sonarr.sh test-client` — tests configured download client connections
|
||||
(currently just qBittorrent_vpn, category `tv-sonarr`)
|
||||
|
||||
## Credentials
|
||||
|
||||
`SONARR_API_KEY` / `SONARR_URL` live in this repo's `.credentials` (source it
|
||||
before running `sonarr.sh` manually outside the script — the script sources
|
||||
it itself). Get a fresh key from Sonarr UI → Settings → General → Security,
|
||||
or `docker exec sonarr grep -o 'ApiKey>[^<]*' /config/config.xml`.
|
||||
Reference in New Issue
Block a user