Files
Panama/server/home/Media/compact-tv/README.md
T

6.3 KiB

compact-tv

Sonarr and Radarr each have exactly four quality profiles. Two of them (Standard and Mini) also have their files shrunk by compact-tv.py, which runs every night.

Profile Used for What gets kept
Mini shows I demote hard TV: 720p at 800 kbps (~0.42 GB per hour). Movies: 720p at 1500 kbps
Standard every request from other users (Seerr default), and anything I demote TV: 1080p at 1200 kbps (~0.6 GB per hour). Movies: 1080p at 2500 kbps (~1.2 GB per hour)
HD my own library, unchanged whatever downloads, up to 1080p
UHD only me, through Seerr's 4K servers 4K

SD sources keep their resolution, at 600 kbps for TV and 900 kbps for movies. Movies get higher rates because dark scenes, grain and action break down first at low bitrates. Nothing is ever upscaled. Audio becomes Opus: 96 kbps for stereo, 160 for 5.1, 192 for 7.1. Subtitles, chapters and attachments are copied as they are. I picked the rates from sample encodes of Shameless S05E06 (VMAF ~91 for Standard, ~86 for Mini).

How the profiles work

  • Standard and Mini are copies of HD that upgrade only toward small releases (see below), so Sonarr and Radarr never replace a file with a bigger one.
  • Small downloads first. Both profiles prefer small releases when they grab:
    • The "Small Encode" format (MeGusta, PSA, ELiTE, GalaxyTV/RG, YTS/YIFY) scores +3000.
    • LQ scores 0. In HD and UHD it scores -10000, which keeps those groups out of HD.
    • Standard merges HDTV/WEB/Bluray at each resolution, so the format score decides between sources rather than the source type.
    • Mini merges 720p and 1080p into one group and adds +1000 for 720p.
    • Many files therefore arrive already small, and the job skips them.
  • Minimum sizes are shared by every profile. For 720p I lowered them to 5 MB per minute and for 1080p to 6 MB per minute (8 for Bluray-1080p), so small encodes are not rejected. HD still never takes the LQ groups.
  • Seerr:
    • The "Sonarr - HD" and "Radarr" servers default to Standard. Only my admin account sees the profile picker, so other users always get Standard. I pick HD or UHD per request.
    • Other users no longer have "Request 4K".
  • Configarr is opt-in (docker compose --profile configarr up configarr). Its templates would recreate the old TRaSH profiles and size limits.
  • Backups from before all this: backups/pre-four-profiles-20261028/. That covers both apps' profiles, custom formats and size definitions, which title was on which profile, and Seerr's settings. setup-small-profiles.py reapplies the Standard and Mini settings.

How the job works

The nightly run (00:30 to 08:00) has two phases.

1. Search first. It asks Sonarr and Radarr to search titles on Standard or Mini that it hasn't searched before, biggest first: 5 shows and 20 movies a night, so downloads and indexer use stay spread out. Standard and Mini upgrade only toward small releases:

  • Every allowed quality sits in one group, so no resolution or source upgrade can happen.
  • An upgrade needs a custom-format score jump of at least 2500, and upgrades stop at 3000. Only gaining "Small Encode" clears that bar.

So a search replaces a big file only with a small encode, never with a big HEVC release. After that, the apps' regular RSS checks keep picking up small releases on their own.

2. Encode what's left. A title becomes eligible for encoding 7 days after its search, and only its files that are still big get encoded. Files that arrive after the title's search work differently:

  • This covers new episodes of ongoing shows, and fresh requests.
  • The first night they're seen, they get their own search (one episode or movie at a time).
  • They're encoded only if they're still big 3 days later.

Downloads never wait for any of this, so new episodes and requests land in the library as soon as they download. Only encoding waits. For each file, the job encodes to ~/.cache/compact-tv/work.mkv on the NVMe and then checks the result:

  • the duration matches the original
  • the audio and subtitle track counts match
  • the file is smaller

It then renames the new file over the original, keeping the same name with .mkv, and asks Sonarr or Radarr to rescan. Plex picks the change up through its folder watcher, and the episode keeps its Plex ID, so watch history stays. If a small release shows up later, the app may still replace an encoded file with it, which costs bandwidth, not GPU.

Other details:

  • Each output carries a COMPACT_TIER tag. A file is skipped if re-encoding would save less than 30%. Ruled-out files are cached by size in state.json, so they aren't probed every night. Search dates and failures are kept there too.
  • A downloaded HEVC file no bigger than twice the target size counts as done. That is the small release a search brought in (a MeGusta 1080p episode runs about 2.3 Mbps against Standard's 1.2), and it is neither re-encoded nor searched again. The job's own tagged outputs are exempt, so Standard to Mini still works.
  • Skipped: HDR and Dolby Vision sources (they need tone mapping), subtitle tracks ffprobe can't identify, and files that have failed twice.
  • Speed is about 0.19 hours of GPU per hour of 1080p video (NVENC is the bottleneck). The job stops starting new encodes at 08:00.
  • cost-report.py estimates, per title, the GPU hours the job would need against the size of a fresh small download.
  • A PAUSE file in this folder makes the nightly run do nothing.

Going back up

Moving down works: Standard to Mini re-encodes the Standard copy. Moving up does not restore quality. To get quality back, I set the title to HD and search, which redownloads it at full size.

Running it

./compact-tv.py --dry-run                      # what tonight would do
./compact-tv.py --search-all --limit 0         # search every unsearched title now, encode nothing
touch PAUSE                                    # skip nightly runs until PAUSE is deleted
./compact-tv.py --series "Shameless (US)" --limit 1
systemctl status compact-tv.timer compact-tv.service
tail -f compact-tv.log

The installed units are copies of compact-tv.service and compact-tv.timer from this folder. ExecStart goes through /usr/bin/python3 because SELinux won't let systemd execute a file labeled as home content.