pyimgtag 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.
- pyimgtag-0.1.0/LICENSE +21 -0
- pyimgtag-0.1.0/PKG-INFO +377 -0
- pyimgtag-0.1.0/README.md +327 -0
- pyimgtag-0.1.0/pyproject.toml +68 -0
- pyimgtag-0.1.0/setup.cfg +4 -0
- pyimgtag-0.1.0/src/pyimgtag/__init__.py +5 -0
- pyimgtag-0.1.0/src/pyimgtag/__main__.py +8 -0
- pyimgtag-0.1.0/src/pyimgtag/applescript_writer.py +187 -0
- pyimgtag-0.1.0/src/pyimgtag/cache.py +40 -0
- pyimgtag-0.1.0/src/pyimgtag/commands/__init__.py +1 -0
- pyimgtag-0.1.0/src/pyimgtag/commands/db.py +74 -0
- pyimgtag-0.1.0/src/pyimgtag/commands/faces.py +209 -0
- pyimgtag-0.1.0/src/pyimgtag/commands/preflight_cmd.py +31 -0
- pyimgtag-0.1.0/src/pyimgtag/commands/query.py +70 -0
- pyimgtag-0.1.0/src/pyimgtag/commands/review_cmd.py +27 -0
- pyimgtag-0.1.0/src/pyimgtag/commands/run.py +383 -0
- pyimgtag-0.1.0/src/pyimgtag/commands/tags.py +100 -0
- pyimgtag-0.1.0/src/pyimgtag/dedup.py +97 -0
- pyimgtag-0.1.0/src/pyimgtag/exif_reader.py +223 -0
- pyimgtag-0.1.0/src/pyimgtag/exif_writer.py +342 -0
- pyimgtag-0.1.0/src/pyimgtag/face_clustering.py +73 -0
- pyimgtag-0.1.0/src/pyimgtag/face_detection.py +99 -0
- pyimgtag-0.1.0/src/pyimgtag/face_embedding.py +100 -0
- pyimgtag-0.1.0/src/pyimgtag/filters.py +54 -0
- pyimgtag-0.1.0/src/pyimgtag/geocoder.py +85 -0
- pyimgtag-0.1.0/src/pyimgtag/heic_converter.py +80 -0
- pyimgtag-0.1.0/src/pyimgtag/main.py +315 -0
- pyimgtag-0.1.0/src/pyimgtag/models.py +163 -0
- pyimgtag-0.1.0/src/pyimgtag/ollama_client.py +295 -0
- pyimgtag-0.1.0/src/pyimgtag/output_writer.py +62 -0
- pyimgtag-0.1.0/src/pyimgtag/preflight.py +166 -0
- pyimgtag-0.1.0/src/pyimgtag/progress_db.py +695 -0
- pyimgtag-0.1.0/src/pyimgtag/raw_converter.py +175 -0
- pyimgtag-0.1.0/src/pyimgtag/review_server.py +456 -0
- pyimgtag-0.1.0/src/pyimgtag/scanner.py +52 -0
- pyimgtag-0.1.0/src/pyimgtag.egg-info/PKG-INFO +377 -0
- pyimgtag-0.1.0/src/pyimgtag.egg-info/SOURCES.txt +58 -0
- pyimgtag-0.1.0/src/pyimgtag.egg-info/dependency_links.txt +1 -0
- pyimgtag-0.1.0/src/pyimgtag.egg-info/entry_points.txt +2 -0
- pyimgtag-0.1.0/src/pyimgtag.egg-info/requires.txt +40 -0
- pyimgtag-0.1.0/src/pyimgtag.egg-info/top_level.txt +1 -0
- pyimgtag-0.1.0/tests/test_applescript_writer.py +478 -0
- pyimgtag-0.1.0/tests/test_cache.py +30 -0
- pyimgtag-0.1.0/tests/test_dedup.py +102 -0
- pyimgtag-0.1.0/tests/test_exif_reader.py +115 -0
- pyimgtag-0.1.0/tests/test_exif_writer.py +492 -0
- pyimgtag-0.1.0/tests/test_face_clustering.py +174 -0
- pyimgtag-0.1.0/tests/test_face_detection.py +179 -0
- pyimgtag-0.1.0/tests/test_face_embedding.py +207 -0
- pyimgtag-0.1.0/tests/test_filters.py +90 -0
- pyimgtag-0.1.0/tests/test_heic_converter.py +153 -0
- pyimgtag-0.1.0/tests/test_main.py +892 -0
- pyimgtag-0.1.0/tests/test_models.py +131 -0
- pyimgtag-0.1.0/tests/test_ollama_client.py +246 -0
- pyimgtag-0.1.0/tests/test_output_writer.py +57 -0
- pyimgtag-0.1.0/tests/test_preflight.py +185 -0
- pyimgtag-0.1.0/tests/test_progress_db.py +1244 -0
- pyimgtag-0.1.0/tests/test_raw_converter.py +270 -0
- pyimgtag-0.1.0/tests/test_review_server.py +173 -0
- pyimgtag-0.1.0/tests/test_scanner.py +81 -0
pyimgtag-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 pyimgtag contributors
|
|
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.
|
pyimgtag-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyimgtag
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Tag macOS Photos library images using local Gemma model for searchable tags
|
|
5
|
+
Author: pyimgtag contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
13
|
+
Classifier: Operating System :: MacOS
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: requests>=2.28
|
|
19
|
+
Requires-Dist: Pillow>=10.0
|
|
20
|
+
Requires-Dist: imagehash>=4.3.1
|
|
21
|
+
Requires-Dist: exifread>=3.0
|
|
22
|
+
Provides-Extra: heic
|
|
23
|
+
Requires-Dist: pillow-heif>=0.10.0; extra == "heic"
|
|
24
|
+
Provides-Extra: photos
|
|
25
|
+
Requires-Dist: photoscript>=0.5.3; extra == "photos"
|
|
26
|
+
Provides-Extra: face
|
|
27
|
+
Requires-Dist: face-recognition>=1.3; extra == "face"
|
|
28
|
+
Requires-Dist: scikit-learn>=1.3; extra == "face"
|
|
29
|
+
Provides-Extra: review
|
|
30
|
+
Requires-Dist: fastapi>=0.100; extra == "review"
|
|
31
|
+
Requires-Dist: uvicorn>=0.20; extra == "review"
|
|
32
|
+
Requires-Dist: pydantic>=2.0; extra == "review"
|
|
33
|
+
Provides-Extra: raw
|
|
34
|
+
Requires-Dist: rawpy>=0.18; extra == "raw"
|
|
35
|
+
Provides-Extra: all
|
|
36
|
+
Requires-Dist: pillow-heif>=0.10.0; extra == "all"
|
|
37
|
+
Requires-Dist: photoscript>=0.5.3; extra == "all"
|
|
38
|
+
Requires-Dist: rawpy>=0.18; extra == "all"
|
|
39
|
+
Provides-Extra: dev
|
|
40
|
+
Requires-Dist: pytest>=9.0; extra == "dev"
|
|
41
|
+
Requires-Dist: pytest-xdist>=3.0; extra == "dev"
|
|
42
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
43
|
+
Provides-Extra: lint
|
|
44
|
+
Requires-Dist: mypy>=1.0; extra == "lint"
|
|
45
|
+
Requires-Dist: ruff>=0.1.0; extra == "lint"
|
|
46
|
+
Provides-Extra: security
|
|
47
|
+
Requires-Dist: bandit>=1.7; extra == "security"
|
|
48
|
+
Requires-Dist: pip-audit>=2.6; extra == "security"
|
|
49
|
+
Dynamic: license-file
|
|
50
|
+
|
|
51
|
+
# pyimgtag
|
|
52
|
+
|
|
53
|
+
[](https://github.com/kurok/pyimgtag/actions/workflows/python-package.yml)
|
|
54
|
+
[](https://www.python.org/downloads/)
|
|
55
|
+
[](https://opensource.org/licenses/MIT)
|
|
56
|
+
|
|
57
|
+
Tag images using a local Gemma model for searchable tags, with optional Apple Photos integration on macOS.
|
|
58
|
+
|
|
59
|
+
## Overview
|
|
60
|
+
|
|
61
|
+
pyimgtag uses a locally-running Gemma model (via [Ollama](https://ollama.ai)) to
|
|
62
|
+
analyse images and generate 1-5 descriptive tags per photo. It reads EXIF GPS
|
|
63
|
+
coordinates and resolves them to the nearest city/place using OpenStreetMap
|
|
64
|
+
Nominatim. Everything runs on-device -- no cloud, no data leaves your computer.
|
|
65
|
+
|
|
66
|
+
Works on **macOS, Linux, and Windows**. Apple Photos integration (write-back) is macOS-only.
|
|
67
|
+
|
|
68
|
+
**Key features:**
|
|
69
|
+
|
|
70
|
+
- One local model call per image, compact prompt, low token usage
|
|
71
|
+
- Rich AI metadata: scene category, emotional tone, cleanup classification, text detection, event hints
|
|
72
|
+
- EXIF GPS as source of truth for location (never guessed from image content)
|
|
73
|
+
- Open reverse geocoding via Nominatim with local disk cache
|
|
74
|
+
- Supports exported folders and Apple Photos library originals (macOS only)
|
|
75
|
+
- Apple Photos write-back: push AI tags and descriptions back as keywords/captions (macOS only)
|
|
76
|
+
- Subcommands: `run`, `status`, `reprocess`, `cleanup`, `preflight`
|
|
77
|
+
- Dry-run mode, date/limit filters, JSON/CSV export
|
|
78
|
+
- SQLite progress DB with schema versioning for incremental re-runs
|
|
79
|
+
|
|
80
|
+
## Requirements
|
|
81
|
+
|
|
82
|
+
- Python 3.11+
|
|
83
|
+
- [Ollama](https://ollama.ai) installed and running
|
|
84
|
+
- Gemma 4 model pulled: `ollama pull gemma4:e4b`
|
|
85
|
+
|
|
86
|
+
**macOS-specific:**
|
|
87
|
+
- Apple Silicon or Intel Mac
|
|
88
|
+
- Optional: `exiftool` for reliable HEIC EXIF (falls back to Pillow)
|
|
89
|
+
- Optional: `pillow-heif` for HEIC image loading
|
|
90
|
+
|
|
91
|
+
**All platforms:**
|
|
92
|
+
- Works on macOS, Linux, and Windows
|
|
93
|
+
- EXIF writing via `exiftool` (if installed) works across platforms
|
|
94
|
+
- Apple Photos write-back requires macOS
|
|
95
|
+
|
|
96
|
+
## Quick Start
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pip install -e ".[dev]"
|
|
100
|
+
|
|
101
|
+
# Pull the model
|
|
102
|
+
ollama pull gemma4:e4b
|
|
103
|
+
|
|
104
|
+
# Dry-run on an exported folder, first 20 images
|
|
105
|
+
pyimgtag run --input-dir ~/Pictures/exported --limit 20 --dry-run
|
|
106
|
+
|
|
107
|
+
# Single date
|
|
108
|
+
pyimgtag run --input-dir ~/Pictures/exported --date 2026-04-01 --dry-run
|
|
109
|
+
|
|
110
|
+
# Date range with JSON output
|
|
111
|
+
pyimgtag run --input-dir ~/Pictures/exported \
|
|
112
|
+
--date-from 2026-03-01 --date-to 2026-03-31 \
|
|
113
|
+
--output-json results.json
|
|
114
|
+
|
|
115
|
+
# Photos library
|
|
116
|
+
pyimgtag run --photos-library ~/Pictures/Photos\ Library.photoslibrary \
|
|
117
|
+
--limit 50 --dry-run
|
|
118
|
+
|
|
119
|
+
# Check processing progress
|
|
120
|
+
pyimgtag status
|
|
121
|
+
|
|
122
|
+
# Re-tag all photos (e.g. after prompt improvements)
|
|
123
|
+
pyimgtag reprocess
|
|
124
|
+
|
|
125
|
+
# List photos flagged for deletion
|
|
126
|
+
pyimgtag cleanup
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Installation
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# From source
|
|
133
|
+
git clone https://github.com/kurok/pyimgtag.git
|
|
134
|
+
cd pyimgtag
|
|
135
|
+
pip install -e ".[dev]"
|
|
136
|
+
|
|
137
|
+
# Optional HEIC support
|
|
138
|
+
pip install pillow-heif
|
|
139
|
+
|
|
140
|
+
# Optional exiftool (better EXIF for HEIC)
|
|
141
|
+
brew install exiftool
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Platform Support
|
|
145
|
+
|
|
146
|
+
| Feature | macOS | Linux | Windows |
|
|
147
|
+
|---------|-------|-------|---------|
|
|
148
|
+
| Image tagging via Ollama | ✅ | ✅ | ✅ |
|
|
149
|
+
| EXIF reading (GPS, dates) | ✅ | ✅ | ✅ |
|
|
150
|
+
| Reverse geocoding (Nominatim) | ✅ | ✅ | ✅ |
|
|
151
|
+
| EXIF writing via `exiftool` | ✅ | ✅ | ✅ |
|
|
152
|
+
| Apple Photos library scanning | ✅ | ❌ | ❌ |
|
|
153
|
+
| Apple Photos write-back | ✅ | ❌ | ❌ |
|
|
154
|
+
|
|
155
|
+
**Note:** Most features work cross-platform. Apple Photos integration is macOS-only since it requires macOS-specific AppleScript functionality.
|
|
156
|
+
|
|
157
|
+
### Cross-Platform Examples
|
|
158
|
+
|
|
159
|
+
**Linux/Windows (export folders only):**
|
|
160
|
+
```bash
|
|
161
|
+
# Tag exported images with EXIF writing
|
|
162
|
+
pyimgtag run --input-dir /mnt/photos \
|
|
163
|
+
--output-json results.json \
|
|
164
|
+
--write-exif # If exiftool is installed
|
|
165
|
+
|
|
166
|
+
# Tags and descriptions stored in results.json and EXIF
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**macOS (both export folders and Photos library):**
|
|
170
|
+
```bash
|
|
171
|
+
# Tag Photos library with direct write-back to Photos app
|
|
172
|
+
pyimgtag run --photos-library ~/Pictures/Photos\ Library.photoslibrary \
|
|
173
|
+
--write-back # Push tags/descriptions to Apple Photos
|
|
174
|
+
|
|
175
|
+
# Or export folder with both EXIF and JSON output
|
|
176
|
+
pyimgtag run --input-dir ~/Downloads/exported \
|
|
177
|
+
--write-exif --output-json results.json
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The tool gracefully handles missing features—if you use `--write-back` on Linux/Windows, it will warn you and proceed without it.
|
|
181
|
+
|
|
182
|
+
## Usage
|
|
183
|
+
|
|
184
|
+
### Subcommands
|
|
185
|
+
|
|
186
|
+
pyimgtag uses subcommands. Run `pyimgtag --help` for the full list.
|
|
187
|
+
|
|
188
|
+
#### `pyimgtag run` — tag images
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# Exported image folder
|
|
192
|
+
pyimgtag run --input-dir /path/to/photos
|
|
193
|
+
|
|
194
|
+
# Apple Photos library
|
|
195
|
+
pyimgtag run --photos-library ~/Pictures/Photos\ Library.photoslibrary
|
|
196
|
+
|
|
197
|
+
# With filters
|
|
198
|
+
pyimgtag run --input-dir /path/to/photos \
|
|
199
|
+
--limit 100 --date-from 2026-03-01 --date-to 2026-03-31
|
|
200
|
+
|
|
201
|
+
# Write tags back to Apple Photos as keywords
|
|
202
|
+
pyimgtag run --photos-library ~/Pictures/Photos\ Library.photoslibrary \
|
|
203
|
+
--write-back --limit 10
|
|
204
|
+
|
|
205
|
+
# Deduplicate by perceptual hash
|
|
206
|
+
pyimgtag run --input-dir /path/to/photos --dedup
|
|
207
|
+
|
|
208
|
+
# Export to JSON
|
|
209
|
+
pyimgtag run --input-dir /path/to/photos --output-json results.json
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Run flags:**
|
|
213
|
+
|
|
214
|
+
| Flag | Description |
|
|
215
|
+
|---|---|
|
|
216
|
+
| `--input-dir PATH` | Exported image folder |
|
|
217
|
+
| `--photos-library PATH` | Apple Photos library package *(macOS only)* |
|
|
218
|
+
| `--limit N` | Max images to process |
|
|
219
|
+
| `--date YYYY-MM-DD` | Single date filter |
|
|
220
|
+
| `--date-from` / `--date-to` | Date range filter |
|
|
221
|
+
| `--extensions jpg,png` | File types (default: jpg,jpeg,heic,png) |
|
|
222
|
+
| `--skip-no-gps` | Skip images without GPS data |
|
|
223
|
+
| `--dry-run` | Verbose output, no DB writes |
|
|
224
|
+
| `--verbose` / `-v` | Detailed per-file output |
|
|
225
|
+
| `--output-json FILE` | Write results to JSON |
|
|
226
|
+
| `--output-csv FILE` | Write results to CSV |
|
|
227
|
+
| `--jsonl-stdout` | JSONL output to stdout |
|
|
228
|
+
| `--write-back` | Write tags/description back to Apple Photos *(macOS only)* |
|
|
229
|
+
| `--write-exif` | Write description and keywords to image EXIF |
|
|
230
|
+
| `--dedup` | Skip duplicates via perceptual hash |
|
|
231
|
+
| `--dedup-threshold N` | Hamming distance threshold (default: 5) |
|
|
232
|
+
| `--model NAME` | Ollama model (default: gemma4:e4b) |
|
|
233
|
+
| `--ollama-url URL` | Ollama API URL |
|
|
234
|
+
| `--max-dim N` | Max image dimension (default: 1280) |
|
|
235
|
+
| `--timeout N` | Model request timeout in seconds |
|
|
236
|
+
| `--db PATH` | Progress database path |
|
|
237
|
+
| `--no-cache` | Skip progress DB, reprocess all |
|
|
238
|
+
|
|
239
|
+
#### `pyimgtag status` — check progress
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
# Show processing stats
|
|
243
|
+
pyimgtag status
|
|
244
|
+
|
|
245
|
+
# Output:
|
|
246
|
+
# Progress: 142 / 200 (71%)
|
|
247
|
+
# ok: 140
|
|
248
|
+
# error: 2
|
|
249
|
+
# pending: 58
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
#### `pyimgtag reprocess` — reset for re-tagging
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
# Reset everything (e.g. after prompt improvements)
|
|
256
|
+
pyimgtag reprocess
|
|
257
|
+
|
|
258
|
+
# Reset only failed entries
|
|
259
|
+
pyimgtag reprocess --status error
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
#### `pyimgtag cleanup` — find photos to delete
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
# List photos the AI flagged as "delete"
|
|
266
|
+
pyimgtag cleanup
|
|
267
|
+
|
|
268
|
+
# Also include "review" (uncertain) candidates
|
|
269
|
+
pyimgtag cleanup --include-review
|
|
270
|
+
|
|
271
|
+
# Output:
|
|
272
|
+
# Cleanup candidates (delete): 12
|
|
273
|
+
#
|
|
274
|
+
# [delete] /path/to/blurry_photo.jpg | 2026-03-15 | tags: blurry, dark
|
|
275
|
+
# [delete] /path/to/screenshot.png | 2026-04-01 | tags: screenshot, text
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
#### `pyimgtag preflight` — check prerequisites
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
# Verify Ollama, model, and source path
|
|
282
|
+
pyimgtag preflight --input-dir ~/Pictures/exported
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Sample verbose output
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
[1/50] sunset_beach.jpg
|
|
289
|
+
Path: /Users/me/Pictures/exported/sunset_beach.jpg
|
|
290
|
+
Date: 2026-04-01 14:30:00
|
|
291
|
+
Tags: sunset, beach, ocean, waves, sand
|
|
292
|
+
Summary: golden hour sunset over the Pacific
|
|
293
|
+
Scene: outdoor_leisure
|
|
294
|
+
Tone: positive
|
|
295
|
+
Cleanup: keep
|
|
296
|
+
Event: outing
|
|
297
|
+
Signif.: high
|
|
298
|
+
GPS: 37.7749, -122.4194
|
|
299
|
+
Location: San Francisco, California, United States
|
|
300
|
+
Status: ok
|
|
301
|
+
|
|
302
|
+
--- Summary ---
|
|
303
|
+
Scanned: 200
|
|
304
|
+
Processed: 50
|
|
305
|
+
Skipped (date): 0
|
|
306
|
+
Skipped (no GPS): 0
|
|
307
|
+
Skipped (no file):0
|
|
308
|
+
Model failures: 2
|
|
309
|
+
Geocode failures: 0
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Output schema
|
|
313
|
+
|
|
314
|
+
Each result (JSON/CSV) includes:
|
|
315
|
+
|
|
316
|
+
| Field | Description |
|
|
317
|
+
|---|---|
|
|
318
|
+
| `file_path` | Full path to image |
|
|
319
|
+
| `file_name` | Filename |
|
|
320
|
+
| `source_type` | `directory` or `photos_library` |
|
|
321
|
+
| `image_date` | EXIF or file date |
|
|
322
|
+
| `tags` | 1-5 vision model tags |
|
|
323
|
+
| `scene_summary` | Short scene description |
|
|
324
|
+
| `scene_category` | `indoor_home`, `indoor_work`, `outdoor_leisure`, `outdoor_travel`, `transport`, `other` |
|
|
325
|
+
| `emotional_tone` | `positive`, `neutral`, `negative`, `mixed` |
|
|
326
|
+
| `cleanup_class` | `keep`, `review`, `delete` |
|
|
327
|
+
| `has_text` | Whether image contains readable text |
|
|
328
|
+
| `text_summary` | Extracted text summary (if `has_text`) |
|
|
329
|
+
| `event_hint` | `outing`, `gathering`, `work`, `travel`, `daily`, `other` |
|
|
330
|
+
| `significance` | `high`, `medium`, `low` |
|
|
331
|
+
| `gps_lat` / `gps_lon` | EXIF GPS coordinates |
|
|
332
|
+
| `nearest_place` | Village/town/suburb |
|
|
333
|
+
| `nearest_city` | City |
|
|
334
|
+
| `nearest_region` | State/region |
|
|
335
|
+
| `nearest_country` | Country |
|
|
336
|
+
| `processing_status` | `ok` or `error` |
|
|
337
|
+
| `error_message` | Error details if any |
|
|
338
|
+
| `phash` | Perceptual hash (when `--dedup` used) |
|
|
339
|
+
|
|
340
|
+
## Architecture
|
|
341
|
+
|
|
342
|
+
```
|
|
343
|
+
src/pyimgtag/
|
|
344
|
+
main.py CLI entry point, subcommand dispatch
|
|
345
|
+
models.py Data classes (ExifData, TagResult, GeoResult, ImageResult)
|
|
346
|
+
scanner.py Directory and Photos library scanning
|
|
347
|
+
exif_reader.py EXIF GPS + date extraction (exiftool + Pillow)
|
|
348
|
+
ollama_client.py Ollama vision API client (rich structured response)
|
|
349
|
+
geocoder.py Nominatim reverse geocoder with disk cache
|
|
350
|
+
filters.py Date/GPS filter logic
|
|
351
|
+
output_writer.py JSON/CSV/JSONL output
|
|
352
|
+
progress_db.py SQLite progress DB with versioned migrations
|
|
353
|
+
applescript_writer.py Apple Photos keyword/description write-back
|
|
354
|
+
dedup.py Perceptual hash duplicate detection
|
|
355
|
+
heic_converter.py HEIC to JPEG conversion (macOS sips)
|
|
356
|
+
cache.py Simple JSON disk cache
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## Development
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
pip install -e ".[dev,lint,security]"
|
|
363
|
+
|
|
364
|
+
pytest tests/ -v
|
|
365
|
+
ruff format src/ tests/ && ruff check src/ tests/ --fix
|
|
366
|
+
python -m mypy src/pyimgtag/ --ignore-missing-imports --disable-error-code import-untyped
|
|
367
|
+
python -m bandit -r src/pyimgtag/ -c pyproject.toml
|
|
368
|
+
pre-commit install && pre-commit run --all-files
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## Contributing
|
|
372
|
+
|
|
373
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
374
|
+
|
|
375
|
+
## License
|
|
376
|
+
|
|
377
|
+
MIT -- see [LICENSE](LICENSE).
|