schenesort 2.1.1__py3-none-any.whl → 2.3.0__py3-none-any.whl

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/cli.py CHANGED
@@ -444,6 +444,9 @@ def get(
444
444
  paths_only: Annotated[
445
445
  bool, typer.Option("--paths-only", "-p", help="Output only file paths (for scripting)")
446
446
  ] = False,
447
+ browse: Annotated[
448
+ bool, typer.Option("--browse", "-b", help="Open results in TUI browser")
449
+ ] = False,
447
450
  ) -> None:
448
451
  """Query wallpapers by metadata attributes."""
449
452
  from schenesort.db import WallpaperDB
@@ -472,7 +475,13 @@ def get(
472
475
  typer.echo("No wallpapers found matching criteria.", err=True)
473
476
  raise typer.Exit(1)
474
477
 
475
- if paths_only:
478
+ if browse:
479
+ from schenesort.tui import WallpaperBrowser
480
+
481
+ files = [Path(r["path"]) for r in results]
482
+ app_instance = WallpaperBrowser(files=files)
483
+ app_instance.run()
484
+ elif paths_only:
476
485
  for r in results:
477
486
  typer.echo(r["path"])
478
487
  else:
@@ -542,7 +551,10 @@ def stats() -> None:
542
551
 
543
552
  @app.command()
544
553
  def browse(
545
- path: Annotated[Path, typer.Argument(help="Directory or file to browse")],
554
+ path: Annotated[
555
+ Path | None,
556
+ typer.Argument(help="Directory or file to browse (default: config wallpaper path)"),
557
+ ] = None,
546
558
  recursive: Annotated[
547
559
  bool, typer.Option("--recursive", "-r", help="Browse directories recursively")
548
560
  ] = False,
@@ -550,6 +562,15 @@ def browse(
550
562
  """Browse wallpapers in a terminal UI with image preview and metadata display."""
551
563
  from schenesort.tui import WallpaperBrowser
552
564
 
565
+ if path is None:
566
+ cfg = load_config()
567
+ if cfg.wallpaper_path:
568
+ path = Path(cfg.wallpaper_path).expanduser()
569
+ else:
570
+ typer.echo("Error: No path provided and no wallpaper path configured.", err=True)
571
+ typer.echo("Set paths.wallpaper in config or provide a path argument.", err=True)
572
+ raise typer.Exit(1)
573
+
553
574
  path = path.resolve()
554
575
 
555
576
  if not path.exists():
@@ -572,22 +593,17 @@ def config(
572
593
  config_path = get_config_path()
573
594
 
574
595
  if create:
596
+ already_existed = config_path.exists()
575
597
  path = create_default_config()
576
- if path.exists():
577
- typer.echo(f"Config file: {path}")
578
- if path == config_path:
579
- typer.echo("(already existed)" if not create else "(created)")
580
- else:
598
+ typer.echo(f"Config file: {path}")
599
+ typer.echo("(already existed)" if already_existed else "(created)")
600
+
601
+ if config_path.exists():
602
+ typer.echo(f"Config file: {config_path}\n")
603
+ typer.echo(config_path.read_text())
604
+ elif not create:
581
605
  typer.echo(f"Config file: {config_path}")
582
- if config_path.exists():
583
- typer.echo("\nCurrent settings:")
584
- cfg = load_config()
585
- typer.echo(f" ollama.host: {cfg.ollama_host or '(default: localhost:11434)'}")
586
- typer.echo(f" ollama.model: {cfg.ollama_model}")
587
- if cfg.wallpaper_path:
588
- typer.echo(f" paths.wallpaper: {cfg.wallpaper_path}")
589
- else:
590
- typer.echo("(file does not exist, use --create to create)")
606
+ typer.echo("(file does not exist, use --create to create)")
591
607
 
592
608
 
593
609
  DEFAULT_MODEL = "llava"
@@ -1108,20 +1124,27 @@ def metadata_generate(
1108
1124
  metadata = existing
1109
1125
  metadata.description = str(description)
1110
1126
  metadata.ai_model = effective_model
1111
- if isinstance(result.get("scene"), str):
1112
- metadata.scene = result["scene"]
1113
- if isinstance(result.get("tags"), list):
1114
- metadata.tags = result["tags"]
1115
- if isinstance(result.get("mood"), list):
1116
- metadata.mood = result["mood"]
1117
- if isinstance(result.get("style"), str):
1118
- metadata.style = result["style"]
1119
- if isinstance(result.get("colors"), list):
1120
- metadata.colors = result["colors"]
1121
- if isinstance(result.get("time"), str):
1122
- metadata.time_of_day = result["time"]
1123
- if isinstance(result.get("subject"), str):
1124
- metadata.subject = result["subject"]
1127
+ scene = result.get("scene")
1128
+ if isinstance(scene, str):
1129
+ metadata.scene = scene
1130
+ tags = result.get("tags")
1131
+ if isinstance(tags, list):
1132
+ metadata.tags = tags
1133
+ mood = result.get("mood")
1134
+ if isinstance(mood, list):
1135
+ metadata.mood = mood
1136
+ style = result.get("style")
1137
+ if isinstance(style, str):
1138
+ metadata.style = style
1139
+ colors = result.get("colors")
1140
+ if isinstance(colors, list):
1141
+ metadata.colors = colors
1142
+ time_val = result.get("time")
1143
+ if isinstance(time_val, str):
1144
+ metadata.time_of_day = time_val
1145
+ subject = result.get("subject")
1146
+ if isinstance(subject, str):
1147
+ metadata.subject = subject
1125
1148
 
1126
1149
  # Add image dimensions
1127
1150
  width, height = get_image_dimensions(target_path)
@@ -1202,6 +1225,138 @@ def metadata_update_dimensions(
1202
1225
  typer.echo(f"\n{action} {updated_count} sidecar(s), skipped {skipped_count}.")
1203
1226
 
1204
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
+
1205
1360
  @metadata_app.command("embed")
1206
1361
  def metadata_embed(
1207
1362
  path: Annotated[Path, typer.Argument(help="Image file or directory")],
schenesort/config.py CHANGED
@@ -73,7 +73,15 @@ def load_config() -> Config:
73
73
 
74
74
  return config
75
75
 
76
- except Exception:
76
+ except tomllib.TOMLDecodeError as e:
77
+ import sys
78
+
79
+ print(f"Warning: Failed to parse config file: {e}", file=sys.stderr)
80
+ return Config()
81
+ except Exception as e:
82
+ import sys
83
+
84
+ print(f"Warning: Failed to load config file: {e}", file=sys.stderr)
77
85
  return Config()
78
86
 
79
87
 
schenesort/tui/app.py CHANGED
@@ -73,10 +73,16 @@ class WallpaperBrowser(App):
73
73
  Binding("end", "last_image", "Last", show=False),
74
74
  ]
75
75
 
76
- def __init__(self, path: Path, recursive: bool = False) -> None:
76
+ def __init__(
77
+ self,
78
+ path: Path | None = None,
79
+ recursive: bool = False,
80
+ files: list[Path] | None = None,
81
+ ) -> None:
77
82
  super().__init__()
78
83
  self._base_path = path
79
84
  self._recursive = recursive
85
+ self._files = files # Pre-specified file list (from query results)
80
86
  self._images: list[Path] = []
81
87
  self._current_index: int = 0
82
88
 
@@ -99,7 +105,15 @@ class WallpaperBrowser(App):
99
105
  self._update_status("No images found", "")
100
106
 
101
107
  def _load_images(self) -> None:
102
- """Load list of images from the specified path."""
108
+ """Load list of images from the specified path or file list."""
109
+ # If files were provided directly (e.g., from query results), use them
110
+ if self._files:
111
+ self._images = [f for f in self._files if f.exists()]
112
+ return
113
+
114
+ if self._base_path is None:
115
+ return
116
+
103
117
  if self._base_path.is_file():
104
118
  if self._base_path.suffix.lower() in VALID_IMAGE_EXTENSIONS:
105
119
  self._images = [self._base_path]
@@ -128,12 +142,11 @@ class WallpaperBrowser(App):
128
142
  panel.update_metadata(metadata, current_image.name)
129
143
 
130
144
  # Update status
131
- self._update_status(
132
- str(current_image.relative_to(self._base_path))
133
- if current_image.is_relative_to(self._base_path)
134
- else current_image.name,
135
- f"{self._current_index + 1}/{len(self._images)}",
136
- )
145
+ if self._base_path and current_image.is_relative_to(self._base_path):
146
+ display_path = str(current_image.relative_to(self._base_path))
147
+ else:
148
+ display_path = current_image.name
149
+ self._update_status(display_path, f"{self._current_index + 1}/{len(self._images)}")
137
150
 
138
151
  def _update_status(self, left: str, right: str) -> None:
139
152
  """Update the status bar."""
@@ -20,8 +20,10 @@ class ImagePreview(Container):
20
20
  }
21
21
 
22
22
  ImagePreview Image {
23
- width: 100%;
24
- height: 100%;
23
+ width: auto;
24
+ height: auto;
25
+ max-width: 100%;
26
+ max-height: 100%;
25
27
  }
26
28
 
27
29
  ImagePreview .no-image {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: schenesort
3
- Version: 2.1.1
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,20 +12,43 @@ 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.1.1
15
+ # Schenesort v2.3.0
16
16
 
17
- A CLI tool for managing wallpaper collections with AI-powered metadata, terminal UI browsing, and fast SQLite-based querying.
17
+ A CLI tool for managing wallpaper collections with model generated metadata, terminal UI browsing, and SQLite-based
18
+ querying.
19
+
20
+ schenesort takes a directory of random wallpapers, with random filenames, and uses olama with a decent vision model to
21
+ - look at each wallpaper
22
+ - rename the wallpaper to something sensible
23
+ - drop a XMP sidecar with metadata about the file
24
+
25
+ Once you have a collection of wallpapers re-named with metadata sidecars, run the indexer over it to create a sqlitedb
26
+ that can be queried to retrieve suggestions based on tags, colours, names and the like. Then use `get` to get a
27
+ wallpaper path `feh $(schenesort get -1 -p)` or `hyprctl hyprpaper wallpaper "eDP-1,$(schenesort get -1 -p)"`
28
+
29
+ ![Browse example - Autumn](docs/browse-destruction.png)
18
30
 
19
31
  ## Installation
20
32
 
21
33
  ```bash
34
+ uv tool install schenesort
35
+ ```
36
+
37
+ For development:
38
+
39
+ ```bash
40
+ git clone https://github.com/sthysel/schenesort.git
41
+ cd schenesort
22
42
  uv sync
23
43
  ```
24
44
 
25
45
  ## Quick Start
26
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
+
27
50
  ```bash
28
- # Generate AI metadata for images
51
+ # Generate metadata for images
29
52
  schenesort metadata generate ~/wallpapers -r
30
53
 
31
54
  # Browse with TUI
@@ -54,6 +77,7 @@ schenesort get -1 -p | xargs feh # random wallpaper
54
77
  | `info` | Show collection file statistics |
55
78
  | `describe` | AI-rename images based on content (Ollama) |
56
79
  | `models` | List available Ollama models |
80
+ | `collage` | Create a collage grid from matching wallpapers |
57
81
  | `metadata show` | Display XMP sidecar metadata |
58
82
  | `metadata set` | Manually set metadata fields |
59
83
  | `metadata generate` | Generate metadata with AI (Ollama) |
@@ -67,8 +91,14 @@ Browse your wallpaper collection with image preview and metadata display:
67
91
  ```bash
68
92
  schenesort browse ~/wallpapers
69
93
  schenesort browse ~/wallpapers -r # recursive
94
+ schenesort browse # uses paths.wallpaper from config
95
+ schenesort get --mood peaceful -b # browse query results
70
96
  ```
71
97
 
98
+ ![Browse example - Autumn](docs/browse-autumn.png)
99
+
100
+ ![Browse example - Stallman](docs/browse-stallman.png)
101
+
72
102
  **Keyboard shortcuts:**
73
103
  | Key | Action |
74
104
  |--------------|----------------|
@@ -104,6 +134,10 @@ schenesort get --random -n 10 # 10 random wallpapers
104
134
  schenesort get -1 # single random wallpaper
105
135
  schenesort get -1 --mood dramatic # random with filter
106
136
 
137
+ # Browse results in TUI
138
+ schenesort get --mood peaceful --browse # open matches in browser
139
+ schenesort get --style photography -b # short form
140
+
107
141
  # For scripting (paths only)
108
142
  schenesort get -1 -p # just the path
109
143
  feh $(schenesort get -1 -p) # set random wallpaper
@@ -115,6 +149,36 @@ schenesort stats
115
149
 
116
150
  The database is stored at `$XDG_DATA_HOME/schenesort/index.db` (default: `~/.local/share/schenesort/index.db`).
117
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
+ ![Collage example](docs/collage.png)
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
+
118
182
  ## Metadata Management
119
183
 
120
184
  Store metadata in XMP sidecar files (`.xmp`) alongside images without modifying the original files.
@@ -136,7 +200,7 @@ Store metadata in XMP sidecar files (`.xmp`) alongside images without modifying
136
200
  | `source` | Source URL or info |
137
201
  | `ai_model` | Model used for metadata generation |
138
202
 
139
- ### Generate Metadata with AI
203
+ ### Generate Metadata using ollama
140
204
 
141
205
  ```bash
142
206
  # Preview what would be generated
@@ -304,15 +368,15 @@ Metadata is stored in standard XMP format, compatible with digiKam, darktable, a
304
368
 
305
369
  Images are tagged with recommended screen sizes based on resolution:
306
370
 
307
- | Screen | Resolution |
308
- |--------|------------|
309
- | 8K | 7680x4320 |
310
- | 5K | 5120x2880 |
311
- | 4K | 3840x2160 |
312
- | Ultrawide 4K | 5120x2160 |
313
- | Ultrawide 1440p | 3440x1440 |
314
- | 1440p | 2560x1440 |
315
- | 1080p | 1920x1080 |
316
- | 720p | 1280x720 |
371
+ | Screen | Resolution |
372
+ |-----------------|------------|
373
+ | 8K | 7680x4320 |
374
+ | 5K | 5120x2880 |
375
+ | 4K | 3840x2160 |
376
+ | Ultrawide 4K | 5120x2160 |
377
+ | Ultrawide 1440p | 3440x1440 |
378
+ | 1440p | 2560x1440 |
379
+ | 1080p | 1920x1080 |
380
+ | 720p | 1280x720 |
317
381
 
318
382
  An image is recommended for a screen size if it can cover the screen without upscaling.
@@ -0,0 +1,15 @@
1
+ schenesort/__init__.py,sha256=Z7bmXIWiFUCKj1j4JRlPZvmfzc4RC10Lh1WCKNsJn20,61
2
+ schenesort/cli.py,sha256=LU_s4c7Eg_uozxP5I599TV9Y-WirQxx0h-8LvFNYlqM,51550
3
+ schenesort/config.py,sha256=8EuYv2nma3QKnLPSAgAsaOHZlORzinHjy-Bj_LKPfJA,2739
4
+ schenesort/db.py,sha256=RbfZN6d5O5MkTRPbu51fA7tQLOJf1huIjxFdoT4sESk,11398
5
+ schenesort/xmp.py,sha256=1VS_I4akY8Dv_KLPOdzPgBCFy0280oSCsMmo-_A9cNE,9749
6
+ schenesort/tui/__init__.py,sha256=bqSyRlefPfsYhUxsub4Rltz7yjCQVPXvhzj9n2bn370,141
7
+ schenesort/tui/app.py,sha256=HGKBnszTJXtLu9Mo7eJY4wiO2qkB7dFftBoM4GC_js4,6202
8
+ schenesort/tui/widgets/__init__.py,sha256=3cm7vfXG5-xC_UhIbgEtuMxq5I5tXg6okokJ4ecjGIE,202
9
+ schenesort/tui/widgets/image_preview.py,sha256=j_KTQBJk0vbO18FzTo-GYkRlgerBe1K3VGQwMPbEuUw,2846
10
+ schenesort/tui/widgets/metadata_panel.py,sha256=8DnsvDdKf3jzqHOo7dDmxJxL0Mjx5mX7HNwwcMUcJn0,4860
11
+ schenesort-2.3.0.dist-info/METADATA,sha256=F8AMExvB_0nU7CgGkW2CM1az2XJizdQafy0EBJkwnPw,12592
12
+ schenesort-2.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
13
+ schenesort-2.3.0.dist-info/entry_points.txt,sha256=J5lS-N6KgmzjutFi5bG1jv-4Wszbz3MfcOHcbznBVcw,50
14
+ schenesort-2.3.0.dist-info/licenses/LICENSE,sha256=sMw3SMb9ec9dbM2twEMVeunsGwuljza-9kEXg4kSJpg,1070
15
+ schenesort-2.3.0.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- schenesort/__init__.py,sha256=Z7bmXIWiFUCKj1j4JRlPZvmfzc4RC10Lh1WCKNsJn20,61
2
- schenesort/cli.py,sha256=L7TISfb_ebXI6Do0ncpWsYOj--OZtxfTnVqWjgTg8-I,45659
3
- schenesort/config.py,sha256=n8rf2htxwYuyRqBXnfEZ0pJIwK5DTTQws82MPbDQ4zQ,2476
4
- schenesort/db.py,sha256=RbfZN6d5O5MkTRPbu51fA7tQLOJf1huIjxFdoT4sESk,11398
5
- schenesort/xmp.py,sha256=1VS_I4akY8Dv_KLPOdzPgBCFy0280oSCsMmo-_A9cNE,9749
6
- schenesort/tui/__init__.py,sha256=bqSyRlefPfsYhUxsub4Rltz7yjCQVPXvhzj9n2bn370,141
7
- schenesort/tui/app.py,sha256=nyEUBuffGuIrXJvzNjq0pVFujhJ2Z-1NAjy3bdK_kuU,5736
8
- schenesort/tui/widgets/__init__.py,sha256=3cm7vfXG5-xC_UhIbgEtuMxq5I5tXg6okokJ4ecjGIE,202
9
- schenesort/tui/widgets/image_preview.py,sha256=FhFwaIPzNkwcMlL9aqKV7yvuI3SVe6ATYWgY__9ySo4,2795
10
- schenesort/tui/widgets/metadata_panel.py,sha256=8DnsvDdKf3jzqHOo7dDmxJxL0Mjx5mX7HNwwcMUcJn0,4860
11
- schenesort-2.1.1.dist-info/METADATA,sha256=a2kMj55og_OmHo_zJ1_OOzwQr_5PecBY2oIqrOcf51I,9928
12
- schenesort-2.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
13
- schenesort-2.1.1.dist-info/entry_points.txt,sha256=J5lS-N6KgmzjutFi5bG1jv-4Wszbz3MfcOHcbznBVcw,50
14
- schenesort-2.1.1.dist-info/licenses/LICENSE,sha256=sMw3SMb9ec9dbM2twEMVeunsGwuljza-9kEXg4kSJpg,1070
15
- schenesort-2.1.1.dist-info/RECORD,,