# ━━━━━ MEDIA — Manual ━━━━━

Config reference, procedures, operational workflows for Media/ scripts.
For overview see README-Media.md. For per-script detail see script headers.

For arr stack config and procedures (cleanup, release fixer, sync, discovery) see
`Arrs_Stack/Manual-Arrs_Stack.md`.

---

## ━━━ PERMISSIONS MODEL ━━━

```
Directories: 755 nobody:users
  Owner (nobody) — rwx   enter, list, create files
  Group (users)  — r-x   enter and list
  Others         — r-x   Samba guests can browse
  No world-write — prevents accidental deletion by unauthenticated access

Files: 664 nobody:users
  Owner (nobody) — rw    read + write
  Group (users)  — rw    arrs can import, rename, delete
  Others         — r     Samba guests can read
  No execute bit — media files are never executable
```

**Two separate passes — not a single recursive chmod.** Directories need the execute bit
to enter. Files must never have the execute bit. A single `chmod -R 664` would break
directory entry. The script runs `find -type d` and `find -type f` separately.

**If this script corrects many files on every run**, a container has wrong PUID/PGID.
Correct values on unRAID: `PUID=99 (nobody)` `PGID=100 (users)`. Add to each container's
environment in its Docker template. Common culprits: SABnzbd, qBittorrent, slskd.
Once fixed, this script corrects 0 files per run — it becomes a pure daily failsafe.

---

## ━━━ CONFIGURATION — master.conf ━━━

### Permissions

```bash
PERMISSIONS_DIR_MODE="755"
PERMISSIONS_FILE_MODE="664"
PERMISSIONS_OWNER="nobody:users"
```

---

### Media Cleaner — File Patterns

```bash
ANIME_FILE_PATTERNS=(
    "*.sfv"             # checksum verification — useless after download verified
    "*.md5" "*.sha1"    # other checksum formats
    "*.nfo"             # scene info file — not library metadata
    "*.url" "*.lnk"     # website shortcuts
    "*.rar" "*.zip"     # source archives kept by some clients after extraction
    "*.info"            # tool output files
    "*.torrent"         # torrent descriptor left by some clients
    "*.sample*"         # scene preview clip
    "*.proof*"          # screenshot proving encode quality
    "*sync-conflict*"   # Syncthing conflict copies
    "*.scr" "*.exe"     # executables — should never be in a media folder
    "*.srr"             # scene recovery record
    "*.log"             # tool/client logs
    "*.json"            # metadata or tool output
)

MEDIA_FILE_PATTERNS=(
    "${ANIME_FILE_PATTERNS[@]}"   # all anime patterns plus:
    "*.iso"                       # disc images after ripping
    "*.lrc"                       # lyric files in media folders
)
```

> **DO NOT add patterns that match media you want to keep:**
> `*.mkv *.mp4 *.avi *.m4v` — video files
> `*.flac *.mp3 *.m4a` — audio files
> `*.srt *.sub *.ass` — subtitle files (Bazarr managed)
> `*.jpg *.png` — artwork
> Always use `--dry-run` when adding new patterns.

---

### Play State Sync

```bash
PLAY_SYNC_ENABLED=true                # toggle entire sync
PLAY_SYNC_REMOTE=true                 # sync across hosts via Tailscale
                                      # false = local servers only (this host's Emby + Jellyfin)
PLAY_SYNC_TYPES="Movie,Episode"       # item types to sync — Audio excluded, music library too large
PLAY_SYNC_FAV_TYPES="MusicArtist,MusicAlbum,Movie,Series"   # favourites, union sync, never unmarks

PLAY_SYNC_PROBE=true                  # skip per-item work when nothing changed since last run
PLAY_SYNC_PROBE_MAX_AGE_HOURS=24      # force a full comparison when the fingerprint is older

PLAY_SYNC_HANDBACK_RETRIES=5          # fallback handback: attempts before DNS cutover proceeds
PLAY_SYNC_HANDBACK_RETRY_DELAY=60     # seconds between those attempts
```

> **There is no date/lookback filter, and one must not be re-added.** An earlier version
> gated on played-date; it was removed once the real cost was measured — the 30-minute
> runtime was fork overhead per item, not API volume or item count. The fix was jq epoch
> parsing plus the response-hash probe below. Re-introducing a date window would reduce
> correctness (older items silently stop syncing) without meaningfully reducing runtime.

