Getting started¶
This page installs Thumbforge and walks through a first run: set up, fetch a video, generate thumbnail candidates with the offline fake provider, then pick one and export it, and finally batch a playlist. The sample output comes from real runs; ids, timestamps and paths on your machine will differ.
Requirements¶
- uv, the Python package and tool manager. Thumbforge needs Python 3.14; uv downloads it for you if it is not installed.
- Network access for
thumbforge fetch, which reads public YouTube metadata. No YouTube API key is needed (the Data API source is an optional extra; see Series and playlists).
Install¶
Install Thumbforge from PyPI:
uv tool install thumbforge
To build it from a checkout of the repository instead:
git clone https://github.com/khiladisngh/thumbforge
cd thumbforge
uv tool install .
uv tool install puts the thumbforge command on your PATH in its own isolated environment. Check that it resolves:
$ thumbforge --version
thumbforge 0.1.1
If your shell says thumbforge is not found, the directory printed by uv tool dir --bin is not on your PATH. Run uv tool update-shell and open a new terminal. To remove Thumbforge again, run uv tool uninstall thumbforge.
Where Thumbforge keeps its files¶
thumbforge config path
prints every path Thumbforge reads and writes: the configuration file, the data directory (database and images), the state directory and the log file. They follow your operating system's conventions for per-user configuration and data.
To try Thumbforge without touching your real setup, point it at a scratch location. Every command accepts two global options, written before the command name:
thumbforge --config ./scratch/config.toml --data-dir ./scratch/data config path
Or set the same two things for a whole terminal session with environment variables:
export THUMBFORGE_CONFIG="$PWD/scratch/config.toml"
export THUMBFORGE_GENERAL__DATA_DIR="$PWD/scratch/data"
$env:THUMBFORGE_CONFIG = "$PWD\scratch\config.toml"
$env:THUMBFORGE_GENERAL__DATA_DIR = "$PWD\scratch\data"
set THUMBFORGE_CONFIG=%CD%\scratch\config.toml
set THUMBFORGE_GENERAL__DATA_DIR=%CD%\scratch\data
These two options move the configuration file and the data directory, and nothing else. Logs and each run's scratch folder go to a third place, the per-user state directory (state_dir in config path), whatever you set here; Files and logs says how to move it. Progress lines such as run started also print to your terminal (stderr); they are left out of the samples below, and --quiet hides them.
First run¶
1. Write a configuration file¶
$ thumbforge config init
wrote <config-file>
config init writes a commented config.toml listing every setting with its default and prints its path. thumbforge config show prints the effective configuration (defaults, then the file, then THUMBFORGE_* environment variables); thumbforge config set changes a key and validates the result before writing. The defaults already select the fake provider and the bold-title template, which is all this page needs.
2. Create the database¶
$ thumbforge db init
initialized database at <data-dir>/thumbforge.sqlite3 (0001)
db init creates the SQLite database in your data directory, stores the built-in templates in it, and prints its path. thumbforge db status reports the schema revision, file size and journal mode.
3. Check the provider¶
$ thumbforge provider list
┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Key ┃ Name ┃ Version ┃ Auth ┃ Capabilities ┃
┡━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ antigravity │ Antigravity CLI │ unknown │ missing │ negative, aspect · jpeg · x2 │
│ fake │ Fake │ 0.1.1 │ ok │ reference, seed, negative, aspect · png · x8 │
└─────────────┴─────────────────┴─────────┴─────────┴──────────────────────────────────────────────┘
$ thumbforge provider check fake
fake · healthy
provider list prints one row per installed provider with its version, authentication state and capabilities. The fake provider is always there: it works offline and needs no key, so the rest of this page uses it. provider check KEY runs that provider's health checks and prints a table of results below the summary line. See Providers for the real ones.
4. Fetch a video¶
$ thumbforge fetch "https://www.youtube.com/watch?v=jNQXAC9IVRw"
╭─────────── Video ───────────╮
│ Me at the zoo jNQXAC9IVRw │
╰─────────────────────────────╯
Stored 1 channel, 0 playlist, 1 videos.
fetch accepts a video, playlist or channel URL, or a bare id, and stores the metadata in the database. jNQXAC9IVRw is a short public video used as an example; any public video works. yt-dlp may print a warning about a missing JavaScript runtime; the fetch still succeeds.
Put the URL in quotes: an unquoted & ends the command in most shells. A watch link that carries &list=… names one video seen inside a playlist, so fetch stores that video only (0 playlist). To store the playlist, fetch its playlist link; see Work with a playlist.
5. Generate candidates¶
$ thumbforge thumb generate jNQXAC9IVRw --n 3
Run <run>
Kind hero
Status completed
Template bold-title@1
Provider fake@0.1.1:<fingerprint>
Video jNQXAC9IVRw
Started <timestamp>
Finished <timestamp>
┏━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━━┓
┃ # ┃ Status ┃ Size ┃ Compliant ┃ Key ┃ Cost ┃ Asset / Error ┃
┡━━━╇━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━━━━┩
│ 1 │ completed │ 1920x1080 │ ✔ │ <key> │ — │ <asset> │
│ 2 │ completed │ 1920x1080 │ ✔ │ <key> │ — │ <asset> │
│ 3 │ completed │ 1920x1080 │ ✔ │ <key> │ — │ <asset> │
└───┴───────────┴───────────┴───────────┴──────────────┴──────┴───────────────┘
┏━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ # ┃ path ┃
┡━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ 1 │ <data-dir>/assets/<xx>/<asset-file> │
│ 2 │ <data-dir>/assets/<xx>/<asset-file> │
│ 3 │ <data-dir>/assets/<xx>/<asset-file> │
└───┴──────────────────────────────────────────────────────────────────────────┘
Copy images out with: thumbforge thumb export <run|iteration> --to PATH
Pick one with: thumbforge thumb pick <run> <ordinal>
This makes three candidates (the default is four) from the bold-title template and prints one row per candidate. Compliant is whether the final image passes YouTube's thumbnail rules, and Cost is what the provider reported using (— for fake). On a colour terminal that handles UTF-8 you also get a preview of each candidate, drawn in block characters; elsewhere (output piped, --no-color) you get a table of file paths, as in the sample above. The fake provider returns the same image for the same prompt and seed, so the candidates are placeholders, not artwork. Copy the run id (the <run> in the commands below) from the first line.
6. Pick one and export it¶
$ thumbforge thumb pick <run> 2
Picked #2 of run <run> (iteration <iteration-id>)
Export it with: thumbforge thumb export <run> --to PATH
$ thumbforge thumb show <run>
$ thumbforge thumb export <run> --to out
Wrote out/jNQXAC9IVRw-1.jpg
Wrote out/jNQXAC9IVRw-2.jpg
Wrote out/jNQXAC9IVRw-3.jpg
thumb pick marks candidate 2 as the one to keep; picking another one moves the mark. thumb show prints the run again, with a ★ on the picked candidate. On a colour terminal that handles UTF-8 it then draws the finals as a grid of small block-character pictures; anywhere else (output piped to a file or another command, --no-color, a dumb or legacy console) it prints a table of file paths instead, and you open the files yourself or copy them out with thumb export. thumb export copies every completed candidate into the folder out (created if needed) as <video-id>-<number>.jpg; on Windows the paths print with backslashes. Pick and refine covers --raw, exporting one iteration and refining the pick with thumb iterate.
7. Preview a template's prompt¶
A template is two files: a TOML layout and a Jinja prompt. bold-title, minimal and series-parts are built in (thumbforge template list). To write your own, create my-series.toml in the templates folder next to your configuration file (the folder thumbforge template new writes to), with this content:
[template]
name = "my-series"
description = "Large title bottom-left, Part badge top-right"
[canvas]
width = 1920
height = 1080
safe_margin_px = 64
[title]
enabled = true
font = "Inter-Bold"
max_lines = 3
size_px = 120
min_size_px = 72
color = "#FFFFFF"
stroke_px = 6
stroke_color = "#000000"
anchor = "bottom-left"
box = { x = 64, y = 640, w = 1400, h = 376 }
case = "upper"
[part]
enabled = true
format = "PART {n}"
font = "Inter-Bold"
size_px = 72
anchor = "top-right"
badge = { fill = "#E53935", padding_px = 24, radius_px = 16 }
[negative_space]
hint = "leave the lower-left third uncluttered"
and my-series.j2 beside it:
Background art for "{{ video.title }}", {{ width }}x{{ height }}.
{% if part_number %}
Series instalment {{ part_number }}.
{% endif %}
Composition: {{ negative_space }}.
Mood: {{ vars.mood }}.
Then check the layout, store it in the database, and print the prompt it produces for the video you fetched:
$ thumbforge template validate <config-dir>/templates/my-series.toml
ok <config-dir>/templates/my-series.toml (my-series)
$ thumbforge template import <config-dir>/templates/my-series
imported my-series@1 (<spec-hash>)
$ thumbforge template render my-series --video jNQXAC9IVRw --var mood=bright
Background art for "Me at the zoo", 1920x1080.
Composition: leave the lower-left third uncluttered.
Mood: bright.
<config-dir> is the folder holding the config file from thumbforge config path. validate reports every schema error at once. import stores the template as a new immutable version. render fills in the video's title, its part number (this video is in no playlist, so there is none) and the layout's negative-space hint, and calls no provider. Leaving out a variable the prompt uses is an error, not a blank:
$ thumbforge template render my-series --video jNQXAC9IVRw
template: undefined variable 'vars.mood' in my-series@1
That command exits with code 2. To use the template for a run, pass --template my-series to thumb generate. See Templates.
Work with a playlist¶
The same flow works for a whole series. Fetch a playlist, browse what was stored, and give the videos their part numbers:
thumbforge fetch "https://www.youtube.com/playlist?list=<playlist-id>"
thumbforge playlist list
thumbforge playlist show <playlist-id> --videos
thumbforge video list
thumbforge video show <video-id>
fetch stores the channel, the playlist and every video, and prints the playlist with each video's position, part number, id and title. A playlist fetched in the last 24 hours is shown from the database without going back to YouTube; add --refresh to re-read it. playlist show and video show accept a YouTube id or a URL; --videos adds the ordered list of videos with their part numbers.
Use the playlist link, the one with /playlist?list=. A watch link such as https://www.youtube.com/watch?v=<video-id>&list=<playlist-id> stores only that video and no playlist (Stored 1 channel, 0 playlist, 1 videos.), and playlist list stays empty. Quote the link, because of the &. A link of the form watch?list=<playlist-id> without v= is also read as the playlist.
video show prints a Thumbnail line with the address of the video's current thumbnail on YouTube (the source_thumbnail_url field with --json):
$ thumbforge video show jNQXAC9IVRw
Me at the zoo
Title Me at the zoo
YouTube id jNQXAC9IVRw
Length 0:19
Channel jawed
Published 2005-04-24T03:31:52+00:00
URL https://www.youtube.com/watch?v=jNQXAC9IVRw
Thumbnail https://i.ytimg.com/vi/jNQXAC9IVRw/hqdefault.jpg?sqp=…
Fetched <timestamp>
Playlists 0
Thumbforge 0.1.1 has no command that downloads or shows that image. Open the address in a browser, or save it with curl, quoting the address because it contains &:
curl -o current.webp "https://i.ytimg.com/vi/<video-id>/hqdefault.jpg?sqp=…&rs=…"
Take the whole address from thumbforge --json video show <video-id>: the terminal table wraps a long address over several lines. YouTube may send WebP even though the address ends in .jpg, so a .webp name is the safe one to save it under (on PowerShell, write curl.exe; plain curl is a different command there).
Each video starts with a part number equal to its position in the playlist. When the playlist opens with a trailer, leave it unnumbered so the first real episode is Part 1:
thumbforge playlist renumber <playlist-id> --start 1 --skip-ids <trailer-video-id>
The table it prints shows each video's old and new part number; a — marks a video left without one. Skipped videos are not counted, so the remaining parts stay consecutive, and part numbers you set survive a later --refresh. See Series and playlists.
Batch the series¶
With a hero picked (step 6), one command makes a thumbnail for every video in the playlist, in the hero's style, with each video's own title and "Part N" badge:
$ thumbforge batch <playlist-id> --hero <run> --template series-parts --dry-run --only 1-3
┏━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┓
┃ Part ┃ Title ┃ Key ┃ Action ┃ Reason ┃
┡━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━┩
│ 1 │ <title> │ <key> │ create │ new │
│ 2 │ <title> │ <key> │ create │ new │
│ 3 │ <title> │ <key> │ create │ new │
└──────┴────────────────┴──────────┴────────┴────────┘
3 to generate, 0 to retry, 0 to skip. Nothing was generated (--dry-run).
$ thumbforge batch <playlist-id> --hero <run> --template series-parts --only 1-3
Batch run <batch-run>
Playlist <playlist-title>
Template series-parts@1
Provider fake@0.1.1:<fingerprint>
Reference <asset> (final)
Parent run <run>
Status completed
Items 3 completed, 0 failed, 0 pending of 3
…
--dry-run prints the plan and calls no provider; --only 1-3 limits the batch to those part numbers. --only is the only option that selects parts: --max-images merely refuses to start when the batch would make more images than the number you give, and --dry-run ignores it. The second command prints a header and a table with one row per item (part, title, status, size, compliance, asset and, for a failure, the error). On a terminal a progress line per finished item appears while it runs. If you press Ctrl+C, or some items fail, thumbforge runs resume <batch-run> finishes the rest without generating the completed ones again, and thumbforge runs cost <run> totals what a provider reported using. See Batch a playlist and Resume an interrupted run.
Common surprises¶
- A watch link with
&list=…stores one video, not the playlist. Fetch the/playlist?list=link, in quotes. See Work with a playlist. --max-imagesselects nothing, and--dry-runignores it. Choose parts with--only; see Batch a playlist.- Running the same
batchagain makes a new run with no images in it, andruns resumerefuses a run that is alreadycompleted(exit2). See Batch a playlist. - Logs ignore
--configand--data-dir. They go to the per-user state directory. See Files and logs. thumb showprints file paths, not a picture grid, when the terminal cannot draw it, for example when the output is piped. See step 6 above.- Output that is piped or redirected is always UTF-8, whatever the system code page, so a title in Hindi or the
✔in a run table survives> out.txt; read the file as UTF-8. A terminal is unchanged. - There is no command for the video's current thumbnail.
video showprints its address only. See Work with a playlist.
Exit codes¶
Every command exits 0 on success. The CLI reference lists the others: usage errors, not found, provider errors, compliance failures, partial batches and interruption.
Next steps¶
- Read the concepts: Hero, Series and playlists, Templates, Providers.
- Try it on a playlist repeats this page on a real playlist, step by step.
- Generate a hero and Pick and refine go deeper on the commands used above.
- Batch a playlist and Resume an interrupted run cover the whole series.
- Write a custom template makes the look your own.
- Shell completion adds Tab completion for commands and options in bash, zsh, fish and PowerShell.
- The CLI reference,
thumbforge --helpandthumbforge <command> --helplist every command and option.