2 Home
xenarathon edited this page 2026-06-28 11:26:08 -04:00
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.

title-card-stills-finder — Setup & User Guide

This is the hands-on manual: how to install it, every flag, the two scoring modes, and the full end-to-end TitleCardMaker (TCM) workflow. For what it is and why, see the README.


1. What you need

  • Your show's video library laid out Plex-style, with SxxEyy in each filename (e.g. ... - S04E09 - Kir'Shara ...mkv). Season-subfolders are fine.
  • ffmpeg + ffprobe on PATH, and Python 3 with Pillow and numpy.
  • Optionally TitleCardMaker, if you want the stills to actually become cards.

You can install those directly, or skip all of it with the bundled Docker image (below).


2. Install

The repo ships a Dockerfile that bundles ffmpeg + Pillow + numpy. Build it once on a machine that can build images:

git clone http://YOUR-GIT-HOST/xenarathon/title-card-stills-finder.git
docker build -t titlecard-stills:latest title-card-stills-finder

Then run it with your library and output folder mounted in (see §4). The container's entrypoint is the script, so everything after the image name are the script's flags.

Option B — run the script directly

pip install pillow numpy        # ffmpeg must already be installed
python3 titlecard_stills.py --help

3. The two scoring modes

Every sampled frame is scored for sharpness (motion blur loses) and exposure (near-black / blown-out / blank frames are dropped). On top of that you pick one composition rule:

Mode Flag Use when
Right-2/3 (default) --right-bias 0..1 Your card's title is flush-left. Rewards detail in the middle+right with a clear left third.
Text-zone --text-zone x0,y0,x1,y1 Your title sits anywhere else (centered, center-left, lower-third). Rewards frames whose detail stays out of the rectangle you give.

Finding your --text-zone rectangle

The rectangle is x0,y0,x1,y1 as fractions of width/height (0,0 = top-left, 1,1 = bottom-right). The easy way to find it:

  1. Open one card TCM already built for this show.
  2. Draw a box over the area the title text occupies.
  3. Convert the box corners to fractions of the image size.

A center-left title (like a Horizon-style card) is roughly 0.05,0.40,0.58,0.66. Frames where that band is busy (a face there) score low; frames with the subject pushed to the side score high. Tune how hard it pushes with --text-zone-weight (default 0.55).

Every contact sheet drawn in text-zone mode has the box outlined on each thumbnail, so a text-over-face collision is obvious at a glance.


4. Running it

The shape of a run (Docker form — the script form is the same flags without the docker run/mounts):

docker run --rm \
  -v "/path/to/Show":/library:ro \
  -v "/path/to/tcm/config/source/Show (Year)":/src \
  -v "/path/to/out":/out \
  titlecard-stills:latest \
  --library /library --source /src --out /out \
  --candidates 4 --right-bias 0.45
  • --source is only used to detect which episodes already have a sXeY.jpg (so they can be skipped). It's repeatable. Mount it read-only unless you use --auto-place.
  • Output candidates land in /out/sXeY/, each with a _contactsheet.jpg.

Always dry-run first to confirm the episode list: add --dry-run.

All flags

Flag Default What it does
--library (required) Plex-style show folder (Season XX subdirs OK)
--source TCM source dir(s) to check for existing sXeY.jpg (repeatable)
--out (required) Output dir for candidate stills
--candidates 4 Top frames to keep per episode
--interval 12 Seconds between sampled frames (raise to go faster, fewer candidates)
--skip-head / --skip-tail 0.06 / 0.08 Fraction of runtime to skip at start/end (intros/credits)
--right-bias 0.45 0..1 weight of right-2/3 composition vs raw sharpness
--text-zone x0,y0,x1,y1 fractions: score for a CLEAR title band instead (see §3)
--text-zone-weight 0.55 0..1 weight of zone-clear vs sharpness when --text-zone is set
--dedup-dist 12 Max perceptual-hash distance treated as a duplicate
--only Season filter, e.g. S35,S36 or 35,36
--episodes Explicit list, e.g. s1e5,s4e9
--limit 0 Process at most N episodes (handy for a tuning run)
--all off Process every episode, not just those missing a source
--auto-place off Also copy each #1 pick into the source dir as sXeY.jpg
--place-dir Where --auto-place writes (defaults to first --source)
--overwrite off Reprocess episodes that already have an out/ dir
--io-workers / --ep-workers 6 / 2 Parallel frame grabs / episodes in flight
--dry-run off List the episodes that would be processed, then exit

5. End-to-end TitleCardMaker workflow

A. Fill gaps (episodes with no TMDb still)

  1. --dry-run to confirm the missing list.
  2. Run without --auto-place; review the contact sheets in out/.
  3. Copy your pick into the TCM source dir as sXeY.jpg (TCM uses unpadded names, e.g. s4e9.jpg), or re-run with --auto-place to drop each #1 pick in for you.
  4. Rebuild cards in TCM (its UI, or POST /api/v2/series/series/<id>/process).

B. Replace stills that collide with the title

  1. Find the offenders: build all your current cards, then eyeball them (overlay your title rectangle so collisions are obvious).
  2. Re-mine just those with --all --episodes s1e5,s4e9,... --text-zone <your box>.
  3. Back up the originals before overwriting (e.g. cp -n into a _backup_DATE/ subfolder — and leave non-episode assets like badge.png/logo.png alone).
  4. Place your picks, then force a rebuild. TCM caches cards, so the reliable way to make it regenerate is delete the cards, then process: DELETE /api/v2/cards/series/<id>POST /api/v2/series/series/<id>/process.

6. Tips & gotchas

  • 4K is slow. HEVC 2160p decodes on CPU at roughly 35 min/episode. For a big run, launch the container detached (docker run -d) and poll the logs; it survives your SSH session dropping. Raise --interval to trade candidates for speed.
  • Let Docker make the out dir. A -v mount auto-creates the host path as root. If you mkdir it yourself as a non-root user inside a root-owned tree, you'll hit permission errors — just let the mount create it.
  • Watch for burned-in credit text. Opening-credits frames (writer/producer/actor names, ~29 min in) often score well but have text baked in. --skip-head won't fully clear them (credits run into act one); eyeball the contact sheet and pick a deeper candidate if the #1 landed on a credit.
  • Multi-episode files. A combined S01E01-E02 file is parsed as s1e1 only; s1e2 won't get its own still from it.
  • Relative scoring caveat. On mostly-dark space/night frames, any detail in the text band can read as "busy" relative to the black average. Trust your eyes on the contact sheet over the raw number for those.

This guide is updated on each release. Built with help from Claude (Anthropic Opus 4.8).