thumbforge 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. thumbforge-0.1.0/LICENSE +21 -0
  2. thumbforge-0.1.0/PKG-INFO +196 -0
  3. thumbforge-0.1.0/README.md +154 -0
  4. thumbforge-0.1.0/pyproject.toml +197 -0
  5. thumbforge-0.1.0/pyproject.toml.orig +188 -0
  6. thumbforge-0.1.0/src/thumbforge/__init__.py +10 -0
  7. thumbforge-0.1.0/src/thumbforge/__main__.py +3 -0
  8. thumbforge-0.1.0/src/thumbforge/cli/__init__.py +1 -0
  9. thumbforge-0.1.0/src/thumbforge/cli/_errors.py +89 -0
  10. thumbforge-0.1.0/src/thumbforge/cli/_render.py +196 -0
  11. thumbforge-0.1.0/src/thumbforge/cli/_runs.py +429 -0
  12. thumbforge-0.1.0/src/thumbforge/cli/_youtube.py +165 -0
  13. thumbforge-0.1.0/src/thumbforge/cli/app.py +155 -0
  14. thumbforge-0.1.0/src/thumbforge/cli/batch.py +398 -0
  15. thumbforge-0.1.0/src/thumbforge/cli/config.py +147 -0
  16. thumbforge-0.1.0/src/thumbforge/cli/db.py +151 -0
  17. thumbforge-0.1.0/src/thumbforge/cli/fetch.py +124 -0
  18. thumbforge-0.1.0/src/thumbforge/cli/playlist.py +168 -0
  19. thumbforge-0.1.0/src/thumbforge/cli/provider.py +218 -0
  20. thumbforge-0.1.0/src/thumbforge/cli/runs.py +199 -0
  21. thumbforge-0.1.0/src/thumbforge/cli/template.py +271 -0
  22. thumbforge-0.1.0/src/thumbforge/cli/thumb.py +278 -0
  23. thumbforge-0.1.0/src/thumbforge/cli/video.py +107 -0
  24. thumbforge-0.1.0/src/thumbforge/core/__init__.py +5 -0
  25. thumbforge-0.1.0/src/thumbforge/core/enums.py +48 -0
  26. thumbforge-0.1.0/src/thumbforge/core/errors.py +206 -0
  27. thumbforge-0.1.0/src/thumbforge/core/ids.py +38 -0
  28. thumbforge-0.1.0/src/thumbforge/core/json.py +37 -0
  29. thumbforge-0.1.0/src/thumbforge/core/layout.py +188 -0
  30. thumbforge-0.1.0/src/thumbforge/core/models.py +112 -0
  31. thumbforge-0.1.0/src/thumbforge/core/providers.py +205 -0
  32. thumbforge-0.1.0/src/thumbforge/core/redaction.py +99 -0
  33. thumbforge-0.1.0/src/thumbforge/core/services/__init__.py +6 -0
  34. thumbforge-0.1.0/src/thumbforge/core/services/batch.py +733 -0
  35. thumbforge-0.1.0/src/thumbforge/core/services/fetch.py +188 -0
  36. thumbforge-0.1.0/src/thumbforge/core/services/hero.py +698 -0
  37. thumbforge-0.1.0/src/thumbforge/core/services/iterate.py +96 -0
  38. thumbforge-0.1.0/src/thumbforge/core/sources.py +44 -0
  39. thumbforge-0.1.0/src/thumbforge/core/urls.py +152 -0
  40. thumbforge-0.1.0/src/thumbforge/credentials.py +113 -0
  41. thumbforge-0.1.0/src/thumbforge/imaging/__init__.py +1 -0
  42. thumbforge-0.1.0/src/thumbforge/imaging/compliance.py +66 -0
  43. thumbforge-0.1.0/src/thumbforge/imaging/finalize.py +112 -0
  44. thumbforge-0.1.0/src/thumbforge/imaging/fit.py +25 -0
  45. thumbforge-0.1.0/src/thumbforge/imaging/fonts/Inter-Bold.ttf +0 -0
  46. thumbforge-0.1.0/src/thumbforge/imaging/fonts/OFL.txt +92 -0
  47. thumbforge-0.1.0/src/thumbforge/imaging/fonts.py +47 -0
  48. thumbforge-0.1.0/src/thumbforge/imaging/overlay.py +234 -0
  49. thumbforge-0.1.0/src/thumbforge/logging.py +170 -0
  50. thumbforge-0.1.0/src/thumbforge/providers/__init__.py +9 -0
  51. thumbforge-0.1.0/src/thumbforge/providers/antigravity.py +661 -0
  52. thumbforge-0.1.0/src/thumbforge/providers/fake.py +187 -0
  53. thumbforge-0.1.0/src/thumbforge/providers/registry.py +127 -0
  54. thumbforge-0.1.0/src/thumbforge/settings.py +454 -0
  55. thumbforge-0.1.0/src/thumbforge/sources/__init__.py +5 -0
  56. thumbforge-0.1.0/src/thumbforge/sources/youtube_api.py +497 -0
  57. thumbforge-0.1.0/src/thumbforge/sources/ytdlp.py +362 -0
  58. thumbforge-0.1.0/src/thumbforge/storage/__init__.py +52 -0
  59. thumbforge-0.1.0/src/thumbforge/storage/assets.py +171 -0
  60. thumbforge-0.1.0/src/thumbforge/storage/db.py +290 -0
  61. thumbforge-0.1.0/src/thumbforge/storage/migrations/env.py +74 -0
  62. thumbforge-0.1.0/src/thumbforge/storage/migrations/script.py.mako +33 -0
  63. thumbforge-0.1.0/src/thumbforge/storage/migrations/versions/0001_initial.py +281 -0
  64. thumbforge-0.1.0/src/thumbforge/storage/models.py +373 -0
  65. thumbforge-0.1.0/src/thumbforge/storage/repositories.py +462 -0
  66. thumbforge-0.1.0/src/thumbforge/storage/runs.py +600 -0
  67. thumbforge-0.1.0/src/thumbforge/templates/__init__.py +5 -0
  68. thumbforge-0.1.0/src/thumbforge/templates/builtin/bold-title.j2 +5 -0
  69. thumbforge-0.1.0/src/thumbforge/templates/builtin/bold-title.toml +29 -0
  70. thumbforge-0.1.0/src/thumbforge/templates/builtin/minimal.j2 +5 -0
  71. thumbforge-0.1.0/src/thumbforge/templates/builtin/minimal.toml +29 -0
  72. thumbforge-0.1.0/src/thumbforge/templates/builtin/series-parts.j2 +10 -0
  73. thumbforge-0.1.0/src/thumbforge/templates/builtin/series-parts.toml +32 -0
  74. thumbforge-0.1.0/src/thumbforge/templates/builtins.py +29 -0
  75. thumbforge-0.1.0/src/thumbforge/templates/loader.py +284 -0
  76. thumbforge-0.1.0/src/thumbforge/templates/render.py +117 -0
  77. thumbforge-0.1.0/src/thumbforge/templates/schema.py +49 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gishant Singh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,196 @@
