legacy-arrflix/playbooks/import-media/README.md
s8n 24a9497e7d playbooks/ rename + import-media v1.0 + lilo&stitch run
processes/ -> playbooks/ (git mv preserves history; updated cross-refs
in ROADMAP, README, subtitles playbook + scripts).

playbooks/import-media/README.md v1.0 — 7-step import workflow:
  stage on onyx -> rsync to nullstone -> chmod -> verify scan ->
  Items/Counts bump -> optional subtitle pass -> run-log
Cross-references docs/05/07/08, ADMIN-GUIDE, README. Mirrors the
existing subtitles playbook structure (CHANGELOG + runs/_template).

CHANGELOG v1.0 lists known gaps (bin/cleanup-import.sh and
bin/normalize.py still doc-only, ROADMAP M6).

First run logged: playbooks/import-media/runs/lilo-stitch-2002.md.
Lilo & Stitch (2002) imported to /home/user/media/movies/, item
c2f4aff133c1b9631500fadf293b0b2f, TMDb 11544, MovieCount 3 -> 4.
LibraryMonitor didn't auto-fire — needed manual /Library/Refresh;
playbook updated to make this an unconditional step.

Source: 1080p BluRay HEVC 10-bit / EAC3 5.1 / 2x PGS embedded subs.
Per quality bar (README.md:41) — passes.
2026-05-10 02:29:57 +01:00

