schenesort 2.2.0__tar.gz → 2.3.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.
- {schenesort-2.2.0 → schenesort-2.3.0}/PKG-INFO +38 -6
- {schenesort-2.2.0 → schenesort-2.3.0}/README.md +37 -5
- schenesort-2.3.0/docs/browse-destruction.png +0 -0
- schenesort-2.3.0/docs/browse-greek.png +0 -0
- schenesort-2.3.0/docs/collage.png +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/pyproject.toml +2 -2
- {schenesort-2.2.0 → schenesort-2.3.0}/src/schenesort/cli.py +132 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/uv.lock +1 -1
- schenesort-2.2.0/docs/browse-greek.png +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/.envrc +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/.github/workflows/ci.yml +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/.github/workflows/publish.yml +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/.gitignore +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/.pre-commit-config.yaml +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/.python-version +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/CLAUDE.md +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/LICENSE +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/docs/browse-autumn.png +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/docs/browse-stallman.png +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/justfile +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/ollama-setup.md +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/schenesort.yazi/README.md +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/schenesort.yazi/main.lua +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/schenesort.yazi/schenesort.config +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/src/schenesort/__init__.py +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/src/schenesort/config.py +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/src/schenesort/db.py +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/src/schenesort/tui/__init__.py +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/src/schenesort/tui/app.py +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/src/schenesort/tui/widgets/__init__.py +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/src/schenesort/tui/widgets/image_preview.py +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/src/schenesort/tui/widgets/metadata_panel.py +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/src/schenesort/xmp.py +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/tests/__init__.py +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/tests/test_cli.py +0 -0
- {schenesort-2.2.0 → schenesort-2.3.0}/tests/test_sanitise.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: schenesort
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Wallpaper collection management CLI tool
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.13
|
|
@@ -12,7 +12,7 @@ Requires-Dist: textual>=0.95.0
|
|
|
12
12
|
Requires-Dist: typer>=0.21.1
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
|
|
15
|
-
# Schenesort v2.
|
|
15
|
+
# Schenesort v2.3.0
|
|
16
16
|
|
|
17
17
|
A CLI tool for managing wallpaper collections with model generated metadata, terminal UI browsing, and SQLite-based
|
|
18
18
|
querying.
|
|
@@ -26,7 +26,7 @@ Once you have a collection of wallpapers re-named with metadata sidecars, run th
|
|
|
26
26
|
that can be queried to retrieve suggestions based on tags, colours, names and the like. Then use `get` to get a
|
|
27
27
|
wallpaper path `feh $(schenesort get -1 -p)` or `hyprctl hyprpaper wallpaper "eDP-1,$(schenesort get -1 -p)"`
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+

|
|
30
30
|
|
|
31
31
|
## Installation
|
|
32
32
|
|
|
@@ -44,6 +44,9 @@ uv sync
|
|
|
44
44
|
|
|
45
45
|
## Quick Start
|
|
46
46
|
|
|
47
|
+
To generate metadata you need a olama server serving llava nearby, it takes a minute to set one up see [ollama
|
|
48
|
+
setup](./ollama-setup.md).
|
|
49
|
+
|
|
47
50
|
```bash
|
|
48
51
|
# Generate metadata for images
|
|
49
52
|
schenesort metadata generate ~/wallpapers -r
|
|
@@ -74,6 +77,7 @@ schenesort get -1 -p | xargs feh # random wallpaper
|
|
|
74
77
|
| `info` | Show collection file statistics |
|
|
75
78
|
| `describe` | AI-rename images based on content (Ollama) |
|
|
76
79
|
| `models` | List available Ollama models |
|
|
80
|
+
| `collage` | Create a collage grid from matching wallpapers |
|
|
77
81
|
| `metadata show` | Display XMP sidecar metadata |
|
|
78
82
|
| `metadata set` | Manually set metadata fields |
|
|
79
83
|
| `metadata generate` | Generate metadata with AI (Ollama) |
|
|
@@ -91,8 +95,6 @@ schenesort browse # uses paths.wallpaper from config
|
|
|
91
95
|
schenesort get --mood peaceful -b # browse query results
|
|
92
96
|
```
|
|
93
97
|
|
|
94
|
-