1
+ Metadata-Version: 2.4
2
+ Name: thumbforge
3
+ Version: 0.1.0
4
+ Summary: Generate consistent, spec-compliant YouTube thumbnails from a hero image and a playlist.
5
+ Keywords: youtube,thumbnail,cli,image-generation,typer
6
+ Author: Gishant Singh
7
+ Author-email: Gishant Singh <khiladisngh@hotmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 2 - Pre-Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: End Users/Desktop
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Topic :: Multimedia :: Graphics
17
+ Requires-Dist: alembic>=1.14
18
+ Requires-Dist: jinja2>=3.1.6
19
+ Requires-Dist: keyring>=25.0
20
+ Requires-Dist: pillow>=10.0
21
+ Requires-Dist: platformdirs>=4.11.10
22
+ Requires-Dist: pydantic>=2.13.5
23
+ Requires-Dist: pydantic-settings>=2.15.0
24
+ Requires-Dist: rich>=15.0.0
25
+ Requires-Dist: rich-pixels>=3.0.1
26
+ Requires-Dist: sqlalchemy>=2.0,<2.1
27
+ Requires-Dist: structlog>=26.1.0
28
+ Requires-Dist: tenacity>=9.0
29
+ Requires-Dist: tomli-w>=1.2.0
30
+ Requires-Dist: typer
31
+ Requires-Dist: yt-dlp>=2026.8.19
32
+ Requires-Dist: google-api-python-client ; extra == 'api'
33
+ Requires-Dist: google-auth ; extra == 'api'
34
+ Requires-Python: >=3.14
35
+ Project-URL: Homepage, https://github.com/khiladisngh/thumbforge
36
+ Project-URL: Documentation, https://khiladisngh.github.io/thumbforge/
37
+ Project-URL: Repository, https://github.com/khiladisngh/thumbforge
38
+ Project-URL: Issues, https://github.com/khiladisngh/thumbforge/issues
39
+ Project-URL: Changelog, https://github.com/khiladisngh/thumbforge/blob/main/CHANGELOG.md
40
+ Provides-Extra: api
41
+ Description-Content-Type: text/markdown
42
+
43
+ # thumbforge
44
+
45
+ [![ci](https://github.com/khiladisngh/thumbforge/actions/workflows/ci.yml/badge.svg)](https://github.com/khiladisngh/thumbforge/actions/workflows/ci.yml)
46
+ [![docs](https://github.com/khiladisngh/thumbforge/actions/workflows/docs.yml/badge.svg)](https://khiladisngh.github.io/thumbforge/)
47
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/khiladisngh/thumbforge/blob/main/LICENSE)
48
+ ![Python 3.14](https://img.shields.io/badge/python-3.14-blue)
49
+
50
+ Generate consistent, spec-compliant YouTube thumbnails from a hero image and a playlist — from the terminal.
51
+
52
+ - **Hero first.** Generate several candidates for one video, compare, pick, refine.
53
+ - **Batch second.** Use the picked hero as the style reference for every video in a playlist, with deterministic titles and "Part N" badges rendered by Pillow over AI-generated art.
54
+ - **Pluggable providers.** Antigravity CLI first; add your own via the `thumbforge.providers` entry-point group. A deterministic `fake` provider keeps tests offline.
55
+ - **Local database.** Channels, playlists, videos, templates, runs, iterations and assets in SQLite; images content-addressed on disk; interrupted batches resume without regenerating finished items.
56
+
57
+ > **Status: pre-release.** `v0.1.0` has not been tagged or published yet, so Thumbforge is not on PyPI. Install it from GitHub as shown below. The [changelog](https://github.com/khiladisngh/thumbforge/blob/main/CHANGELOG.md) lists what is on `main`.
58
+
59
+ ## Install
60
+
61
+ Thumbforge needs [uv](https://docs.astral.sh/uv/) and Python 3.14 (uv downloads Python if it is missing). Install from GitHub:
62
+
63
+ ```
64
+ uv tool install git+https://github.com/khiladisngh/thumbforge
65
+ ```
66
+
67
+ or from a checkout:
68
+
69
+ ```
70
+ git clone https://github.com/khiladisngh/thumbforge
71
+ cd thumbforge
72
+ uv tool install .
73
+ ```
74
+
75
+ Check it:
76
+
77
+ ```
78
+ thumbforge --version
79
+ ```
80
+
81
+ > **Once v0.1.0 is published** (not yet): `uv tool install thumbforge`.
82
+
83
+ ## Quick start
84
+
85
+ This reproduces the three examples of [`PLAN.md` §5.3](https://github.com/khiladisngh/thumbforge/blob/main/PLAN.md#53-examples) — fetch a playlist, generate a hero, run a batch — with the offline `fake` provider, so it needs no account and no API key. `fetch` reads public YouTube metadata, so it needs network access. The images the `fake` provider returns are placeholders, not artwork.
86
+
87
+ Point Thumbforge at a scratch config and data directory first, so nothing touches your real setup. These two lines are the only shell-specific part:
88
+
89
+ ```
90
+ # Linux / macOS
91
+ export THUMBFORGE_CONFIG="$PWD/scratch/config.toml"
92
+ export THUMBFORGE_GENERAL__DATA_DIR="$PWD/scratch/data"
93
+ ```
94
+
95
+ ```
96
+ # Windows PowerShell
97
+ $env:THUMBFORGE_CONFIG = "$PWD\scratch\config.toml"
98
+ $env:THUMBFORGE_GENERAL__DATA_DIR = "$PWD\scratch\data"
99
+ ```
100
+
101
+ Create the database, which also stores the built-in templates:
102
+
103
+ ```
104
+ $ thumbforge db init
105
+ initialized database at <data-dir>/thumbforge.sqlite3 (0001)
106
+ ```
107
+
108
+ **1. Fetch a playlist.** This one is public and large (about 180 videos); `batch --only` below keeps the run short. It prints the playlist and one row per video, with the video's part number and id:
109
+
110
+ ```
111
+ $ thumbforge fetch "https://www.youtube.com/playlist?list=PLFgquLnL59alCl_2TQvOiD5Vgm1hCaGSI"
112
+ …
113
+ Stored 1 channel, 1 playlist, <n> videos.
114
+ ```
115
+
116
+ **2. Generate a hero.** Pass the id of the first video from the table above as `<video-id>`:
117
+
118
+ ```
119
+ $ thumbforge thumb generate <video-id> --template bold-title --provider fake --n 4
120
+ Run <run-id>
121
+ Kind hero
122
+ Status completed
123
+ Template bold-title@1
124
+ Provider fake@0.1.0:<fingerprint>
125
+ Video <video-id>
126
+ Started <timestamp>
127
+ Finished <timestamp>
128
+ ┏━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━━┓
129
+ ┃ # ┃ Status ┃ Size ┃ Compliant ┃ Key ┃ Cost ┃ Asset / Error ┃
130
+ ┡━━━╇━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━━━━┩
131
+ │ 1 │ completed │ 1920x1080 │ ✔ │ <key> │ — │ <asset> │
132
+ │ 2 │ completed │ 1920x1080 │ ✔ │ <key> │ — │ <asset> │
133
+ │ 3 │ completed │ 1920x1080 │ ✔ │ <key> │ — │ <asset> │
134
+ │ 4 │ completed │ 1920x1080 │ ✔ │ <key> │ — │ <asset> │
135
+ └───┴───────────┴───────────┴───────────┴──────────────┴──────┴───────────────┘
136
+ …
137
+ Pick one with: thumbforge thumb pick <run-id> <ordinal>
138
+ ```
139
+
140
+ The `…` is a table of file paths (a terminal that can draw images shows a preview instead). Copy the run id from the first line.
141
+
142
+ **3. Pick one candidate.** The picked image becomes the style reference for the batch:
143
+
144
+ ```
145
+ $ thumbforge thumb pick <run-id> 2
146
+ Picked #2 of run <run-id> (iteration <iteration-id>)
147
+ Export it with: thumbforge thumb export <run-id> --to PATH
148
+ ```
149
+
150
+ **4. Run a batch** for the first three videos of the playlist, in the hero's style:
151
+
152
+ ```
153
+ $ thumbforge batch PLFgquLnL59alCl_2TQvOiD5Vgm1hCaGSI --hero <run-id> --template series-parts --only 1-3
154
+ Batch run <batch-run-id>
155
+ Playlist <playlist-title>
156
+ Template series-parts@1
157
+ Provider fake@0.1.0:<fingerprint>
158
+ Reference <asset> (final)
159
+ Parent run <run-id>
160
+ Status completed
161
+ Items 3 completed, 0 failed, 0 pending of 3
162
+ …
163
+ ```
164
+
165
+ Run ids, asset hashes and timestamps differ on every run, and the playlist's videos change over time. Progress lines such as `run started` go to stderr and are left out above; `--quiet` hides them. `batch --dry-run` prints the plan and generates nothing. If a batch is interrupted or some items fail, `thumbforge runs resume <batch-run-id>` finishes the rest without regenerating the completed ones.
166
+
167
+ Where to go next:
168
+
169
+ - [Getting started](https://khiladisngh.github.io/thumbforge/user-guide/getting-started/): the same flow with templates, exporting and playlist numbering.
170
+ - [Generate a hero](https://khiladisngh.github.io/thumbforge/user-guide/how-to/generate-a-hero/) and [Pick and refine](https://khiladisngh.github.io/thumbforge/user-guide/how-to/pick-and-refine/).
171
+ - [Providers](https://khiladisngh.github.io/thumbforge/user-guide/concepts/providers/): the real image providers and their keys.
172
+ - [Shell completion](https://khiladisngh.github.io/thumbforge/user-guide/how-to/shell-completion/).
173
+
174
+ ## Documentation
175
+
176
+ Published at **https://khiladisngh.github.io/thumbforge/**, in three sections: a user guide, developer docs (architecture, conventions, testing, specs) and maintainer docs (release process, CI, roadmap, decision records).
177
+
178
+ - [`PLAN.md`](https://github.com/khiladisngh/thumbforge/blob/main/PLAN.md) — full project plan
179
+ - [`CHANGELOG.md`](https://github.com/khiladisngh/thumbforge/blob/main/CHANGELOG.md) — changes by Conventional Commit type
180
+ - [`AGENTS.md`](https://github.com/khiladisngh/thumbforge/blob/main/AGENTS.md) — instructions for coding agents (this repo is built spec-first by agents)
181
+ - [`OPEN_QUESTIONS.md`](https://github.com/khiladisngh/thumbforge/blob/main/OPEN_QUESTIONS.md) — spikes and decisions
182
+
183
+ ## Development
184
+
185
+ ```
186
+ uv sync --locked
187
+ uv run pytest -q
188
+ uv run ruff check . && uv run ruff format .
189
+ uv run pyright
190
+ uv run zensical serve # docs at http://localhost:8000
191
+ pre-commit install
192
+ ```
193
+
194
+ ## License
195
+
196
+ [MIT](https://github.com/khiladisngh/thumbforge/blob/main/LICENSE) © 2026 Gishant Singh
@@ -0,0 +1,154 @@
1
+ # thumbforge
2
+
3
+ [![ci](https://github.com/khiladisngh/thumbforge/actions/workflows/ci.yml/badge.svg)](https://github.com/khiladisngh/thumbforge/actions/workflows/ci.yml)
4
+ [![docs](https://github.com/khiladisngh/thumbforge/actions/workflows/docs.yml/badge.svg)](https://khiladisngh.github.io/thumbforge/)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/khiladisngh/thumbforge/blob/main/LICENSE)
6
+ ![Python 3.14](https://img.shields.io/badge/python-3.14-blue)
7
+
8
+ Generate consistent, spec-compliant YouTube thumbnails from a hero image and a playlist — from the terminal.
9
+
10
+ - **Hero first.** Generate several candidates for one video, compare, pick, refine.
11
+ - **Batch second.** Use the picked hero as the style reference for every video in a playlist, with deterministic titles and "Part N" badges rendered by Pillow over AI-generated art.
12
+ - **Pluggable providers.** Antigravity CLI first; add your own via the `thumbforge.providers` entry-point group. A deterministic `fake` provider keeps tests offline.
13
+ - **Local database.** Channels, playlists, videos, templates, runs, iterations and assets in SQLite; images content-addressed on disk; interrupted batches resume without regenerating finished items.
14
+
15
+ > **Status: pre-release.** `v0.1.0` has not been tagged or published yet, so Thumbforge is not on PyPI. Install it from GitHub as shown below. The [changelog](https://github.com/khiladisngh/thumbforge/blob/main/CHANGELOG.md) lists what is on `main`.
16
+
17
+ ## Install
18
+
19
+ Thumbforge needs [uv](https://docs.astral.sh/uv/) and Python 3.14 (uv downloads Python if it is missing). Install from GitHub:
20
+
21
+ ```
22
+ uv tool install git+https://github.com/khiladisngh/thumbforge
23
+ ```
24
+
25
+ or from a checkout:
26
+
27
+ ```
28
+ git clone https://github.com/khiladisngh/thumbforge
29
+ cd thumbforge
30
+ uv tool install .
31
+ ```
32
+
33
+ Check it:
34
+
35
+ ```
36
+ thumbforge --version
37
+ ```
38
+
39
+ > **Once v0.1.0 is published** (not yet): `uv tool install thumbforge`.
40
+
41
+ ## Quick start
42
+
43
+ This reproduces the three examples of [`PLAN.md` §5.3](https://github.com/khiladisngh/thumbforge/blob/main/PLAN.md#53-examples) — fetch a playlist, generate a hero, run a batch — with the offline `fake` provider, so it needs no account and no API key. `fetch` reads public YouTube metadata, so it needs network access. The images the `fake` provider returns are placeholders, not artwork.
44
+
45
+ Point Thumbforge at a scratch config and data directory first, so nothing touches your real setup. These two lines are the only shell-specific part:
46
+
47
+ ```
48
+ # Linux / macOS
49
+ export THUMBFORGE_CONFIG="$PWD/scratch/config.toml"
50
+ export THUMBFORGE_GENERAL__DATA_DIR="$PWD/scratch/data"
51
+ ```
52
+
53
+ ```
54
+ # Windows PowerShell
55
+ $env:THUMBFORGE_CONFIG = "$PWD\scratch\config.toml"
56
+ $env:THUMBFORGE_GENERAL__DATA_DIR = "$PWD\scratch\data"
57
+ ```
58
+
59
+ Create the database, which also stores the built-in templates:
60
+
61
+ ```
62
+ $ thumbforge db init
63
+ initialized database at <data-dir>/thumbforge.sqlite3 (0001)
64
+ ```
65
+
66
+ **1. Fetch a playlist.** This one is public and large (about 180 videos); `batch --only` below keeps the run short. It prints the playlist and one row per video, with the video's part number and id:
67
+
68
+ ```
69
+ $ thumbforge fetch "https://www.youtube.com/playlist?list=PLFgquLnL59alCl_2TQvOiD5Vgm1hCaGSI"
70
+ …
71
+ Stored 1 channel, 1 playlist, <n> videos.
72
+ ```
73
+
74
+ **2. Generate a hero.** Pass the id of the first video from the table above as `<video-id>`:
75
+
76
+ ```
77
+ $ thumbforge thumb generate <video-id> --template bold-title --provider fake --n 4
78
+ Run <run-id>
79
+ Kind hero
80
+ Status completed
81
+ Template bold-title@1
82
+ Provider fake@0.1.0:<fingerprint>
83
+ Video <video-id>
84
+ Started <timestamp>
85
+ Finished <timestamp>
86
+ ┏━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━━┓
87
+ ┃ # ┃ Status ┃ Size ┃ Compliant ┃ Key ┃ Cost ┃ Asset / Error ┃
88
+ ┡━━━╇━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━━━━┩
89
+ │ 1 │ completed │ 1920x1080 │ ✔ │ <key> │ — │ <asset> │
90
+ │ 2 │ completed │ 1920x1080 │ ✔ │ <key> │ — │ <asset> │
91
+ │ 3 │ completed │ 1920x1080 │ ✔ │ <key> │ — │ <asset> │
92
+ │ 4 │ completed │ 1920x1080 │ ✔ │ <key> │ — │ <asset> │
93
+ └───┴───────────┴───────────┴───────────┴──────────────┴──────┴───────────────┘
94
+ …
95
+ Pick one with: thumbforge thumb pick <run-id> <ordinal>
96
+ ```
97
+
98
+ The `…` is a table of file paths (a terminal that can draw images shows a preview instead). Copy the run id from the first line.
99
+
100
+ **3. Pick one candidate.** The picked image becomes the style reference for the batch:
101
+
102
+ ```
103
+ $ thumbforge thumb pick <run-id> 2
104
+ Picked #2 of run <run-id> (iteration <iteration-id>)
105
+ Export it with: thumbforge thumb export <run-id> --to PATH
106
+ ```
107
+
108
+ **4. Run a batch** for the first three videos of the playlist, in the hero's style:
109
+
110
+ ```
111
+ $ thumbforge batch PLFgquLnL59alCl_2TQvOiD5Vgm1hCaGSI --hero <run-id> --template series-parts --only 1-3
112
+ Batch run <batch-run-id>
113
+ Playlist <playlist-title>
114
+ Template series-parts@1
115
+ Provider fake@0.1.0:<fingerprint>
116
+ Reference <asset> (final)
117
+ Parent run <run-id>
118
+ Status completed
119
+ Items 3 completed, 0 failed, 0 pending of 3
120
+ …
121
+ ```
122
+
123
+ Run ids, asset hashes and timestamps differ on every run, and the playlist's videos change over time. Progress lines such as `run started` go to stderr and are left out above; `--quiet` hides them. `batch --dry-run` prints the plan and generates nothing. If a batch is interrupted or some items fail, `thumbforge runs resume <batch-run-id>` finishes the rest without regenerating the completed ones.
124
+
125
+ Where to go next:
126
+
127
+ - [Getting started](https://khiladisngh.github.io/thumbforge/user-guide/getting-started/): the same flow with templates, exporting and playlist numbering.
128
+ - [Generate a hero](https://khiladisngh.github.io/thumbforge/user-guide/how-to/generate-a-hero/) and [Pick and refine](https://khiladisngh.github.io/thumbforge/user-guide/how-to/pick-and-refine/).
129
+ - [Providers](https://khiladisngh.github.io/thumbforge/user-guide/concepts/providers/): the real image providers and their keys.
130
+ - [Shell completion](https://khiladisngh.github.io/thumbforge/user-guide/how-to/shell-completion/).
131
+
132
+ ## Documentation
133
+
134
+ Published at **https://khiladisngh.github.io/thumbforge/**, in three sections: a user guide, developer docs (architecture, conventions, testing, specs) and maintainer docs (release process, CI, roadmap, decision records).
135
+
136
+ - [`PLAN.md`](https://github.com/khiladisngh/thumbforge/blob/main/PLAN.md) — full project plan
137
+ - [`CHANGELOG.md`](https://github.com/khiladisngh/thumbforge/blob/main/CHANGELOG.md) — changes by Conventional Commit type
138
+ - [`AGENTS.md`](https://github.com/khiladisngh/thumbforge/blob/main/AGENTS.md) — instructions for coding agents (this repo is built spec-first by agents)
139
+ - [`OPEN_QUESTIONS.md`](https://github.com/khiladisngh/thumbforge/blob/main/OPEN_QUESTIONS.md) — spikes and decisions
140
+
141
+ ## Development
142
+
143
+ ```
144
+ uv sync --locked
145
+ uv run pytest -q
146
+ uv run ruff check . && uv run ruff format .
147
+ uv run pyright
148
+ uv run zensical serve # docs at http://localhost:8000
149
+ pre-commit install
150
+ ```
151
+
152
+ ## License
153
+
154
+ [MIT](https://github.com/khiladisngh/thumbforge/blob/main/LICENSE) © 2026 Gishant Singh
@@ -0,0 +1,197 @@
1
+ [project]
2
+ name = "thumbforge"
3
+ version = "0.1.0"
4
+ description = "Generate consistent, spec-compliant YouTube thumbnails from a hero image and a playlist."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.14"
9
+ keywords = [
10
+ "youtube",
11
+ "thumbnail",
12
+ "cli",
13
+ "image-generation",
14
+ "typer",
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 2 - Pre-Alpha",
18
+ "Environment :: Console",
19
+ "Intended Audience :: End Users/Desktop",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.14",
23
+ "Topic :: Multimedia :: Graphics",
24
+ ]
25
+ dependencies = [
26
+ "alembic>=1.14",
27
+ "jinja2>=3.1.6",
28
+ "keyring>=25.0",
29
+ "pillow>=10.0",
30
+ "platformdirs>=4.11.10",
31
+ "pydantic>=2.13.5",
32
+ "pydantic-settings>=2.15.0",
33
+ "rich>=15.0.0",
34
+ "rich-pixels>=3.0.1",
35
+ "sqlalchemy>=2.0,<2.1",
36
+ "structlog>=26.1.0",
37
+ "tenacity>=9.0",
38
+ "tomli-w>=1.2.0",
39
+ "typer",
40
+ "yt-dlp>=2026.8.19",
41
+ ]
42
+
43
+ [[project.authors]]
44
+ name = "Gishant Singh"
45
+ email = "khiladisngh@hotmail.com"
46
+
47
+ [project.optional-dependencies]
48
+ api = [
49
+ "google-api-python-client",
50
+ "google-auth",
51
+ ]
52
+
53
+ [project.urls]
54
+ Homepage = "https://github.com/khiladisngh/thumbforge"
55
+ Documentation = "https://khiladisngh.github.io/thumbforge/"
56
+ Repository = "https://github.com/khiladisngh/thumbforge"
57
+ Issues = "https://github.com/khiladisngh/thumbforge/issues"
58
+ Changelog = "https://github.com/khiladisngh/thumbforge/blob/main/CHANGELOG.md"
59
+
60
+ [project.scripts]
61
+ thumbforge = "thumbforge.cli.app:main"
62
+
63
+ [project.entry-points."thumbforge.providers"]
64
+
65
+ [dependency-groups]
66
+ dev = [
67
+ "ruff",
68
+ "pyright",
69
+ "pytest",
70
+ "pytest-asyncio",
71
+ "pytest-cov",
72
+ "pre-commit",
73
+ "import-linter",
74
+ "zensical",
75
+ "testcontainers>=4.15.0",
76
+ "google-api-python-client",
77
+ "google-api-python-client-stubs",
78
+ "google-auth",
79
+ ]
80
+
81
+ [build-system]
82
+ requires = ["uv_build>=0.12.16,<0.13.0"]
83
+ build-backend = "uv_build"
84
+
85
+ [tool.ruff]
86
+ line-length = 100
87
+ target-version = "py314"
88
+
89
+ [tool.ruff.lint]
90
+ select = [
91
+ "E",
92
+ "F",
93
+ "I",
94
+ "UP",
95
+ "B",
96
+ "SIM",
97
+ "TCH",
98
+ "RUF",
99
+ ]
100
+
101
+ [tool.ruff.lint.flake8-type-checking]
102
+ runtime-evaluated-base-classes = [
103
+ "pydantic.BaseModel",
104
+ "pydantic_settings.BaseSettings",
105
+ ]
106
+
107
+ [tool.ruff.lint.per-file-ignores]
108
+ "src/thumbforge/cli/*.py" = [
109
+ "TC001",
110
+ "TC002",
111
+ "TC003",
112
+ ]
113
+
114
+ [tool.pyright]
115
+ strict = ["src"]
116
+ pythonVersion = "3.14"
117
+ venvPath = "."
118
+ venv = ".venv"
119
+
120
+ [tool.pytest.ini_options]
121
+ asyncio_mode = "auto"
122
+ markers = [
123
+ "integration: hits the network or a real provider; opt in with -m integration",
124
+ "golden: image or prompt-text snapshot comparison",
125
+ ]
126
+ testpaths = ["tests"]
127
+ addopts = "-m 'not integration'"
128
+
129
+ [tool.coverage.run]
130
+ source = ["thumbforge"]
131
+ branch = true
132
+ omit = ["src/thumbforge/__main__.py"]
133
+
134
+ [tool.coverage.report]
135
+ fail_under = 80
136
+ show_missing = true
137
+
138
+ [tool.importlinter]
139
+ root_package = "thumbforge"
140
+
141
+ [[tool.importlinter.contracts]]
142
+ name = "nothing imports cli"
143
+ type = "forbidden"
144
+ source_modules = [
145
+ "thumbforge.core",
146
+ "thumbforge.storage",
147
+ "thumbforge.sources",
148
+ "thumbforge.providers",
149
+ "thumbforge.settings",
150
+ "thumbforge.logging",
151
+ "thumbforge.credentials",
152
+ "thumbforge.imaging",
153
+ "thumbforge.templates",
154
+ ]
155
+ forbidden_modules = ["thumbforge.cli"]
156
+
157
+ [[tool.importlinter.contracts]]
158
+ name = "core is pure"
159
+ type = "forbidden"
160
+ source_modules = ["thumbforge.core"]
161
+ forbidden_modules = [
162
+ "thumbforge.storage",
163
+ "thumbforge.sources",
164
+ "thumbforge.providers",
165
+ "thumbforge.cli",
166
+ "thumbforge.settings",
167
+ "thumbforge.logging",
168
+ "thumbforge.credentials",
169
+ "thumbforge.imaging",
170
+ "thumbforge.templates",
171
+ ]
172
+
173
+ [[tool.importlinter.contracts]]
174
+ name = "adapters are independent"
175
+ type = "independence"
176
+ modules = [
177
+ "thumbforge.storage",
178
+ "thumbforge.sources",
179
+ "thumbforge.providers",
180
+ "thumbforge.templates",
181
+ "thumbforge.imaging",
182
+ ]
183
+
184
+ [[tool.importlinter.contracts]]
185
+ name = "core leaf modules import nothing from core"
186
+ type = "forbidden"
187
+ source_modules = [
188
+ "thumbforge.core.enums",
189
+ "thumbforge.core.errors",
190
+ "thumbforge.core.ids",
191
+ "thumbforge.core.json",
192
+ ]
193
+ forbidden_modules = [
194
+ "thumbforge.core.models",
195
+ "thumbforge.core.providers",
196
+ "thumbforge.core.urls",
197
+ ]