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.
- thumbforge-0.1.0/LICENSE +21 -0
- thumbforge-0.1.0/PKG-INFO +196 -0
- thumbforge-0.1.0/README.md +154 -0
- thumbforge-0.1.0/pyproject.toml +197 -0
- thumbforge-0.1.0/pyproject.toml.orig +188 -0
- thumbforge-0.1.0/src/thumbforge/__init__.py +10 -0
- thumbforge-0.1.0/src/thumbforge/__main__.py +3 -0
- thumbforge-0.1.0/src/thumbforge/cli/__init__.py +1 -0
- thumbforge-0.1.0/src/thumbforge/cli/_errors.py +89 -0
- thumbforge-0.1.0/src/thumbforge/cli/_render.py +196 -0
- thumbforge-0.1.0/src/thumbforge/cli/_runs.py +429 -0
- thumbforge-0.1.0/src/thumbforge/cli/_youtube.py +165 -0
- thumbforge-0.1.0/src/thumbforge/cli/app.py +155 -0
- thumbforge-0.1.0/src/thumbforge/cli/batch.py +398 -0
- thumbforge-0.1.0/src/thumbforge/cli/config.py +147 -0
- thumbforge-0.1.0/src/thumbforge/cli/db.py +151 -0
- thumbforge-0.1.0/src/thumbforge/cli/fetch.py +124 -0
- thumbforge-0.1.0/src/thumbforge/cli/playlist.py +168 -0
- thumbforge-0.1.0/src/thumbforge/cli/provider.py +218 -0
- thumbforge-0.1.0/src/thumbforge/cli/runs.py +199 -0
- thumbforge-0.1.0/src/thumbforge/cli/template.py +271 -0
- thumbforge-0.1.0/src/thumbforge/cli/thumb.py +278 -0
- thumbforge-0.1.0/src/thumbforge/cli/video.py +107 -0
- thumbforge-0.1.0/src/thumbforge/core/__init__.py +5 -0
- thumbforge-0.1.0/src/thumbforge/core/enums.py +48 -0
- thumbforge-0.1.0/src/thumbforge/core/errors.py +206 -0
- thumbforge-0.1.0/src/thumbforge/core/ids.py +38 -0
- thumbforge-0.1.0/src/thumbforge/core/json.py +37 -0
- thumbforge-0.1.0/src/thumbforge/core/layout.py +188 -0
- thumbforge-0.1.0/src/thumbforge/core/models.py +112 -0
- thumbforge-0.1.0/src/thumbforge/core/providers.py +205 -0
- thumbforge-0.1.0/src/thumbforge/core/redaction.py +99 -0
- thumbforge-0.1.0/src/thumbforge/core/services/__init__.py +6 -0
- thumbforge-0.1.0/src/thumbforge/core/services/batch.py +733 -0
- thumbforge-0.1.0/src/thumbforge/core/services/fetch.py +188 -0
- thumbforge-0.1.0/src/thumbforge/core/services/hero.py +698 -0
- thumbforge-0.1.0/src/thumbforge/core/services/iterate.py +96 -0
- thumbforge-0.1.0/src/thumbforge/core/sources.py +44 -0
- thumbforge-0.1.0/src/thumbforge/core/urls.py +152 -0
- thumbforge-0.1.0/src/thumbforge/credentials.py +113 -0
- thumbforge-0.1.0/src/thumbforge/imaging/__init__.py +1 -0
- thumbforge-0.1.0/src/thumbforge/imaging/compliance.py +66 -0
- thumbforge-0.1.0/src/thumbforge/imaging/finalize.py +112 -0
- thumbforge-0.1.0/src/thumbforge/imaging/fit.py +25 -0
- thumbforge-0.1.0/src/thumbforge/imaging/fonts/Inter-Bold.ttf +0 -0
- thumbforge-0.1.0/src/thumbforge/imaging/fonts/OFL.txt +92 -0
- thumbforge-0.1.0/src/thumbforge/imaging/fonts.py +47 -0
- thumbforge-0.1.0/src/thumbforge/imaging/overlay.py +234 -0
- thumbforge-0.1.0/src/thumbforge/logging.py +170 -0
- thumbforge-0.1.0/src/thumbforge/providers/__init__.py +9 -0
- thumbforge-0.1.0/src/thumbforge/providers/antigravity.py +661 -0
- thumbforge-0.1.0/src/thumbforge/providers/fake.py +187 -0
- thumbforge-0.1.0/src/thumbforge/providers/registry.py +127 -0
- thumbforge-0.1.0/src/thumbforge/settings.py +454 -0
- thumbforge-0.1.0/src/thumbforge/sources/__init__.py +5 -0
- thumbforge-0.1.0/src/thumbforge/sources/youtube_api.py +497 -0
- thumbforge-0.1.0/src/thumbforge/sources/ytdlp.py +362 -0
- thumbforge-0.1.0/src/thumbforge/storage/__init__.py +52 -0
- thumbforge-0.1.0/src/thumbforge/storage/assets.py +171 -0
- thumbforge-0.1.0/src/thumbforge/storage/db.py +290 -0
- thumbforge-0.1.0/src/thumbforge/storage/migrations/env.py +74 -0
- thumbforge-0.1.0/src/thumbforge/storage/migrations/script.py.mako +33 -0
- thumbforge-0.1.0/src/thumbforge/storage/migrations/versions/0001_initial.py +281 -0
- thumbforge-0.1.0/src/thumbforge/storage/models.py +373 -0
- thumbforge-0.1.0/src/thumbforge/storage/repositories.py +462 -0
- thumbforge-0.1.0/src/thumbforge/storage/runs.py +600 -0
- thumbforge-0.1.0/src/thumbforge/templates/__init__.py +5 -0
- thumbforge-0.1.0/src/thumbforge/templates/builtin/bold-title.j2 +5 -0
- thumbforge-0.1.0/src/thumbforge/templates/builtin/bold-title.toml +29 -0
- thumbforge-0.1.0/src/thumbforge/templates/builtin/minimal.j2 +5 -0
- thumbforge-0.1.0/src/thumbforge/templates/builtin/minimal.toml +29 -0
- thumbforge-0.1.0/src/thumbforge/templates/builtin/series-parts.j2 +10 -0
- thumbforge-0.1.0/src/thumbforge/templates/builtin/series-parts.toml +32 -0
- thumbforge-0.1.0/src/thumbforge/templates/builtins.py +29 -0
- thumbforge-0.1.0/src/thumbforge/templates/loader.py +284 -0
- thumbforge-0.1.0/src/thumbforge/templates/render.py +117 -0
- thumbforge-0.1.0/src/thumbforge/templates/schema.py +49 -0
thumbforge-0.1.0/LICENSE
ADDED
|
@@ -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
|
+
[](https://github.com/khiladisngh/thumbforge/actions/workflows/ci.yml)
|
|
46
|
+
[](https://khiladisngh.github.io/thumbforge/)
|
|
47
|
+
[](https://github.com/khiladisngh/thumbforge/blob/main/LICENSE)
|
|
48
|
+

|
|
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
|
+
[](https://github.com/khiladisngh/thumbforge/actions/workflows/ci.yml)
|
|
4
|
+
[](https://khiladisngh.github.io/thumbforge/)
|
|
5
|
+
[](https://github.com/khiladisngh/thumbforge/blob/main/LICENSE)
|
|
6
|
+

|
|
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
|
+
]
|