|
|
95
|
-
|
|
96
98
|

|
|
97
99
|
|
|
98
100
|

|
|
@@ -147,6 +149,36 @@ schenesort stats
|
|
|
147
149
|
|
|
148
150
|
The database is stored at `$XDG_DATA_HOME/schenesort/index.db` (default: `~/.local/share/schenesort/index.db`).
|
|
149
151
|
|
|
152
|
+
## Collage Generation
|
|
153
|
+
|
|
154
|
+
Create a collage grid (up to 4x4) from wallpapers matching query criteria:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# Create a 2x2 collage (default)
|
|
158
|
+
schenesort collage output.png --mood peaceful
|
|
159
|
+
|
|
160
|
+
# Create a 4x4 collage of landscape images
|
|
161
|
+
schenesort collage wall.png --subject landscape --cols 4 --rows 4
|
|
162
|
+
|
|
163
|
+
# Custom tile size (default: 480x270)
|
|
164
|
+
schenesort collage collage.png --tile-width 640 --tile-height 360
|
|
165
|
+
|
|
166
|
+
# Combine filters
|
|
167
|
+
schenesort collage night_cities.png --time night --subject urban --cols 3 --rows 2
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+

|
|
171
|
+
|
|
172
|
+
| Option | Description | Default |
|
|
173
|
+
|-----------------|--------------------------------------|---------|
|
|
174
|
+
| `--cols` | Number of columns (1-4) | 2 |
|
|
175
|
+
| `--rows` | Number of rows (1-4) | 2 |
|
|
176
|
+
| `--tile-width` | Width of each tile in pixels | 480 |
|
|
177
|
+
| `--tile-height` | Height of each tile in pixels | 270 |
|
|
178
|
+
| `--random` | Select images randomly | True |
|
|
179
|
+
|
|
180
|
+
All `get` query filters are supported: `--tag`, `--mood`, `--color`, `--style`, `--subject`, `--time`, `--screen`, `--min-width`, `--min-height`, `--search`.
|
|
181
|
+
|
|
150
182
|
## Metadata Management
|
|
151
183
|
|
|
152
184
|
Store metadata in XMP sidecar files (`.xmp`) alongside images without modifying the original files.
|
|
@@ -168,7 +200,7 @@ Store metadata in XMP sidecar files (`.xmp`) alongside images without modifying
|
|
|
168
200
|
| `source` | Source URL or info |
|
|
169
201
|
| `ai_model` | Model used for metadata generation |
|
|
170
202
|
|
|
171
|
-
### Generate Metadata
|
|
203
|
+
### Generate Metadata using ollama
|
|
172
204
|
|
|
173
205
|
```bash
|
|
174
206
|
# Preview what would be generated
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Schenesort v2.
|
|
1
|
+
# Schenesort v2.3.0
|
|
2
2
|
|
|
3
3
|
A CLI tool for managing wallpaper collections with model generated metadata, terminal UI browsing, and SQLite-based
|
|
4
4
|
querying.
|
|
@@ -12,7 +12,7 @@ Once you have a collection of wallpapers re-named with metadata sidecars, run th
|
|
|
12
12
|
that can be queried to retrieve suggestions based on tags, colours, names and the like. Then use `get` to get a
|
|
13
13
|
wallpaper path `feh $(schenesort get -1 -p)` or `hyprctl hyprpaper wallpaper "eDP-1,$(schenesort get -1 -p)"`
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+

|
|
16
16
|
|
|
17
17
|
## Installation
|
|
18
18
|
|
|
@@ -30,6 +30,9 @@ uv sync
|
|
|
30
30
|
|
|
31
31
|
## Quick Start
|
|
32
32
|
|
|
33
|
+
To generate metadata you need a olama server serving llava nearby, it takes a minute to set one up see [ollama
|
|
34
|
+
setup](./ollama-setup.md).
|
|
35
|
+
|
|
33
36
|
```bash
|
|
34
37
|
# Generate metadata for images
|
|
35
38
|
schenesort metadata generate ~/wallpapers -r
|
|
@@ -60,6 +63,7 @@ schenesort get -1 -p | xargs feh # random wallpaper
|
|
|
60
63
|
| `info` | Show collection file statistics |
|
|
61
64
|
| `describe` | AI-rename images based on content (Ollama) |
|
|
62
65
|
| `models` | List available Ollama models |
|
|
66
|
+
| `collage` | Create a collage grid from matching wallpapers |
|
|
63
67
|
| `metadata show` | Display XMP sidecar metadata |
|
|
64
68
|
| `metadata set` | Manually set metadata fields |
|
|
65
69
|
| `metadata generate` | Generate metadata with AI (Ollama) |
|
|
@@ -77,8 +81,6 @@ schenesort browse # uses paths.wallpaper from config
|
|
|
77
81
|
schenesort get --mood peaceful -b # browse query results
|
|
78
82
|
```
|
|
79
83
|
|
|
80
|
-

|
|
81
|
-
|
|
82
84
|

|
|
83
85
|
|
|
84
86
|

|
|
@@ -133,6 +135,36 @@ schenesort stats
|
|
|
133
135
|
|
|
134
136
|
The database is stored at `$XDG_DATA_HOME/schenesort/index.db` (default: `~/.local/share/schenesort/index.db`).
|
|
135
137
|
|
|
138
|
+
## Collage Generation
|
|
139
|
+
|
|
140
|
+
Create a collage grid (up to 4x4) from wallpapers matching query criteria:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Create a 2x2 collage (default)
|
|
144
|
+
schenesort collage output.png --mood peaceful
|
|
145
|
+
|
|
146
|
+
# Create a 4x4 collage of landscape images
|
|
147
|
+
schenesort collage wall.png --subject landscape --cols 4 --rows 4
|
|
148
|
+
|
|
149
|
+
# Custom tile size (default: 480x270)
|
|
150
|
+
schenesort collage collage.png --tile-width 640 --tile-height 360
|
|
151
|
+
|
|
152
|
+
# Combine filters
|
|
153
|
+
schenesort collage night_cities.png --time night --subject urban --cols 3 --rows 2
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+

|
|
157
|
+
|
|
158
|
+
| Option | Description | Default |
|
|
159
|
+
|-----------------|--------------------------------------|---------|
|
|
160
|
+
| `--cols` | Number of columns (1-4) | 2 |
|
|
161
|
+
| `--rows` | Number of rows (1-4) | 2 |
|
|
162
|
+
| `--tile-width` | Width of each tile in pixels | 480 |
|
|
163
|
+
| `--tile-height` | Height of each tile in pixels | 270 |
|
|
164
|
+
| `--random` | Select images randomly | True |
|
|
165
|
+
|
|
166
|
+
All `get` query filters are supported: `--tag`, `--mood`, `--color`, `--style`, `--subject`, `--time`, `--screen`, `--min-width`, `--min-height`, `--search`.
|
|
167
|
+
|
|
136
168
|
## Metadata Management
|
|
137
169
|
|
|
138
170
|
Store metadata in XMP sidecar files (`.xmp`) alongside images without modifying the original files.
|
|
@@ -154,7 +186,7 @@ Store metadata in XMP sidecar files (`.xmp`) alongside images without modifying
|
|
|
154
186
|
| `source` | Source URL or info |
|
|
155
187
|
| `ai_model` | Model used for metadata generation |
|
|
156
188
|
|
|
157
|
-
### Generate Metadata
|
|
189
|
+
### Generate Metadata using ollama
|
|
158
190
|
|
|
159
191
|
```bash
|
|
160
192
|
# Preview what would be generated
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "schenesort"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.3.0"
|
|
4
4
|
description = "Wallpaper collection management CLI tool"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.13"
|
|
@@ -56,7 +56,7 @@ testpaths = ["tests"]
|
|
|
56
56
|
pythonpath = ["src"]
|
|
57
57
|
|
|
58
58
|
[tool.bumpversion]
|
|
59
|
-
current_version = "2.
|
|
59
|
+
current_version = "2.3.0"
|
|
60
60
|
commit = true
|
|
61
61
|
tag = true
|
|
62
62
|
tag_name = "v{new_version}"
|
|
@@ -1225,6 +1225,138 @@ def metadata_update_dimensions(
|
|
|
1225
1225
|
typer.echo(f"\n{action} {updated_count} sidecar(s), skipped {skipped_count}.")
|
|
1226
1226
|
|
|
1227
1227
|
|
|
1228
|
+
@app.command()
|
|
1229
|
+
def collage(
|
|
1230
|
+
output: Annotated[Path, typer.Argument(help="Output PNG file path")],
|
|
1231
|
+
tag: Annotated[str | None, typer.Option("--tag", "-t", help="Filter by tag")] = None,
|
|
1232
|
+
mood: Annotated[str | None, typer.Option("--mood", "-m", help="Filter by mood")] = None,
|
|
1233
|
+
color: Annotated[str | None, typer.Option("--color", "-c", help="Filter by color")] = None,
|
|
1234
|
+
style: Annotated[str | None, typer.Option("--style", "-s", help="Filter by style")] = None,
|
|
1235
|
+
subject: Annotated[str | None, typer.Option("--subject", help="Filter by subject")] = None,
|
|
1236
|
+
time: Annotated[str | None, typer.Option("--time", help="Filter by time of day")] = None,
|
|
1237
|
+
screen: Annotated[
|
|
1238
|
+
str | None, typer.Option("--screen", help="Filter by recommended screen (4K, 1440p, etc)")
|
|
1239
|
+
] = None,
|
|
1240
|
+
min_width: Annotated[
|
|
1241
|
+
int | None, typer.Option("--min-width", help="Minimum width in pixels")
|
|
1242
|
+
] = None,
|
|
1243
|
+
min_height: Annotated[
|
|
1244
|
+
int | None, typer.Option("--min-height", help="Minimum height in pixels")
|
|
1245
|
+
] = None,
|
|
1246
|
+
search: Annotated[
|
|
1247
|
+
str | None, typer.Option("--search", "-q", help="Search description, scene, style, subject")
|
|
1248
|
+
] = None,
|
|
1249
|
+
cols: Annotated[int, typer.Option("--cols", help="Number of columns (1-4)")] = 2,
|
|
1250
|
+
rows: Annotated[int, typer.Option("--rows", help="Number of rows (1-4)")] = 2,
|
|
1251
|
+
tile_width: Annotated[
|
|
1252
|
+
int, typer.Option("--tile-width", "-w", help="Width of each tile in pixels")
|
|
1253
|
+
] = 480,
|
|
1254
|
+
tile_height: Annotated[
|
|
1255
|
+
int, typer.Option("--tile-height", "-h", help="Height of each tile in pixels")
|
|
1256
|
+
] = 270,
|
|
1257
|
+
random: Annotated[bool, typer.Option("--random", "-R", help="Select images randomly")] = True,
|
|
1258
|
+
) -> None:
|
|
1259
|
+
"""Create a collage of wallpapers matching the given criteria (max 4x4 grid)."""
|
|
1260
|
+
from PIL import Image
|
|
1261
|
+
|
|
1262
|
+
from schenesort.db import WallpaperDB
|
|
1263
|
+
|
|
1264
|
+
# Validate grid size
|
|
1265
|
+
if cols < 1 or cols > 4:
|
|
1266
|
+
typer.echo("Error: --cols must be between 1 and 4.", err=True)
|
|
1267
|
+
raise typer.Exit(1)
|
|
1268
|
+
if rows < 1 or rows > 4:
|
|
1269
|
+
typer.echo("Error: --rows must be between 1 and 4.", err=True)
|
|
1270
|
+
raise typer.Exit(1)
|
|
1271
|
+
|
|
1272
|
+
num_images = cols * rows
|
|
1273
|
+
|
|
1274
|
+
with WallpaperDB() as db:
|
|
1275
|
+
results = db.query(
|
|
1276
|
+
tag=tag,
|
|
1277
|
+
mood=mood,
|
|
1278
|
+
color=color,
|
|
1279
|
+
style=style,
|
|
1280
|
+
subject=subject,
|
|
1281
|
+
time_of_day=time,
|
|
1282
|
+
screen=screen,
|
|
1283
|
+
min_width=min_width,
|
|
1284
|
+
min_height=min_height,
|
|
1285
|
+
search=search,
|
|
1286
|
+
limit=num_images,
|
|
1287
|
+
random=random,
|
|
1288
|
+
)
|
|
1289
|
+
|
|
1290
|
+
if not results:
|
|
1291
|
+
typer.echo("No wallpapers found matching criteria.", err=True)
|
|
1292
|
+
raise typer.Exit(1)
|
|
1293
|
+
|
|
1294
|
+
if len(results) < num_images:
|
|
1295
|
+
typer.echo(
|
|
1296
|
+
f"Warning: Only found {len(results)} image(s), "
|
|
1297
|
+
f"need {num_images} for {cols}x{rows} grid."
|
|
1298
|
+
)
|
|
1299
|
+
|
|
1300
|
+
# Create collage canvas
|
|
1301
|
+
collage_width = cols * tile_width
|
|
1302
|
+
collage_height = rows * tile_height
|
|
1303
|
+
collage_img = Image.new("RGB", (collage_width, collage_height), color=(0, 0, 0))
|
|
1304
|
+
|
|
1305
|
+
typer.echo(f"Creating {cols}x{rows} collage ({collage_width}x{collage_height})...")
|
|
1306
|
+
|
|
1307
|
+
for idx, r in enumerate(results):
|
|
1308
|
+
if idx >= num_images:
|
|
1309
|
+
break
|
|
1310
|
+
|
|
1311
|
+
filepath = Path(r["path"])
|
|
1312
|
+
row = idx // cols
|
|
1313
|
+
col = idx % cols
|
|
1314
|
+
|
|
1315
|
+
try:
|
|
1316
|
+
with Image.open(filepath) as img:
|
|
1317
|
+
# Convert to RGB if necessary (handles RGBA, palette, etc.)
|
|
1318
|
+
if img.mode != "RGB":
|
|
1319
|
+
img = img.convert("RGB")
|
|
1320
|
+
|
|
1321
|
+
# Resize to fit tile while preserving aspect ratio, then crop to fill
|
|
1322
|
+
img_ratio = img.width / img.height
|
|
1323
|
+
tile_ratio = tile_width / tile_height
|
|
1324
|
+
|
|
1325
|
+
if img_ratio > tile_ratio:
|
|
1326
|
+
# Image is wider - fit by height, crop width
|
|
1327
|
+
new_height = tile_height
|
|
1328
|
+
new_width = int(tile_height * img_ratio)
|
|
1329
|
+
else:
|
|
1330
|
+
# Image is taller - fit by width, crop height
|
|
1331
|
+
new_width = tile_width
|
|
1332
|
+
new_height = int(tile_width / img_ratio)
|
|
1333
|
+
|
|
1334
|
+
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
|
1335
|
+
|
|
1336
|
+
# Center crop to tile size
|
|
1337
|
+
left = (new_width - tile_width) // 2
|
|
1338
|
+
top = (new_height - tile_height) // 2
|
|
1339
|
+
img = img.crop((left, top, left + tile_width, top + tile_height))
|
|
1340
|
+
|
|
1341
|
+
# Paste into collage
|
|
1342
|
+
x = col * tile_width
|
|
1343
|
+
y = row * tile_height
|
|
1344
|
+
collage_img.paste(img, (x, y))
|
|
1345
|
+
|
|
1346
|
+
typer.echo(f" [{row},{col}] {filepath.name}")
|
|
1347
|
+
|
|
1348
|
+
except Exception as e:
|
|
1349
|
+
typer.echo(f" [{row},{col}] Failed to load {filepath.name}: {e}", err=True)
|
|
1350
|
+
|
|
1351
|
+
# Save collage
|
|
1352
|
+
output = output.resolve()
|
|
1353
|
+
if not output.suffix.lower() == ".png":
|
|
1354
|
+
output = output.with_suffix(".png")
|
|
1355
|
+
|
|
1356
|
+
collage_img.save(output, "PNG")
|
|
1357
|
+
typer.echo(f"\nSaved collage to: {output}")
|
|
1358
|
+
|
|
1359
|
+
|
|
1228
1360
|
@metadata_app.command("embed")
|
|
1229
1361
|
def metadata_embed(
|
|
1230
1362
|
path: Annotated[Path, typer.Argument(help="Image file or directory")],
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|