229 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Import Media Playbook
> Repeatable workflow for adding a new movie or TV show to ARRFLIX.
> Mirror the format of `playbooks/subtitles/README.md` (CHANGELOG-driven, runs/ folder for per-import logs).
Version: **v1.0** (2026-05-10)
---
## TL;DR — five-step import
1. **Stage** on onyx — rename to canonical form, cleanup junk
2. **rsync** to nullstone `/home/user/media/<library>/<Title> (<Year>)/`
3. **Permissions**`chown user:user`, files `644`, dirs `755`
4. **Verify scan**`LibraryMonitor` auto-refreshes ~13 s after copy; `Items/Counts` should bump
5. **Subtitles** (if needed) — follow `playbooks/subtitles/`
If anything goes wrong, **don't delete the source download** until the new content is confirmed playing in the app (`ADMIN-GUIDE.md:74`).
---
## Pre-flight checklist
Before importing:
- [ ] Source file is canonical quality (4K source-permitting, AI-upscale otherwise — no 480p filler, no junk encodes per `README.md:41`)
- [ ] Title and year confirmed (TMDb / IMDb)
- [ ] No copy already in library — check via `curl -s -H "X-Emby-Token: $TOK" 'https://arrflix.s8n.ru/Items?Recursive=true&IncludeItemTypes=Movie&searchTerm=<title>'`
- [ ] Container running healthy: `ssh user@192.168.0.100 'docker ps --filter name=jellyfin --format "{{.Names}} {{.Status}}"'`
---
## Step 1 — Stage on onyx
Source land in `/home/admin/Downloads/<release-name>/`. Stage to a clean dir before pushing.
### Movie
```bash
SRC="/home/admin/Downloads/<Original Release Name>/<file>.mkv"
DEST_DIR="/home/admin/staging-jelly/<Title> (<Year>)"
mkdir -p "$DEST_DIR"
cp "$SRC" "$DEST_DIR/<Title> (<Year>).mkv"
ls -la "$DEST_DIR"
```
### TV (single season)
```bash
SRC="/home/admin/Downloads/<Series Release>"
DEST_DIR="/home/admin/staging-jelly/<Series> (<Year>)/Season <NN>"
mkdir -p "$DEST_DIR"
# rename per docs/08 — <Series> (<Year>) - SNNEMM - <Title>.mkv
for ep in "$SRC"/*.mkv; do
cp "$ep" "$DEST_DIR/<Series> (<Year>) - S<NN>E<MM> - <Episode Title>.mkv"
done
```
### Filename rules (canonical, see `docs/05` + `docs/08`)
- Year in `()` mandatory, even when unique
- Forbidden chars in path: `< > : " / \ | ? *`
- Strip group tags: `[YIFY]`, `[RARBG]`, `[FS99 Joy]`, `-FQM`, `-AMIABLE`, etc.
- Strip resolution/codec/source/audio tokens: `1080p`, `2160p`, `BluRay`, `WEB-DL`, `x265`, `HEVC`, `10bit`, `AAC`, `DTS`, `EAC3`, `5.1`, `H264`
- Strip language tokens from video file (`.eng`, `.pl`, etc. — those go on subtitle sidecars only)
- Lowercase extension
- Movies: `<Title> (<Year>).<ext>`
- TV: `<Series> (<Year>) - S<NN>E<MM> - <Episode Title>.<ext>`, episodes inside `Season <NN>/`
### Cleanup junk
If source dir has extra files (NFO, JPG, sample, torrent, RARBG.txt, etc.) — staging copy should include ONLY the media file. NFOs that pin TMDb/IMDb ID are exception (see `docs/07`).
`bin/cleanup-import.sh` and `bin/normalize.py` are documented in `docs/07` and `docs/08` but not yet extracted as runnable scripts (ROADMAP M6). For now, manual cleanup per the rules above.
---
## Step 2 — rsync to nullstone
### Movie
```bash
rsync -a --info=progress2 --no-owner --no-group \
"/home/admin/staging-jelly/<Title> (<Year>)" \
user@192.168.0.100:/home/user/media/movies/
```
### TV (one season)
```bash
rsync -a --info=progress2 --no-owner --no-group \
"/home/admin/staging-jelly/<Series> (<Year>)/Season <NN>" \
'user@192.168.0.100:/home/user/media/tv/<Series> (<Year>)/'
```
`scp -r` works too but `rsync -a` preserves perms-by-mode + resumes on interrupt.
---
## Step 3 — Permissions on nullstone
Files must be `user:user` 644 (per live audit, `stat -c '%U:%G %a'`):
```bash
ssh user@192.168.0.100 '
cd "/home/user/media/movies/<Title> (<Year>)"
find . -type d -exec chmod 755 {} \;
find . -type f -exec chmod 644 {} \;
ls -la
'
```
`rsync` over ssh as `user@` already lands files as `user:user`. The chmod pass is for safety (`mv` from another path may differ).
---
## Step 4 — Verify scan picked up
Jellyfin's `LibraryMonitor` watches the bind mount and auto-refreshes within ~13 s of file landing. Confirm in logs:
```bash
ssh user@192.168.0.100 'docker logs jellyfin --tail 20 | grep -E "LibraryMonitor.*<Title>|<Title>.*refreshed"'
```
Expect: `LibraryMonitor: <Title> ... will be refreshed.`
If LibraryMonitor doesn't fire (rare), force a manual refresh:
```bash
TOK=<your-admin-token>
ssh user@192.168.0.100 "docker exec jellyfin curl -s -X POST -H 'X-Emby-Token: $TOK' http://127.0.0.1:8096/Library/Refresh"
```
---
## Step 5 — Verify count bump + metadata + playback
```bash
TOK=<token>
ssh user@192.168.0.100 "docker exec jellyfin curl -s -H 'X-Emby-Token: $TOK' http://127.0.0.1:8096/Items/Counts" | python3 -m json.tool
```
`MovieCount` (or `EpisodeCount`) should be `+1` (or `+N`).
Find the new item ID:
```bash
ssh user@192.168.0.100 "docker exec jellyfin curl -s -H 'X-Emby-Token: $TOK' 'http://127.0.0.1:8096/Items?Recursive=true&IncludeItemTypes=Movie&searchTerm=<Title>'" | python3 -c "import sys,json;[print(i['Id'],i['Name']) for i in json.load(sys.stdin)['Items']]"
```
Probe codec/streams (sanity check no transcode surprise):
```bash
ssh user@192.168.0.100 'docker exec jellyfin /usr/lib/jellyfin-ffmpeg/ffprobe -hide_banner "/media/movies/<Title> (<Year>)/<Title> (<Year>).mkv" 2>&1 | grep -E "Stream|Duration"'
```
Open in browser → confirm artwork + title + duration look right.
If TMDb match is wrong (rare for canonical titles), force the right one by adding `[tmdbid-NNNNN]` to the folder name and re-scanning. See `docs/05:54-56`.
---
## Step 6 — Subtitles (optional, see playbook)
If the user wants English subs and source doesn't have them embedded:
- Follow `playbooks/subtitles/README.md`
- Drop `.eng.srt` next to the mkv
- After dropping subs: `POST /Items/{id}/Refresh?MetadataRefreshMode=ValidationOnly&Recursive=true`
---
## Step 7 — Document the run
Copy `runs/_template.md` to `runs/<title-slug>.md` and record:
- Source provenance (laptop path, release name, hash if you bothered)
- Target nullstone path
- Item ID
- Counts before/after
- Codec/stream summary
- Subtitle status
Commit + push: see `testing/DEPLOY.md` for git workflow.
---
## Verification checklist (from `docs/05:1082-1099`)
- [ ] Folder matches `<Title> (<Year>)` (movies) or `<Series> (<Year>)/Season <NN>` (TV)
- [ ] Filename matches canonical pattern (no group tags, no codec/resolution tokens)
- [ ] Year present, four digits, in parentheses
- [ ] No forbidden chars `< > : " / \ | ? *`
- [ ] Folder + filename match exactly (basename equals folder name for movies)
- [ ] One folder per item — no shared folders
- [ ] NFO present iff TMDb override needed (`[tmdbid-NNNN]` token)
- [ ] Subtitle sidecars correctly named (`.eng.srt`, not `.en.srt` or stripped)
- [ ] `/Library/Refresh` returned 204 / scheduled task ran (or LibraryMonitor auto-fired)
- [ ] `ProviderIds` populated on the new item (TMDb match successful)
- [ ] Image artwork populated
---
## Rollback / abort
If a partial copy fails or the wrong item gets matched:
```bash
# Remove the item folder
ssh user@192.168.0.100 'rm -rf "/home/user/media/movies/<Title> (<Year>)"'
# Force scan to drop it from index
ssh user@192.168.0.100 "docker exec jellyfin curl -s -X POST -H 'X-Emby-Token: $TOK' http://127.0.0.1:8096/Library/Refresh"
```
Source download on laptop is untouched — restage and retry per `docs/07:55-58`.
---
## Cross-references
| Topic | File |
|-------|------|
| Brand quality bar | `README.md:41` |
| Daily ops media flow | `ADMIN-GUIDE.md:3546` |
| Folder/filename rules | `docs/05`, `docs/08` |
| Pre-import cleanup logic | `docs/07` |
| Subtitle sidecar form | `docs/03` + `playbooks/subtitles/` |
| TMDb override token | `docs/05:54-56` |
| Verification checklist (canonical) | `docs/05:1082-1099` |
| LibraryMonitor proof | live `docker logs jellyfin | grep LibraryMonitor` |
| Existing run examples | `runs/` |