Emby and Jellyfin servers configured per-host:

```bash
# host*.conf
HOST1_EMBY_URL="http://192.168.50.2:8096"
HOST1_EMBY_API_KEY="..."
HOST1_JELLYFIN_URL=""   # empty = skip Jellyfin on this host
HOST1_JELLYFIN_API_KEY=""
```

---

## ━━━ CONFIGURATION — host*.conf ━━━

```bash
# Shares this server applies permissions to
HOST1_MEDIA_PERMISSION_SHARES=(
    "/mnt/user/Movies"
    "/mnt/user/Tv_Shows"
    "/mnt/user/Music"
    "/mnt/user/Kids_Movies"
    "/mnt/user/Kids_Tv_Shows"
    "/mnt/user/Sports"
    "/mnt/user/stand-up_comedy"
)

# Folders cleaned by each profile
HOST1_ANIME_CLEAN_FOLDERS=(
    "/mnt/user/Anime_Movies"
    "/mnt/user/Anime_Movies-Old"
    "/mnt/user/Anime_Shows"
    "/mnt/user/Anime_Shows-Old"
)
HOST1_MEDIA_CLEAN_FOLDERS=(
    "/mnt/user/Kids_Movies"
    "/mnt/user/Kids_Tv_Shows"
    "/mnt/user/Movies"
    "/mnt/user/Music"
    "/mnt/user/Sports"
    "/mnt/user/stand-up_comedy"
    "/mnt/user/Tv_Shows"
)
```

---

## ━━━ OUTPUT TIERS ━━━

All scripts have two output levels controlled by `--log`.

Without `--log`, each script processes silently and concludes with a summary block.
Warnings and errors are always visible.

With `--log`, per-item detail appears — individual shares being processed, file counts,
per-server sync results.

---

## ━━━ FLAG REFERENCE ━━━

### media_shares_permissions.sh

`media_shares_permissions.sh`
Apply correct ownership and permissions to all configured media shares. Safe to run
manually at any time — idempotent, only changes what's wrong.

`media_shares_permissions.sh --dry-run`
Show how many files and directories would be corrected per share. If unexpectedly large,
check container PUID/PGID settings first (PUID=99 PGID=100).

`media_shares_permissions.sh --status`
Show configured share list and ownership of the share roots.

`media_shares_permissions.sh --log`
Show ownership correction count per share and per file (verbose).

> On large libraries this runs 20-30 minutes. This is expected — millions of files with
> recursive walk takes time. Designed to run overnight in the maintenance window.

---

### media_cleaner.sh

`media_cleaner.sh anime`
Remove junk files from anime share folders using ANIME_FILE_PATTERNS.

`media_cleaner.sh media`
Remove junk files from media share folders using MEDIA_FILE_PATTERNS.

`media_cleaner.sh [profile] --dry-run`
Show what would be deleted without removing anything. Always run first when adding new
patterns or folders.

`media_cleaner.sh [profile] --status`
Show folder list and file patterns for the profile.

`media_cleaner.sh [profile] --log`
Show every file examined, not just those removed.

---

### play_state_sync.sh

`play_state_sync.sh`
Sync played/unplayed state, resume positions and favourites across **every** configured Emby
and Jellyfin server — not just local→remote. Servers are discovered from every
`HOST*_TRANSCODE_SERVERS` entry, with remote hosts' localhost URLs rewritten to their
Tailscale IP. Newest `LastPlayedDate` wins; state only ever moves forward, never clears.

No age filter — every matched item is considered on every run. The **change probe** is what
keeps that cheap: the raw API responses are hashed and compared against the fingerprint from
the last successful run, and per-item processing is skipped entirely when nothing moved.
Fetches still happen every run, so nothing can be missed by the probe.

`play_state_sync.sh --full`
Bypass the change probe and force the full per-item comparison even when the fingerprint
matches. Use after a new Emby install or database restore, or when debugging a sync that
appears to be skipping work it should be doing.

`play_state_sync.sh --wait`
Wait for an in-progress run instead of exiting. For manual runs that would otherwise be
skipped by the scheduled every-30-minute pass. Used by `fallback.sh` during handback.

`play_state_sync.sh --dry-run`
Show what would be synced without writing any state.

`play_state_sync.sh --status`
Show configured servers, reachability, and user counts.

`play_state_sync.sh --log`
Verbose output — show each item comparison.
