All notable changes to GeoSnag will be documented in this file.
Format follows Keep a Changelog. Versioning follows Semantic Versioning.
- CI: install write backends —
lint-and-test.ymlandrelease.ymlnow installlibimage-exiftool-perlandpyexiv2viapip install ".[dev]"soTestApplytests pass on all Python versions.
- Division by zero in matcher when
max_time_delta_minutes: 0. Now returns 100% confidence for exact-timestamp matches instead of crashing. - GPS coordinates removed from debug logs —
writer.pyno longer logs lat/lon at 6-decimal precision, preventing accidental location data exposure in log files. - Stale
create_backupparameter removed from README and INSTALL.md config examples (feature was removed in v0.1.1). - HEIC altitude conversion now logs failures at debug level instead of silently swallowing them.
- ExifTool error messages truncated to 500 characters to prevent unbounded
stderr in
RuntimeError. - Temp index file cleanup failures now logged as warnings instead of silently ignored.
- Match cache threshold log no longer shows
Noneon first run. - Hardcoded
v0.3.0intest_index.pyreplaced with dynamic__version__.
- Config validation —
max_time_delta_minutesrejects negative values;min_confidenceenforces 0–100 range. Invalid config now raisesValueErrorwith a clear message at startup. - Processed marker timestamp switched from local time to UTC (suffix
Z). - CI expanded —
lint-and-test.ymlnow runstest_writer.py,test_special_paths.py, andtest_integration.pyalongside existing tests. register_heif_opener()called once at module level instead of per-file in_scan_heic(), eliminating repeated PIL state mutation in the thread pool.- Directory traversal deduplicated —
collect_photo_paths()inscanner.pyis the single implementation;parallel.py._collect_file_paths()delegates to it. - HEIC test fixtures added —
phone_with_gps.heicandphone_no_gps.heicwith 4 new E2E tests covering HEIC scanning and matching. - Vendored
geosnag/vendor/.gitkeepremoved.
- PIL Image file descriptor leak in HEIC scanner —
Image.open()was not closed on exception. Now uses a context manager (with Image.open(...) as img:). - pyexiv2 Image leak in scanner and writer —
img.close()was skipped whenread_exif()ormodify_exif()raised. Now wrapped intry/finally(3 sites). - GPS coordinate validation added to
write_gps_to_exif()— rejects writes with out-of-range latitude/longitude before calling the backend. - CLI backend validation —
geosnag --applynow exits immediately with a clear install message when no EXIF write backend is available (instead of scanning 2889 files and failing each one individually). Dry-run mode is unaffected. - Outdated
UserCommentreferences instamp_processed()docstring andconfig.example.yamlcomment updated toExif.Image.Software. - Hardcoded version
v0.1.1intest_integration.pyreplaced with dynamic__version__import. matches_report.htmladded to.gitignore.
pyexiv2moved to optional dependency. Core install (pip install geosnag) now requiresexifread,pillow-heif, andPyYAML. Install pyexiv2 viapip install geosnag[all]. This unblocks installation on Synology NAS wherelibexiv2is unavailable.pillow-heifpromoted to core dependency. HEIC/HEIF is the default iPhone format and too common to leave optional. The[heic]extra has been removed.- ExifTool vendoring removed. The 20 MB vendored Perl distribution still required
a system Perl interpreter, providing no benefit over
opkg install perl-image-exiftool. Removedscripts/download_exiftool.py,geosnag/vendor/, related CI bundle step,MANIFEST.invendor include, and[tool.setuptools.package-data]. _find_exiftool()simplified to probe system binaries only:exiftool,/opt/bin/exiftool,/usr/bin/exiftool.- Explicit
encoding="utf-8"added to exiftoolsubprocess.run()calls for robustness with non-ASCII file paths. - Version string deduplicated —
__init__.pynow reads fromimportlib.metadatainstead of duplicating the version frompyproject.toml. - Redundant
Pillowdirect dependency removed (pulled transitively bypillow-heif). - Release workflow split into
buildandpublishjobs;publishusesenvironment: pypifor OIDC trusted publishing with optional approval gate. - README quick start updated with
pip install geosnag[all]as primary and Synology-specific minimal install path. CODEOWNERSand importable GitHub branch ruleset added.
- ExifTool fallback backend (
geosnag/writer.py). Whenpyexiv2/libexiv2is unavailable (e.g. Synology DSM with glibc < 2.32), GPS writes automatically fall back to ExifTool. Probed at:exiftool,/opt/bin/exiftool,/usr/bin/exiftool. - Unit tests for the writer module (
tests/test_writer.py). Covers_probe_cmd,_has_pyexiv2,_find_exiftool,_write_gps_exiftool(all argument combinations),_stamp_exiftool, and the pyexiv2/exiftool/neither routing. - CLI end-to-end tests (
tests/test_e2e.py, 32 tests). Invokespython -m geosnag.clias a subprocess and verifies exit codes, stdout content, and filesystem side effects. - ExifTool integration test in
tests/test_integration.py. Patches_PYEXIV2_OK=Falseand runs a real ExifTool write against the NEF fixture. libimage-exiftool-perladded to CI apt-get dependencies.
- Processed-file stamp field migrated from
Exif.Photo.UserCommenttoExif.Image.Software. The new field has no charset-prefix ambiguity and is semantically more appropriate for a tool tag. All three scanner paths updated. Note: files stamped by v0.1.x will be re-evaluated once, after which they receive the new stamp. test_e2e.pyrenamed totest_integration.py. CLI subprocess tests added as a newtest_e2e.py.
- ExifTool
UserCommentcharset-prefix bug that caused processed files to be rescanned on every run. Eliminated by migrating toExif.Image.Software.
- Removed the automatic backup feature (
.geosnag.bakfiles).pyexiv2/libexiv2prepares the full EXIF structure in memory before touching the file, making the extra copy unnecessary and cluttering the photo directory.
Initial release. Core features:
- EXIF GPS coordinate reading and writing
- Timestamp-based matching algorithm with confidence scoring
- Unified
scan_dirsconfiguration — auto-classifies photos as sources (with GPS) or targets (without GPS) - Support for JPG, ARW, NEF, CR2, CR3, DNG, ORF, RAF, RW2, HEIC, HEIF, PNG
- Write modes: EXIF, XMP sidecar, or both
- Processed-file tagging via
Exif.Photo.UserCommentfor safe re-runs - Scan index cache — skip EXIF reads for unchanged files between runs
- Multithreaded scanning with configurable worker count
- Dry-run mode by default — no files modified unless
--applyis passed - CSV match report export (
--report) - Configurable time window, exclusion patterns
- Designed for Synology NAS (
@eaDir,#recycleexclusions) - Standard pip-installable Python package with CLI entry point