photo-cli is CLI tool (works on Linux & macOS & Windows) that extracts when and where (reverse geocode) your photographs are taken, archive or copy into a new organized folder (not modifying source folder) with various folder & file naming strategies with an album support to categorize, list & view easily. All photo metadata is stored on local SQLite for archive operation and CSV for others. On CSV file (can view on Microsoft Excel, Libre/OpenOffice Calc, Apple Numbers, Google Sheets), you can navigate your photo locations on Google Maps & Earth with your custom label and pin style.
- Features Explained With An Example
- Installation
- Sample Usage Screenshots
- How It's Done?
- Supported Photo Types
- Processing Companion Files
- Address Building & Reverse Geocoding
- Usages
- Commands
- Command Line Options/Arguments
- Settings
- Exit Codes
- Contributing
- Code of Conduct
- Changelog - Release History
- Attribution
- License
- Uninstallation
- Credits
There is five main feature that can be explained better with examples.
- Archive & index with albums into a specific folder with metadata stored locally on SQLite with
photo-cli archivecommand - Copy into a new organized folder example with
photo-cli copycommand - List/Open Photos by their metadata on Archived Folder
- Export all extracted information into a CSV Report With
photo-cli infoCommand - Navigate Your Photo Locations on Google Maps & Earth
1. Archive & index with albums into a specific folder with metadata stored locally on SQLite with photo-cli archive command
| Original Folder Hierarchy | After photo-cli |
|---|---|
├── DSC_5727.jpg
├── GOPR6742.jpg
├── Italy album
│ ├── DJI_01732.jpg
│ ├── DJI_01733.jpg
│ ├── DSC00001.JPG
│ ├── DSC03467.jpg
│ ├── DSC_1769.JPG
│ ├── DSC_1770.JPG
│ ├── DSC_1770_(same).jpg
│ ├── DSC_1771.JPG
│ ├── GOPR7496.jpg
│ ├── GOPR7497.jpg
│ ├── IMG_0747.JPG
│ ├── IMG_1979.HEIC
│ ├── IMG_1979.mov
│ ├── IMG_1979.xmp
│ ├── IMG_2371.jpg
│ └── IMG_O1979.aae
└── Spain Journey
├── DSC_1807.jpg
├── DSC_1808.jpg
└── IMG_5397.jpg
|
├── 2005 │ ├── 08 │ │ └── 13 │ │ └── 2005.08.13_09.47.23-5842c73cfdc5f347551bb6016e00c71bb1393169.jpg │ └── 12 │ └── 14 │ └── 2005.12.14_14.39.47-03cb14d5c68beed97cbe73164de9771d537fcd96.jpg ├── 2008 │ ├── 07 │ │ └── 16 │ │ └── 2008.07.16_11.33.20-90d835861e1aa3c829e3ab28a7f01ec3a090f664.jpg │ └── 10 │ └── 22 │ ├── 2008.10.22_16.28.39-5d66eec547469a1817bda4abe35c801359b2bb55.jpg │ ├── 2008.10.22_16.29.49-629b0b141634d6c0906e49af448bec8d755ba32c.jpg │ ├── 2008.10.22_16.38.20-620d23336a12ab54f9f0190fe93960a4dba2df59.jpg │ ├── 2008.10.22_16.43.21-3b0a3215b4f66d7ff4804dd223f192c21aee71bc.jpg │ ├── 2008.10.22_16.44.01-d470205a1d331a9d3765b3762b7c954bb8efc6ea.jpg │ ├── 2008.10.22_16.46.53-f670f2bb6c54898894b06b083185b05086bd4e6e.jpg │ ├── 2008.10.22_16.52.15-6b89a245809031ecc47789cdeaa332545330fc39.jpg │ ├── 2008.10.22_16.55.37-dd42edcde2433a7df4a3d67bf61944a20884da89.jpg │ └── 2008.10.22_17.00.07-a0ab699f5f99fce8ff49163e87c7590c2c9a66eb.jpg ├── 2012 │ └── 06 │ └── 22 │ └── 2012.06.22_19.52.31-bb649a18b3e7bb3df3701587a13f833749091817.jpg ├── 2015 │ └── 04 │ └── 10 │ ├── 2015.04.10_20.12.23-3907fc960f2873f40c8f35643dd444e0468be131.jpg │ └── 2015.04.10_20.12.23-9f4e6d352ec172e1059571250655e376769080fe.jpg ├── 2025 │ └── 06 │ └── 03 │ ├── 2025.06.03_13.53.36-8a45af72730474e22582afbe72f53685d705a72c.heic │ └── 2025.06.03_13.53.36-8a45af72730474e22582afbe72f53685d705a72c.mov ├── no-photo-taken-date │ └── cf756397cc3ca81b2650c8801fd64e172504015a.jpg └── photo-cli.sqlite3 |
This archive process is done by running only the following single command;
photo-cli archive --input [relative|full folder path] --output [relative|full folder path] --album-type DateRange --album-name My-Album --auto-reverse-geocode-album --expected-day-range 7300 --delete-on-source --reverse-geocode OpenStreetMapFoundation --openstreetmap-properties country city
Same command with shorter alias of all argument names & values
photo-cli archive -i [relative|full folder path] -o [relative|full folder path] -y 2 -a My-Album -s -w 7300 -f -e 2 -r country city
Console/terminal output (as progress may take time, for each operation completion status shown with progress)
Click to expand
[17:07:27] Searching photo main files: started
[17:07:27] Searching photo main files: finished. 18 photo(s) found.
[17:07:27] Searching photo companion files: started
[17:07:27] Searching photo companion files: finished. 1 companion file(s) found.
[17:07:27] No coordinate found on `Gps` directory. Path:</test-photographs/Spain Journey/IMG_5397.jpg>
[17:07:27] No coordinate found on `Gps` directory. Path:</test-photographs/Italy album/IMG_2371.jpg>
[17:07:28] Calculating file hashes: started
[17:07:28] Calculating file hashes: finished.
[17:07:28] This OpenStreetMapFoundation provider is using rate limit of 1 second(s) between each request
[17:07:28] Reverse Geocoding: started
[17:07:31] Requested address types: City on index #2, not found on OpenStreetMap's response. Available types found:
{"country_code":"gb","country":"United Kingdom","postcode":"SL4 2DR","suburb":"Sunninghill and Ascot","road":"Windsor Road"}
. Path:</test-photographs/GOPR6742.jpg>
[17:07:47] Reverse Geocoding: finished.
[17:07:47] Directory grouping: started
[17:07:47] Directory grouping: finished.
[17:07:47] Processing target folder: started
[17:07:47] Photo is skipped due to same photo has already been archived. Same photo paths: <test-photographs/Italy album/DSC_1770.JPG>, <
/test-photographs/Italy album/DSC_1770_(same).jpg>
[17:07:47] Processing target folder: finished.
[17:07:47] Verified all photo files copied successfully by comparing file hashes from original photo files.
[17:07:47] Archiving photos to SQLite: started
[17:07:47] Archiving photos to SQLite: finished.
[17:07:47] Saving new date range album: started
[17:07:47] Saving new date range album: finished.
[17:07:47] Saving reverse geocode albums: started
[17:07:47] Saving reverse geocode albums: finished.
[17:07:47] Deleting source files: started
[17:07:47] Deleting source files: finished.
[17:07:47] Deleting empty directories: started
[17:07:47] Deleting empty directories: finished.
Statistics
┌────────────────────────────────────────────────┬───────┐
│ Statistic │ Count │
├────────────────────────────────────────────────┼───────┤
│ File System Error(s) │ 0 │
│ Photo(s) found │ 18 │
│ Photo(s) copied │ 17 │
│ Photo(s) existed on the output │ 0 │
│ Photo(s) are skipped, they have the same photo │ 1 │
│ Directory/directories created │ 8 │
│ │ │
│ Companion file(s) found │ 1 │
│ Companion file(s) copied │ 1 │
│ Companion file(s) existed on the output │ 0 │
│ │ │
│ Source photo file(s) deleted │ 17 │
│ Source companion file(s) deleted │ 1 │
│ Source empty directory(ies) deleted │ 1 │
│ │ │
│ User defined album created │ 1 │
│ User defined album updated │ 0 │
│ Auto address album created │ 15 │
│ │ │
│ Reverse geocode request sent │ 14 │
│ Reverse geocode evaluated from memory │ 2 │
│ Reverse geocode evaluated from database │ 0 │
│ Photo(s) has taken date and coordinate │ 16 │
│ Photo(s) has taken date but no coordinate │ 1 │
│ Photo(s) has coordinate but no taken date │ 0 │
│ Photo(s) has no taken date and coordinate │ 1 │
│ │ │
│ Photo(s) has unknown/invalid format │ 0 │
│ Photo(s) caused unexpected error internally │ 0 │
└────────────────────────────────────────────────┴───────┘
[17:07:47] Archive process completed successfully
- Gather all photo paths in the source folder within subfolders.
- Gather all photo companion files (if there is any) is which used but not limited for storing metadata, edits, RAW format files stored with same file name. For example Live Photos on iPhone storing short video clip of photo with a
movextension. - Extract EXIF data of each photograph's taken date and coordinate. As third-party reverse geocode is selected, we are building address with
OpenStreetMapby using given administrative levels ascity townfor each photograph. - As
expected-day-rangeargument is given with, this is a validation between the photo taken date date range in days. If there is any photograph that has taken date outside the given day range, process won't start. This is optional argument to stop processing archive photos that are not within the expected given day range. - Photos which doesn't have coordinate information or reverse geocode propery missing, would be listed on the output by their paths as warning.
- As
verifyargument is given, we are calculating file hashes of each photograph's file to verify that all photo files copied successfully by comparing file hashes from original photo files at the end of the process. - On the output folder, photos will be placed on folder hierarchy by it's photo taken date's
/[year]/[month]/[day]. For example:/2008/07/16/. - Photo file names will be formatted as
yyyy.MM.dd_HH.mm.ss-{sha1-hash-of-file}.{extension}. For example:2008.07.16_11.33.20-90d835861e1aa3c829e3ab28a7f01ec3a090f664.jpg. Input file name is:IMG_2371.jpg. Companion files are copied with the same name with their original file extension. - Input folder has duplicate photos with different names
DSC_1770.JPG,DSC_1770_(same).JPG. We are only archiving one of them by comparing file hashes to output2008/10/22/2008.10.22_17.00.07-a0ab699f5f99fce8ff49163e87c7590c2c9a66eb.jpgand logs warning to output with their paths. - The photo that don't have any photo taken date
Spain Journey/IMG_5397.jpg, copied intono-photo-taken-datefolder with only a sha1-hashcf756397cc3ca81b2650c8801fd64e172504015a.jpg. - After copying all photos, we are verifying that all photo files copied successfully by comparing file hashes. It guarantees that there won't be any corrupted photos that is caused by disk operation failures.
- All photo taken dates, address information is saved on local SQLite database on the output folder's top most folder
photo-cli.sqlite3to able to open photos by their all metadata information. - As
album-nameargument is given with value ofMy-Albumwith a given album type ofDateRange. We are creating an album on the database with the earliest and latest photo taken date able to match photos by date range with the album name to open photographs later. - As
auto-reverse-geocode-albumargument is given, we are creating albums on the database by each reverse geocode location property level to be able to open photos by the reverse geocode location. These are some of albums created regarding to sample photographs.Firenze,Venezia,Italia,United Kingdom. - As
delete-on-sourceargument is given, we are deleting all source photo files, companion files & empty directories after archiving process is completed successfully.. - Showing all the statistics of the process on the output.
photo-cli list --input [relative|full folder path] --type Albums
Same command with shorter alias of all argument names & values
photo-cli list -i [relative|full folder path] -t 1
Click to expand
┌────┬──────────────────┬────────────────┬─────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────┐
│ Id │ Name │ Type │ Created At │ Configuration │
├────┼──────────────────┼────────────────┼─────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────┤
│ 1 │ My-Album │ UserDefined │ 2025-07-27 17:07:47 │ {"PhotoIds":[3],"DateRange":{"Start":"2005-08-13T09:47:23","End":"2025-06-03T13:53:36"}} │
│ 2 │ United Kingdom │ ReverseGeocode │ 2025-07-27 17:07:47 │ {"ReverseGeocode":{"ReverseGeocodeFormatted":"United Kingdom"}} │
│ 3 │ Kenya-Barut ward │ ReverseGeocode │ 2025-07-27 17:07:47 │ {"ReverseGeocode":{"ReverseGeocodeFormatted":"Kenya-Barut ward"}} │
│ 4 │ España-Madrid │ ReverseGeocode │ 2025-07-27 17:07:47 │ {"ReverseGeocode":{"ReverseGeocodeFormatted":"España-Madrid"}} │
│ 5 │ Italia-Arezzo │ ReverseGeocode │ 2025-07-27 17:07:47 │ {"ReverseGeocode":{"ReverseGeocodeFormatted":"Italia-Arezzo"}} │
│ 6 │ Italia-Venezia │ ReverseGeocode │ 2025-07-27 17:07:47 │ {"ReverseGeocode":{"ReverseGeocodeFormatted":"Italia-Venezia"}} │
│ 7 │ Italia-Firenze │ ReverseGeocode │ 2025-07-27 17:07:47 │ {"ReverseGeocode":{"ReverseGeocodeFormatted":"Italia-Firenze"}} │
│ 8 │ United Kingdom │ ReverseGeocode │ 2025-07-27 17:07:47 │ {"ReverseGeocode":{"Address1":"United Kingdom"}} │
│ 9 │ Kenya │ ReverseGeocode │ 2025-07-27 17:07:47 │ {"ReverseGeocode":{"Address1":"Kenya"}} │
│ 10 │ España │ ReverseGeocode │ 2025-07-27 17:07:47 │ {"ReverseGeocode":{"Address1":"España"}} │
│ 11 │ Italia │ ReverseGeocode │ 2025-07-27 17:07:47 │ {"ReverseGeocode":{"Address1":"Italia"}} │
│ 12 │ Barut ward │ ReverseGeocode │ 2025-07-27 17:07:47 │ {"ReverseGeocode":{"Address2":"Barut ward"}} │
│ 13 │ Madrid │ ReverseGeocode │ 2025-07-27 17:07:47 │ {"ReverseGeocode":{"Address2":"Madrid"}} │
│ 14 │ Arezzo │ ReverseGeocode │ 2025-07-27 17:07:47 │ {"ReverseGeocode":{"Address2":"Arezzo"}} │
│ 15 │ Venezia │ ReverseGeocode │ 2025-07-27 17:07:47 │ {"ReverseGeocode":{"Address2":"Venezia"}} │
│ 16 │ Firenze │ ReverseGeocode │ 2025-07-27 17:07:47 │ {"ReverseGeocode":{"Address2":"Firenze"}} │
└────┴──────────────────┴────────────────┴─────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────┘
Contents of the Photos table in photo-cli.sqlite3 SQLite Database in Markdown Table (output of archive command)
Click to expand
| Id | Path | CreatedAt | DateTaken | ReverseGeocodeFormatted | Latitude | Longitude | Year | Month | Day | Hour | Minute | Seconds | Address1 | Address2 | Address3 | Address4 | Address5 | Address6 | Address7 | Address8 | Sha1Hash | IsDeleted | ModifiedAt |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 2012/06/22/2012.06.22_19.52.31-bb649a18b3e7bb3df3701587a13f833749091817.jpg | 2025-07-27 17:07:47.378073 | 2012-06-22 19:52:31 | United Kingdom | 51.4248 | -0.6736 | 2012 | 6 | 22 | 19 | 52 | 31 | United Kingdom | null | null | null | null | null | null | null | bb649a18b3e7bb3df3701587a13f833749091817 | 0 | null |
| 2 | 2005/08/13/2005.08.13_09.47.23-5842c73cfdc5f347551bb6016e00c71bb1393169.jpg | 2025-07-27 17:07:47.378273 | 2005-08-13 09:47:23 | Kenya-Barut ward | -0.3713 | 36.0564 | 2005 | 8 | 13 | 9 | 47 | 23 | Kenya | Barut ward | null | null | null | null | null | null | 5842c73cfdc5f347551bb6016e00c71bb1393169 | 0 | null |
| 3 | no-photo-taken-date/cf756397cc3ca81b2650c8801fd64e172504015a.jpg | 2025-07-27 17:07:47.378275 | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | cf756397cc3ca81b2650c8801fd64e172504015a | 0 | null |
| 4 | 2015/04/10/2015.04.10_20.12.23-9f4e6d352ec172e1059571250655e376769080fe.jpg | 2025-07-27 17:07:47.378276 | 2015-04-10 20:12:23 | España-Madrid | 40.447 | -3.7248 | 2015 | 4 | 10 | 20 | 12 | 23 | España | Madrid | null | null | null | null | null | null | 9f4e6d352ec172e1059571250655e376769080fe | 0 | null |
| 5 | 2015/04/10/2015.04.10_20.12.23-3907fc960f2873f40c8f35643dd444e0468be131.jpg | 2025-07-27 17:07:47.378277 | 2015-04-10 20:12:23 | España-Madrid | 40.447 | -3.7248 | 2015 | 4 | 10 | 20 | 12 | 23 | España | Madrid | null | null | null | null | null | null | 3907fc960f2873f40c8f35643dd444e0468be131 | 0 | null |
| 6 | 2008/07/16/2008.07.16_11.33.20-90d835861e1aa3c829e3ab28a7f01ec3a090f664.jpg | 2025-07-27 17:07:47.378278 | 2008-07-16 11:33:20 | null | null | null | 2008 | 7 | 16 | 11 | 33 | 20 | null | null | null | null | null | null | null | null | 90d835861e1aa3c829e3ab28a7f01ec3a090f664 | 0 | null |
| 7 | 2008/10/22/2008.10.22_16.44.01-d470205a1d331a9d3765b3762b7c954bb8efc6ea.jpg | 2025-07-27 17:07:47.378278 | 2008-10-22 16:44:01 | Italia-Arezzo | 43.4684 | 11.8815 | 2008 | 10 | 22 | 16 | 44 | 1 | Italia | Arezzo | null | null | null | null | null | null | d470205a1d331a9d3765b3762b7c954bb8efc6ea | 0 | null |
| 8 | 2008/10/22/2008.10.22_17.00.07-a0ab699f5f99fce8ff49163e87c7590c2c9a66eb.jpg | 2025-07-27 17:07:47.378279 | 2008-10-22 17:00:07 | Italia-Arezzo | 43.4645 | 11.8815 | 2008 | 10 | 22 | 17 | 0 | 7 | Italia | Arezzo | null | null | null | null | null | null | a0ab699f5f99fce8ff49163e87c7590c2c9a66eb | 0 | null |
| 9 | 2008/10/22/2008.10.22_16.52.15-6b89a245809031ecc47789cdeaa332545330fc39.jpg | 2025-07-27 17:07:47.37828 | 2008-10-22 16:52:15 | Italia-Arezzo | 43.4673 | 11.8792 | 2008 | 10 | 22 | 16 | 52 | 15 | Italia | Arezzo | null | null | null | null | null | null | 6b89a245809031ecc47789cdeaa332545330fc39 | 0 | null |
| 10 | 2008/10/22/2008.10.22_16.55.37-dd42edcde2433a7df4a3d67bf61944a20884da89.jpg | 2025-07-27 17:07:47.378281 | 2008-10-22 16:55:37 | Italia-Arezzo | 43.466 | 11.8791 | 2008 | 10 | 22 | 16 | 55 | 37 | Italia | Arezzo | null | null | null | null | null | null | dd42edcde2433a7df4a3d67bf61944a20884da89 | 0 | null |
| 11 | 2008/10/22/2008.10.22_16.43.21-3b0a3215b4f66d7ff4804dd223f192c21aee71bc.jpg | 2025-07-27 17:07:47.378282 | 2008-10-22 16:43:21 | Italia-Arezzo | 43.4684 | 11.8816 | 2008 | 10 | 22 | 16 | 43 | 21 | Italia | Arezzo | null | null | null | null | null | null | 3b0a3215b4f66d7ff4804dd223f192c21aee71bc | 0 | null |
| 12 | 2008/10/22/2008.10.22_16.29.49-629b0b141634d6c0906e49af448bec8d755ba32c.jpg | 2025-07-27 17:07:47.378282 | 2008-10-22 16:29:49 | Italia-Arezzo | 43.4672 | 11.8854 | 2008 | 10 | 22 | 16 | 29 | 49 | Italia | Arezzo | null | null | null | null | null | null | 629b0b141634d6c0906e49af448bec8d755ba32c | 0 | null |
| 13 | 2008/10/22/2008.10.22_16.38.20-620d23336a12ab54f9f0190fe93960a4dba2df59.jpg | 2025-07-27 17:07:47.378283 | 2008-10-22 16:38:20 | Italia-Arezzo | 43.4671 | 11.8845 | 2008 | 10 | 22 | 16 | 38 | 20 | Italia | Arezzo | null | null | null | null | null | null | 620d23336a12ab54f9f0190fe93960a4dba2df59 | 0 | null |
| 14 | 2008/10/22/2008.10.22_16.28.39-5d66eec547469a1817bda4abe35c801359b2bb55.jpg | 2025-07-27 17:07:47.378284 | 2008-10-22 16:28:39 | Italia-Arezzo | 43.4674 | 11.8851 | 2008 | 10 | 22 | 16 | 28 | 39 | Italia | Arezzo | null | null | null | null | null | null | 5d66eec547469a1817bda4abe35c801359b2bb55 | 0 | null |
| 15 | 2008/10/22/2008.10.22_16.46.53-f670f2bb6c54898894b06b083185b05086bd4e6e.jpg | 2025-07-27 17:07:47.378284 | 2008-10-22 16:46:53 | Italia-Arezzo | 43.4682 | 11.8802 | 2008 | 10 | 22 | 16 | 46 | 53 | Italia | Arezzo | null | null | null | null | null | null | f670f2bb6c54898894b06b083185b05086bd4e6e | 0 | null |
| 16 | 2025/06/03/2025.06.03_13.53.36-8a45af72730474e22582afbe72f53685d705a72c.heic | 2025-07-27 17:07:47.378285 | 2025-06-03 13:53:36 | Italia-Venezia | 45.4332 | 12.3246 | 2025 | 6 | 3 | 13 | 53 | 36 | Italia | Venezia | null | null | null | null | null | null | 8a45af72730474e22582afbe72f53685d705a72c | 0 | null |
| 17 | 2005/12/14/2005.12.14_14.39.47-03cb14d5c68beed97cbe73164de9771d537fcd96.jpg | 2025-07-27 17:07:47.378286 | 2005-12-14 14:39:47 | Italia-Firenze | 43.7856 | 11.2346 | 2005 | 12 | 14 | 14 | 39 | 47 | Italia | Firenze | null | null | null | null | null | null | 03cb14d5c68beed97cbe73164de9771d537fcd96 | 0 | null |
| Original Folder Hierarchy | After photo-cli |
|---|---|
├── DSC_5727.jpg
├── GOPR6742.jpg
├── Italy album
│ ├── DJI_01732.jpg
│ ├── DJI_01733.jpg
│ ├── DSC00001.JPG
│ ├── DSC03467.jpg
│ ├── DSC_1769.JPG
│ ├── DSC_1770.JPG
│ ├── DSC_1770_(same).jpg
│ ├── DSC_1771.JPG
│ ├── GOPR7496.jpg
│ ├── GOPR7497.jpg
│ ├── IMG_0747.JPG
│ ├── IMG_1979.HEIC
│ ├── IMG_1979.mov
│ ├── IMG_1979.xmp
│ ├── IMG_2371.jpg
│ └── IMG_O1979.aae
└── Spain Journey
├── DSC_1807.jpg
├── DSC_1808.jpg
└── IMG_5397.jpg
|
. ├── 2005.08.13_09.47.23-Kenya-Barut ward.jpg ├── 2005.12.14-2025.06.03-Italy album │ ├── 2005.12.14_14.39.47-Italia-Firenze.jpg │ ├── 2008.10.22_16.28.39-Italia-Arezzo.jpg │ ├── 2008.10.22_16.29.49-Italia-Arezzo.jpg │ ├── 2008.10.22_16.38.20-Italia-Arezzo.jpg │ ├── 2008.10.22_16.43.21-Italia-Arezzo.jpg │ ├── 2008.10.22_16.44.01-Italia-Arezzo.jpg │ ├── 2008.10.22_16.46.53-Italia-Arezzo.jpg │ ├── 2008.10.22_16.52.15-Italia-Arezzo.jpg │ ├── 2008.10.22_16.55.37-Italia-Arezzo.jpg │ ├── 2008.10.22_17.00.07-Italia-Arezzo-1.jpg │ ├── 2008.10.22_17.00.07-Italia-Arezzo-2.jpg │ ├── 2025.06.03_13.53.36-Italia-Venezia.heic │ └── 2025.06.03_13.53.36-Italia-Venezia.mov ├── 2012.06.22_19.52.31-United Kingdom.jpg ├── 2015.04.10-2015.04.10-Spain Journey │ ├── 2015.04.10_20.12.23-España-Madrid-1.jpg │ └── 2015.04.10_20.12.23-España-Madrid-2.jpg ├── Italy album │ └── no-address │ └── IMG_2371.jpg ├── Spain Journey │ └── no-address-and-no-photo-taken-date │ └── IMG_5397.jpg ├── photo-cli-report.csv └── sha1.lst |
There are lots of transformation options and customization settings, this is just a one of them. This transformation is done by running only the following single command;
Command with explicit argument names & values
photo-cli copy --process-type SubFoldersPreserveFolderHierarchy --naming-style DateTimeWithSecondsAddress --number-style PaddingZeroCharacter --folder-append DayRange --folder-append-location Prefix --reverse-geocode OpenStreetMapFoundation --openstreetmap-properties country city --output photo-cli-test --no-coordinate InSubFolder --no-taken-date InSubFolder --verify --expected-day-range 7300 --missing-reverse-geocode Continue
Same command with shorter alias of all argument names & values
photo-cli copy -f 2 -s 8 -n 2 -a 4 -p 1 -e 2 -r country city -o photo-cli-test -c 3 -t 3 -v -w 7300 -z 0
Console/terminal output (as progress may take time, for each operation completion status shown with progress)
Click to expand
[17:07:28] Searching photo main files: started
[17:07:28] Searching photo main files: finished. 18 photo(s) found.
[17:07:28] Searching photo companion files: started
[17:07:28] Searching photo companion files: finished. 1 companion file(s) found.
[17:07:28] No coordinate found on `Gps` directory. Path:<
/Users/ac/src/photo-cli/docs/test-photographs/Spain Journey/IMG_5397.jpg>
[17:07:28] No coordinate found on `Gps` directory. Path:<
/Users/ac/src/photo-cli/docs/test-photographs/Italy album/IMG_2371.jpg>
[17:07:28] This OpenStreetMapFoundation provider is using rate limit of 1
second(s) between each request
[17:07:28] Reverse Geocoding: started
[17:07:29] Requested address types: City on index #2, not found on OpenStreetMap's response. Available types found:
{"country_code":"gb","country":"United Kingdom","postcode":"SL4 2DR","suburb":"Sunninghill and Ascot","road":"Windsor Road"}
. Path:</Users/ac/src/photo-cli/docs/test-photographs/GOPR6742.jpg>
[17:07:44] Reverse Geocoding: finished.
[17:07:44] Directory grouping: started
[17:07:44] Directory grouping: finished.
[17:07:44] Processing target folder: started
[17:07:45] Processing target folder: finished.
[17:07:45] Verified all photo files copied successfully by comparing file hashes from original photo files.
[17:07:45] All files SHA1 hashes written into file: sha1.lst. You may verify yourself with `sha1sum --check sha1.lst` tool in Linux/macOS.
[17:07:45] Writing csv report: started
[17:07:45] Writing csv report: finished.
Statistics
┌────────────────────────────────────────────────┬───────┐ddress
│ Statistic │ Count │
├────────────────────────────────────────────────┼───────┤
│ File System Error(s) │ 0 │
│ Photo(s) found │ 18 │
│ Photo(s) copied │ 18 │
│ Photo(s) existed on the output │ 0 │
│ Photo(s) are skipped, they have the same photo │ 0 │
│ Directory/directories created │ 4 │
│ │ │
│ Companion file(s) found │ 1 │
│ Companion file(s) copied │ 1 │
│ Companion file(s) existed on the output │ 0 │
│ │ │
│ Source photo file(s) deleted │ 0 │
│ Source companion file(s) deleted │ 0 │
│ Source empty directory(ies) deleted │ 0 │
│ │ │
│ User defined album created │ 0 │
│ User defined album updated │ 0 │
│ Auto address album created │ 0 │
│ │ │
│ Reverse geocode request sent │ 14 │
│ Reverse geocode evaluated from memory │ 2 │
│ Reverse geocode evaluated from database │ 0 │
│ Photo(s) has taken date and coordinate │ 16 │
│ Photo(s) has taken date but no coordinate │ 1 │
│ Photo(s) has coordinate but no taken date │ 0 │
│ Photo(s) has no taken date and coordinate │ 1 │
│ │ │
│ Photo(s) has unknown/invalid format │ 0 │
│ Photo(s) caused unexpected error internally │ 0 │
└────────────────────────────────────────────────┴───────┘
[17:07:45] Copy process completed successfully
- Gather all photo paths in the source folder within subfolders.
- Extract EXIF data of each photograph's taken date and coordinate.
- As the file name strategy is selected as
DateTimeWithSecondsAddressand it contains the address, by using third-party reverse geocode provider we are building the address withOpenStreetMapby using given administrative levels ascity town suburbfor each photograph. - As the folder process type is selected as
SubFoldersPreserveFolderHierarchyfolder and file hierarchy at the new output folder will be the same. - As the folder append type is selected as
DayRangeand folder append location isPrefix, folder names on output folder will be created with same name prefixed with a earliest and latest photograph taken date. For example:2005.12.14-2008.10.22-Italy album(original folder name isItaly album) - As the file name strategy is selected as
DateTimeWithSecondsAddresseach photograph file name would be copied as photo taken date unified with the address which is built from third party reverse geocode provider by photograph's coordinate. For example:2012.06.22_19.52.31-United Kingdom-Ascot-Sunninghill and Ascot.jpg(original file name isGOPR6742.jpg) - As no photograph taken date action is selected as
InSubFolderand no coordinate action is selected asInSubFolder, photographs with no related EXIF data copied into a sub folder by obeying original folder hierarchy. For example:/Italy album/no-address/IMG_2371.jpgand/Spain Journey/no-address-and-no-photo-taken-date/IMG_5397.jpg - As verify is added, it is verifying that all photo files copied successfully by comparing file hashes. By adding this, it guarantees that there won't be any corrupted photos that is caused by disk operation failures.
- To verify and see all information in one place,
photo-cli-report.csvreport will be created on the output file. Can be examined in Markdown table or CSV file.
Click to expand
| PhotoPath | PhotoNewPath | PhotoDateTaken | ReverseGeocodeFormatted | Latitude | Longitude | PhotoTakenYear | PhotoTakenMonth | PhotoTakenDay | PhotoTakenHour | PhotoTakenMinute | PhotoTakenSeconds | Address1 | Address2 | Address3 | Address4 | Address5 | Address6 | Address7 | Address8 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| /TestImages/DSC_5727.jpg | photo-cli-test/2005.08.13_09.47.23-Kenya.jpg | 08/13/2005 09:47:23 | Kenya | -0.37129999999999996 | 36.056416666666664 | 2005 | 8 | 13 | 9 | 47 | 23 | Kenya | |||||||
| /TestImages/GOPR6742.jpg | photo-cli-test/2012.06.22_19.52.31-United Kingdom-Ascot-Sunninghill and Ascot.jpg | 06/22/2012 19:52:31 | United Kingdom-Ascot-Sunninghill and Ascot | 51.424838333333334 | -0.6735616666666666 | 2012 | 6 | 22 | 19 | 52 | 31 | United Kingdom | Ascot | Sunninghill and Ascot | |||||
| /TestImages/Italy album/DSC03467.jpg | photo-cli-test/2005.12.14-2008.10.22-Italy album/2005.12.14_14.39.47-Italia-Firenze-Quartiere 1.jpg | 12/14/2005 14:39:47 | Italia-Firenze-Quartiere 1 | 43.78559443333333 | 11.234619433333334 | 2005 | 12 | 14 | 14 | 39 | 47 | Italia | Firenze | Quartiere 1 | |||||
| /TestImages/Italy album/GOPR7497.jpg | photo-cli-test/2005.12.14-2008.10.22-Italy album/2008.10.22_16.28.39-Italia-Arezzo.jpg | 10/22/2008 16:28:39 | Italia-Arezzo | 43.46744833333334 | 11.885126666663888 | 2008 | 10 | 22 | 16 | 28 | 39 | Italia | Arezzo | ||||||
| /TestImages/Italy album/DJI_01732.jpg | photo-cli-test/2005.12.14-2008.10.22-Italy album/2008.10.22_16.29.49-Italia-Arezzo.jpg | 10/22/2008 16:29:49 | Italia-Arezzo | 43.46715666666389 | 11.885394999997223 | 2008 | 10 | 22 | 16 | 29 | 49 | Italia | Arezzo | ||||||
| /TestImages/Italy album/GOPR7496.jpg | photo-cli-test/2005.12.14-2008.10.22-Italy album/2008.10.22_16.38.20-Italia-Arezzo.jpg | 10/22/2008 16:38:20 | Italia-Arezzo | 43.467081666663894 | 11.884538333330555 | 2008 | 10 | 22 | 16 | 38 | 20 | Italia | Arezzo | ||||||
| /TestImages/Italy album/DJI_01733.jpg | photo-cli-test/2005.12.14-2008.10.22-Italy album/2008.10.22_16.43.21-Italia-Arezzo.jpg | 10/22/2008 16:43:21 | Italia-Arezzo | 43.468365 | 11.881634999972222 | 2008 | 10 | 22 | 16 | 43 | 21 | Italia | Arezzo | ||||||
| /TestImages/Italy album/DSC00001.JPG | photo-cli-test/2005.12.14-2008.10.22-Italy album/2008.10.22_16.44.01-Italia-Arezzo.jpg | 10/22/2008 16:44:01 | Italia-Arezzo | 43.46844166666667 | 11.881515 | 2008 | 10 | 22 | 16 | 44 | 1 | Italia | Arezzo | ||||||
| /TestImages/Italy album/IMG_0747.JPG | photo-cli-test/2005.12.14-2008.10.22-Italy album/2008.10.22_16.46.53-Italia-Arezzo.jpg | 10/22/2008 16:46:53 | Italia-Arezzo | 43.468243333330555 | 11.880171666638889 | 2008 | 10 | 22 | 16 | 46 | 53 | Italia | Arezzo | ||||||
| /TestImages/Italy album/DSC_1771.JPG | photo-cli-test/2005.12.14-2008.10.22-Italy album/2008.10.22_16.52.15-Italia-Arezzo.jpg | 10/22/2008 16:52:15 | Italia-Arezzo | 43.46725499999722 | 11.879213333333334 | 2008 | 10 | 22 | 16 | 52 | 15 | Italia | Arezzo | ||||||
| /TestImages/Italy album/DSC_1769.JPG | photo-cli-test/2005.12.14-2008.10.22-Italy album/2008.10.22_16.55.37-Italia-Arezzo.jpg | 10/22/2008 16:55:37 | Italia-Arezzo | 43.46601166663889 | 11.87911166663889 | 2008 | 10 | 22 | 16 | 55 | 37 | Italia | Arezzo | ||||||
| /TestImages/Italy album/DSC_1770.JPG | photo-cli-test/2005.12.14-2008.10.22-Italy album/2008.10.22_17.00.07-Italia-Arezzo-1.jpg | 10/22/2008 17:00:07 | Italia-Arezzo | 43.464455 | 11.881478333333334 | 2008 | 10 | 22 | 17 | 0 | 7 | Italia | Arezzo | ||||||
| /TestImages/Italy album/DSC_1770_(same).jpg | photo-cli-test/2005.12.14-2008.10.22-Italy album/2008.10.22_17.00.07-Italia-Arezzo-2.jpg | 10/22/2008 17:00:07 | Italia-Arezzo | 43.464455 | 11.881478333333334 | 2008 | 10 | 22 | 17 | 0 | 7 | Italia | Arezzo | ||||||
| /TestImages/Italy album/IMG_2371.jpg | photo-cli-test/Italy album/no-address/IMG_2371.jpg | 07/16/2008 11:33:20 | 2008 | 7 | 16 | 11 | 33 | 20 | |||||||||||
| /TestImages/Spain Journey/DSC_1807.jpg | photo-cli-test/2015.04.10-2015.04.10-Spain Journey/2015.04.10_20.12.23-España-Madrid-1.jpg | 04/10/2015 20:12:23 | España-Madrid | 40.44697222222222 | -3.724752777777778 | 2015 | 4 | 10 | 20 | 12 | 23 | España | Madrid | ||||||
| /TestImages/Spain Journey/DSC_1808.jpg | photo-cli-test/2015.04.10-2015.04.10-Spain Journey/2015.04.10_20.12.23-España-Madrid-2.jpg | 04/10/2015 20:12:23 | España-Madrid | 40.44697222222222 | -3.724752777777778 | 2015 | 4 | 10 | 20 | 12 | 23 | España | Madrid | ||||||
| /TestImages/Spain Journey/IMG_5397.jpg | photo-cli-test/Spain Journey/no-address-and-no-photo-taken-date/IMG_5397.jpg |
Click to expand
PhotoPath,PhotoNewPath,PhotoDateTaken,ReverseGeocodeFormatted,Latitude,Longitude,PhotoTakenYear,PhotoTakenMonth,PhotoTakenDay,PhotoTakenHour,PhotoTakenMinute,PhotoTakenSeconds,Address1,Address2,Address3,Address4,Address5,Address6,Address7,Address8
/TestImages/DSC_5727.jpg,photo-cli-test/2005.08.13_09.47.23-Kenya.jpg,08/13/2005 09:47:23,Kenya,-0.37129999999999996,36.056416666666664,2005,8,13,9,47,23,Kenya,,,,,,,
/TestImages/GOPR6742.jpg,photo-cli-test/2012.06.22_19.52.31-United Kingdom-Ascot-Sunninghill and Ascot.jpg,06/22/2012 19:52:31,United Kingdom-Ascot-Sunninghill and Ascot,51.424838333333334,-0.6735616666666666,2012,6,22,19,52,31,United Kingdom,Ascot,Sunninghill and Ascot,,,,,
/TestImages/Italy album/DSC03467.jpg,photo-cli-test/2005.12.14-2008.10.22-Italy album/2005.12.14_14.39.47-Italia-Firenze-Quartiere 1.jpg,12/14/2005 14:39:47,Italia-Firenze-Quartiere 1,43.78559443333333,11.234619433333334,2005,12,14,14,39,47,Italia,Firenze,Quartiere 1,,,,,
/TestImages/Italy album/GOPR7497.jpg,photo-cli-test/2005.12.14-2008.10.22-Italy album/2008.10.22_16.28.39-Italia-Arezzo.jpg,10/22/2008 16:28:39,Italia-Arezzo,43.46744833333334,11.885126666663888,2008,10,22,16,28,39,Italia,Arezzo,,,,,,
/TestImages/Italy album/DJI_01732.jpg,photo-cli-test/2005.12.14-2008.10.22-Italy album/2008.10.22_16.29.49-Italia-Arezzo.jpg,10/22/2008 16:29:49,Italia-Arezzo,43.46715666666389,11.885394999997223,2008,10,22,16,29,49,Italia,Arezzo,,,,,,
/TestImages/Italy album/GOPR7496.jpg,photo-cli-test/2005.12.14-2008.10.22-Italy album/2008.10.22_16.38.20-Italia-Arezzo.jpg,10/22/2008 16:38:20,Italia-Arezzo,43.467081666663894,11.884538333330555,2008,10,22,16,38,20,Italia,Arezzo,,,,,,
/TestImages/Italy album/DJI_01733.jpg,photo-cli-test/2005.12.14-2008.10.22-Italy album/2008.10.22_16.43.21-Italia-Arezzo.jpg,10/22/2008 16:43:21,Italia-Arezzo,43.468365,11.881634999972222,2008,10,22,16,43,21,Italia,Arezzo,,,,,,
/TestImages/Italy album/DSC00001.JPG,photo-cli-test/2005.12.14-2008.10.22-Italy album/2008.10.22_16.44.01-Italia-Arezzo.jpg,10/22/2008 16:44:01,Italia-Arezzo,43.46844166666667,11.881515,2008,10,22,16,44,1,Italia,Arezzo,,,,,,
/TestImages/Italy album/IMG_0747.JPG,photo-cli-test/2005.12.14-2008.10.22-Italy album/2008.10.22_16.46.53-Italia-Arezzo.jpg,10/22/2008 16:46:53,Italia-Arezzo,43.468243333330555,11.880171666638889,2008,10,22,16,46,53,Italia,Arezzo,,,,,,
/TestImages/Italy album/DSC_1771.JPG,photo-cli-test/2005.12.14-2008.10.22-Italy album/2008.10.22_16.52.15-Italia-Arezzo.jpg,10/22/2008 16:52:15,Italia-Arezzo,43.46725499999722,11.879213333333334,2008,10,22,16,52,15,Italia,Arezzo,,,,,,
/TestImages/Italy album/DSC_1769.JPG,photo-cli-test/2005.12.14-2008.10.22-Italy album/2008.10.22_16.55.37-Italia-Arezzo.jpg,10/22/2008 16:55:37,Italia-Arezzo,43.46601166663889,11.87911166663889,2008,10,22,16,55,37,Italia,Arezzo,,,,,,
/TestImages/Italy album/DSC_1770.JPG,photo-cli-test/2005.12.14-2008.10.22-Italy album/2008.10.22_17.00.07-Italia-Arezzo-1.jpg,10/22/2008 17:00:07,Italia-Arezzo,43.464455,11.881478333333334,2008,10,22,17,0,7,Italia,Arezzo,,,,,,
/TestImages/Italy album/DSC_1770_(same).jpg,photo-cli-test/2005.12.14-2008.10.22-Italy album/2008.10.22_17.00.07-Italia-Arezzo-2.jpg,10/22/2008 17:00:07,Italia-Arezzo,43.464455,11.881478333333334,2008,10,22,17,0,7,Italia,Arezzo,,,,,,
/TestImages/Italy album/IMG_2371.jpg,photo-cli-test/Italy album/no-address/IMG_2371.jpg,07/16/2008 11:33:20,,,,2008,7,16,11,33,20,,,,,,,,
/TestImages/Spain Journey/DSC_1807.jpg,photo-cli-test/2015.04.10-2015.04.10-Spain Journey/2015.04.10_20.12.23-España-Madrid-1.jpg,04/10/2015 20:12:23,España-Madrid,40.44697222222222,-3.724752777777778,2015,4,10,20,12,23,España,Madrid,,,,,,
/TestImages/Spain Journey/DSC_1808.jpg,photo-cli-test/2015.04.10-2015.04.10-Spain Journey/2015.04.10_20.12.23-España-Madrid-2.jpg,04/10/2015 20:12:23,España-Madrid,40.44697222222222,-3.724752777777778,2015,4,10,20,12,23,España,Madrid,,,,,,
/TestImages/Spain Journey/IMG_5397.jpg,photo-cli-test/Spain Journey/no-address-and-no-photo-taken-date/IMG_5397.jpg,,,,,,,,,,,,,,,,,,
After archiving our photos like the first example, we can list all the photos metadata and open the photos (currently only macOS Preview App is supported) by some filters.
This viewing process is done by running the following single command;
photo-cli list --input [relative|full existing archive path] --type PhotosByDate --year 2008 --month 10
Same command with shorter alias of all argument names & values
photo-cli list -i [relative|full existing archive path] -t 3 -y 2008 -m 10
Note: You can also open the photos by geolocation name (if you have used the argument auto-reverse-geocode-album while archiving) or by album ID.
Important note: If using Windows or Linux, you will get the list of full photo paths as output currently (like below) as there is no default photo viewer apps on these operating systems. If you want to open the photos on your photo viewer app of your, you can pipe this photo paths to your prefered photo viewer app.
Click to expand
/[archive-full-folder-path]/2008/10/22/2008.10.22_16.29.49-629b0b141634d6c0906e49af448bec8d755ba32c.jpg
/[archive-full-folder-path]/2008/10/22/2008.10.22_16.55.37-dd42edcde2433a7df4a3d67bf61944a20884da89.jpg
/[archive-full-folder-path]/2008/10/22/2008.10.22_17.00.07-a0ab699f5f99fce8ff49163e87c7590c2c9a66eb.jpg
/[archive-full-folder-path]/2008/10/22/2008.10.22_16.46.53-f670f2bb6c54898894b06b083185b05086bd4e6e.jpg
/[archive-full-folder-path]/2008/10/22/2008.10.22_16.44.01-d470205a1d331a9d3765b3762b7c954bb8efc6ea.jpg
/[archive-full-folder-path]/2008/10/22/2008.10.22_16.52.15-6b89a245809031ecc47789cdeaa332545330fc39.jpg
/[archive-full-folder-path]/2008/10/22/2008.10.22_16.28.39-5d66eec547469a1817bda4abe35c801359b2bb55.jpg
/[archive-full-folder-path]/2008/10/22/2008.10.22_16.38.20-620d23336a12ab54f9f0190fe93960a4dba2df59.jpg
/[archive-full-folder-path]/2008/10/22/2008.10.22_16.43.21-3b0a3215b4f66d7ff4804dd223f192c21aee71bc.jpg
Click to expand
| PhotoPath | PhotoNewPath | PhotoDateTaken | ReverseGeocodeFormatted | Latitude | Longitude | PhotoTakenYear | PhotoTakenMonth | PhotoTakenDay | PhotoTakenHour | PhotoTakenMinute | PhotoTakenSeconds | Address1 | Address2 | Address3 | Address4 | Address5 | Address6 | Address7 | Address8 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| /TestImages/DSC_5727.jpg | 08/13/2005 09:47:23 | Kenya | -0.37129999999999996 | 36.056416666666664 | 2005 | 8 | 13 | 9 | 47 | 23 | Kenya | ||||||||
| /TestImages/GOPR6742.jpg | 06/22/2012 19:52:31 | United Kingdom-Ascot-Sunninghill and Ascot | 51.424838333333334 | -0.6735616666666666 | 2012 | 6 | 22 | 19 | 52 | 31 | United Kingdom | Ascot | Sunninghill and Ascot | ||||||
| /TestImages/Italy album/DSC_1770.JPG | 10/22/2008 17:00:07 | Italia-Arezzo | 43.464455 | 11.881478333333334 | 2008 | 10 | 22 | 17 | 0 | 7 | Italia | Arezzo | |||||||
| /TestImages/Italy album/DSC_1771.JPG | 10/22/2008 16:52:15 | Italia-Arezzo | 43.46725499999722 | 11.879213333333334 | 2008 | 10 | 22 | 16 | 52 | 15 | Italia | Arezzo | |||||||
| /TestImages/Italy album/IMG_0747.JPG | 10/22/2008 16:46:53 | Italia-Arezzo | 43.468243333330555 | 11.880171666638889 | 2008 | 10 | 22 | 16 | 46 | 53 | Italia | Arezzo | |||||||
| /TestImages/Italy album/IMG_2371.jpg | 07/16/2008 11:33:20 | 2008 | 7 | 16 | 11 | 33 | 20 | ||||||||||||
| /TestImages/Italy album/DSC_1770_(same).jpg | 10/22/2008 17:00:07 | Italia-Arezzo | 43.464455 | 11.881478333333334 | 2008 | 10 | 22 | 17 | 0 | 7 | Italia | Arezzo | |||||||
| /TestImages/Italy album/DJI_01733.jpg | 10/22/2008 16:43:21 | Italia-Arezzo | 43.468365 | 11.881634999972222 | 2008 | 10 | 22 | 16 | 43 | 21 | Italia | Arezzo | |||||||
| /TestImages/Italy album/DSC00001.JPG | 10/22/2008 16:44:01 | Italia-Arezzo | 43.46844166666667 | 11.881515 | 2008 | 10 | 22 | 16 | 44 | 1 | Italia | Arezzo | |||||||
| /TestImages/Italy album/DSC_1769.JPG | 10/22/2008 16:55:37 | Italia-Arezzo | 43.46601166663889 | 11.87911166663889 | 2008 | 10 | 22 | 16 | 55 | 37 | Italia | Arezzo | |||||||
| /TestImages/Italy album/GOPR7497.jpg | 10/22/2008 16:28:39 | Italia-Arezzo | 43.46744833333334 | 11.885126666663888 | 2008 | 10 | 22 | 16 | 28 | 39 | Italia | Arezzo | |||||||
| /TestImages/Italy album/DSC03467.jpg | 12/14/2005 14:39:47 | Italia-Firenze-Quartiere 1 | 43.78559443333333 | 11.234619433333334 | 2005 | 12 | 14 | 14 | 39 | 47 | Italia | Firenze | Quartiere 1 | ||||||
| /TestImages/Italy album/GOPR7496.jpg | 10/22/2008 16:38:20 | Italia-Arezzo | 43.467081666663894 | 11.884538333330555 | 2008 | 10 | 22 | 16 | 38 | 20 | Italia | Arezzo | |||||||
| /TestImages/Italy album/DJI_01732.jpg | 10/22/2008 16:29:49 | Italia-Arezzo | 43.46715666666389 | 11.885394999997223 | 2008 | 10 | 22 | 16 | 29 | 49 | Italia | Arezzo | |||||||
| /TestImages/Spain Journey/DSC_1807.jpg | 04/10/2015 20:12:23 | España-Madrid | 40.44697222222222 | -3.724752777777778 | 2015 | 4 | 10 | 20 | 12 | 23 | España | Madrid | |||||||
| /TestImages/Spain Journey/DSC_1808.jpg | 04/10/2015 20:12:23 | España-Madrid | 40.44697222222222 | -3.724752777777778 | 2015 | 4 | 10 | 20 | 12 | 23 | España | Madrid | |||||||
| /TestImages/Spain Journey/IMG_5397.jpg |
Click to expand
PhotoPath,PhotoNewPath,PhotoDateTaken,ReverseGeocodeFormatted,Latitude,Longitude,PhotoTakenYear,PhotoTakenMonth,PhotoTakenDay,PhotoTakenHour,PhotoTakenMinute,PhotoTakenSeconds,Address1,Address2,Address3,Address4,Address5,Address6,Address7,Address8
/TestImages/DSC_5727.jpg,,08/13/2005 09:47:23,Kenya,-0.37129999999999996,36.056416666666664,2005,8,13,9,47,23,Kenya,,,,,,,
/TestImages/GOPR6742.jpg,,06/22/2012 19:52:31,United Kingdom-Ascot-Sunninghill and Ascot,51.424838333333334,-0.6735616666666666,2012,6,22,19,52,31,United Kingdom,Ascot,Sunninghill and Ascot,,,,,
/TestImages/Italy album/DSC_1770.JPG,,10/22/2008 17:00:07,Italia-Arezzo,43.464455,11.881478333333334,2008,10,22,17,0,7,Italia,Arezzo,,,,,,
/TestImages/Italy album/DSC_1771.JPG,,10/22/2008 16:52:15,Italia-Arezzo,43.46725499999722,11.879213333333334,2008,10,22,16,52,15,Italia,Arezzo,,,,,,
/TestImages/Italy album/IMG_0747.JPG,,10/22/2008 16:46:53,Italia-Arezzo,43.468243333330555,11.880171666638889,2008,10,22,16,46,53,Italia,Arezzo,,,,,,
/TestImages/Italy album/IMG_2371.jpg,,07/16/2008 11:33:20,,,,2008,7,16,11,33,20,,,,,,,,
/TestImages/Italy album/DSC_1770_(same).jpg,,10/22/2008 17:00:07,Italia-Arezzo,43.464455,11.881478333333334,2008,10,22,17,0,7,Italia,Arezzo,,,,,,
/TestImages/Italy album/DJI_01733.jpg,,10/22/2008 16:43:21,Italia-Arezzo,43.468365,11.881634999972222,2008,10,22,16,43,21,Italia,Arezzo,,,,,,
/TestImages/Italy album/DSC00001.JPG,,10/22/2008 16:44:01,Italia-Arezzo,43.46844166666667,11.881515,2008,10,22,16,44,1,Italia,Arezzo,,,,,,
/TestImages/Italy album/DSC_1769.JPG,,10/22/2008 16:55:37,Italia-Arezzo,43.46601166663889,11.87911166663889,2008,10,22,16,55,37,Italia,Arezzo,,,,,,
/TestImages/Italy album/GOPR7497.jpg,,10/22/2008 16:28:39,Italia-Arezzo,43.46744833333334,11.885126666663888,2008,10,22,16,28,39,Italia,Arezzo,,,,,,
/TestImages/Italy album/DSC03467.jpg,,12/14/2005 14:39:47,Italia-Firenze-Quartiere 1,43.78559443333333,11.234619433333334,2005,12,14,14,39,47,Italia,Firenze,Quartiere 1,,,,,
/TestImages/Italy album/GOPR7496.jpg,,10/22/2008 16:38:20,Italia-Arezzo,43.467081666663894,11.884538333330555,2008,10,22,16,38,20,Italia,Arezzo,,,,,,
/TestImages/Italy album/DJI_01732.jpg,,10/22/2008 16:29:49,Italia-Arezzo,43.46715666666389,11.885394999997223,2008,10,22,16,29,49,Italia,Arezzo,,,,,,
/TestImages/Spain Journey/DSC_1807.jpg,,04/10/2015 20:12:23,España-Madrid,40.44697222222222,-3.724752777777778,2015,4,10,20,12,23,España,Madrid,,,,,,
/TestImages/Spain Journey/DSC_1808.jpg,,04/10/2015 20:12:23,España-Madrid,40.44697222222222,-3.724752777777778,2015,4,10,20,12,23,España,Madrid,,,,,,
/TestImages/Spain Journey/IMG_5397.jpg,,,,,,,,,,,,,,,,,,,
There is some options and lots of customization settings, this is just a one of them. This information extracted is done by running only the following single command;
Command with explicit argument names & values
photo-cli info --all-folders --output photo-info.csv --reverse-geocode OpenStreetMapFoundation --openstreetmap-properties country city --no-taken-date Continue --no-coordinate Continue --missing-reverse-geocode Continue
Same command with shorter alias of all argument names & values
photo-cli info -a -o photo-info.csv -e 2 -r country city -t 0 -c 0 -z 0
Console/terminal output (as progress may take time, for each operation completion status shown as percentage)
Click to expand
[17:07:33] Searching photo main files: started
[17:07:33] Searching photo main files: finished. 18 photo(s) found.
[17:07:33] No coordinate found on `Gps` directory. Path:</Users/ac/src/photo-cli/docs/test-photographs/Spain Journey/IMG_5397.jpg>
[17:07:33] No coordinate found on `Gps` directory. Path:</Users/ac/src/photo-cli/docs/test-photographs/Italy album/IMG_2371.jpg>
[17:07:33] Reverse Geocoding: started
[17:07:33] Requested address types: City on index #2, not found on OpenStreetMap's response. Available types found:
{"country_code":"gb","country":"United Kingdom","postcode":"SL4 2DR","suburb":"Sunninghill and Ascot","road":"Windsor Road"}
. Path:</Users/ac/src/photo-cli/docs/test-photographs/GOPR6742.jpg>
[17:07:49] Reverse Geocoding: finished.
[17:07:49] Writing csv report: started
[17:07:49] Writing csv report: finished.
Statistics
┌────────────────────────────────────────────────┬───────┐
│ Statistic │ Count │
├────────────────────────────────────────────────┼───────┤
│ File System Error(s) │ 0 │
│ Photo(s) found │ 18 │
│ Photo(s) copied │ 0 │
│ Photo(s) existed on the output │ 0 │
│ Photo(s) are skipped, they have the same photo │ 0 │
│ Directory/directories created │ 0 │
│ │ │
│ Companion file(s) found │ 0 │
│ Companion file(s) copied │ 0 │
│ Companion file(s) existed on the output │ 0 │
│ │ │
│ Source photo file(s) deleted │ 0 │
│ Source companion file(s) deleted │ 0 │
│ Source empty directory(ies) deleted │ 0 │
│ │ │
│ User defined album created │ 0 │
│ User defined album updated │ 0 │
│ Auto address album created │ 0 │
│ │ │
│ Reverse geocode request sent │ 14 │
│ Reverse geocode evaluated from memory │ 2 │
│ Reverse geocode evaluated from database │ 0 │
│ Photo(s) has taken date and coordinate │ 16 │
│ Photo(s) has taken date but no coordinate │ 1 │
│ Photo(s) has coordinate but no taken date │ 0 │
│ Photo(s) has no taken date and coordinate │ 1 │
│ │ │
│ Photo(s) has unknown/invalid format │ 0 │
│ Photo(s) caused unexpected error internally │ 0 │
└────────────────────────────────────────────────┴───────┘
- As all folders is selected. We are gathering all photo paths in the source folder within subfolders.
- Extract EXIF data of each photograph's taken date and coordinate.
- As third-party reverse geocode is selected, we are building address with
OpenStreetMapby using given administrative levels ascity town suburbfor each photograph. - As no photograph taken date action is selected as
Continueand no coordinate action is selected asContinue, they are listing in report with empty data.
If you want to discover your photographs interactively in the world, you may do it by importing your CSV output (whether photo-cli copy or photo-cli info command) to Google Maps and Google Earth, you can interactively navigate through your photographs.
Open Google My Maps and after clicking Create a New Map, you can import your CSV file on a layer(you may add many layers).
After installing Google Earth Desktop, on File menu, you can import your CSV file via Import menu item.
To navigate your photographs on Google Earth Web, first you should import your CSV on Google Earth Desktop and save it as KMZ or KML. Then you can create a project and add this KML file.
This application can be installed by Homebrew (macOS & Linux), container (Docker, Podman), standalone executable (without dependency and SDK) or as .NET tool.
See the installation for details.
Note: You may test commands on test photographs which has coordinates and photograph taken dates in it.
The following command used in all samples with test photographs
photo-cli archive --input [relative|full folder path] --output [relative|full folder path] --album-type DateRange --album-name My-Album --auto-reverse-geocode-album --expected-day-range 7300 --delete-on-source --reverse-geocode OpenStreetMapFoundation --openstreetmap-properties country city
photo-cli list --input [relative|full existing archive path] --type PhotosByDate --year 2008 --month 10
docker run --rm --volume ./test-photographs:/photos/input --volume ./archive:/photos/output photocli/photocli archive --input /photos/input --output /photos/output --album-type DateRange --album-name My-Album --auto-reverse-geocode-album --expected-day-range 7300 --delete-on-source --reverse-geocode OpenStreetMapFoundation --openstreetmap-properties country cityBy extracting Exchangeable image file format stored on each of your photographs.
Photograph's taken date used to determine when photograph's date. Most of the camera/cell phones save this data without any setting.
Photograph's coordinate data is sent to selected third-party reverse geocode provider to build a address.
Most of the cameras and cellphones have GPS receiver on them. You need to be sure that on settings something like Save GPS location should
be enabled.
With the default settings, we can process jpg, jpeg, heic and png photo files. As this tool internally using MetadataExtractor package to extract image EXIF data, you can extend the supported photos with the MetadataExtractor supported files by using settings verb's set action.
settings -k SupportedExtensions -v jpg,ext1,ext2,ext3
We are also processing the companion files with the same name of your main photo files. The default settings is currently only mov which is popular among iPhone Live Photos output is also exporting with a video file next to your photo.
You can extend this companion extension by using settings verb's set action.
settings -k CompanionExtensions -v mov,ext1,ext2,ext3
If you use only photo taken date and not interested in building address from reverse geocode, you can skip reading this section. But if you want to use address (reverse geocode) in file and/or folder naming, you should read the following sections and must learn the details.
To build addresses we need a reserve geocode provider. Currently, there is four reverse geocode provider is supported.
- BigDataCloud
- Open Street Map Foundation - Nominatim
- Google Maps
- LocationIq
| Reverse Geocode Provider | API Key Required | Free Tier | Free Count Limit | Free Rate Limit (free) | Map Data Owner |
|---|---|---|---|---|---|
| BigDataCloud | Yes | Yes - | 50.000 req/month | Big Data Cloud | |
| Open Street Map Foundation - Nominatim | No | - | - | 1req/sec | Open Street Map Foundation |
| GoogleMaps | Yes | No | - | - | |
| LocationIq | Yes | Yes | 5.000 req/day | 1req/sec | Open Street Map Foundation |
After selecting reverse geocode provider, you need to provide an API key. There are three ways to provide this API key;
- Send as an argument every time
- Use persist a setting to save it as configuration, so you don't need to submit everytime.
- Use environment variable, so you don't need to submit everytime.
| Reverse Geocode Provider | Settings Key | Environment Variable | Argument |
|---|---|---|---|
| BigDataCloud | BigDataCloudApiKey |
PHOTO_CLI_BIG_DATA_CLOUD_API_KEY |
-b or --bigdatacloud-key |
| Open Street Map Foundation - Nominatim | - | - | - |
| GoogleMaps | GoogleMapsApiKey |
PHOTO_CLI_GOOGLE_MAPS_API_KEY |
-k or --googlemaps-key |
| LocationIq | LocationIqApiKey |
PHOTO_CLI_LOCATIONIQ_API_KEY |
-q or --locationiq-key |
Every reverse geocode provider has its data and they also represent it very differently. The information returned from reverse geocode provider is different or may differ in the level of detail. As there is no way to generalize every reverse geocode provider's response into the same address administrative level, users must understand the response returned from their selected reverse geocode provider.
There is two way to understand the reverse geocoding response.
photo-cli has a feature to extract and list the response of each reverse geocode provider. If you are using a reverse geocode provider that needs an API key, first you need to get it from the provider and set API key.
Listing Reverse Geocode Response. Ref: reverse geocode provider command line arguments
photo-cli address --input [input-file.jpg] --reverse-geocode [selected-reverse-geocode-provider]`
For example, a photo was taken on Anıtkabir(place), Çankaya(town), Ankara(city), Turkey(country) with coordinate as 39.925054 and longitude
as 32.8347552 (Coordinate in Google Maps) responses should be like the following.
photo-cli address -i DSC_7082.jpg -e 1
AdminLevel2: Turkey
AdminLevel3: Central Anatolia Region
AdminLevel4: Ankara Province
AdminLevel6: Çankaya
AdminLevel8: Mebusevleri Mahallesi
photo-cli address -i DSC_7082.jpg -e 2
CountryCode: tr
Country: Türkiye
Region: İç Anadolu Bölgesi
Province: Ankara
City: Ankara
Town: Çankaya
Postcode: 06430
Suburb: Yücetepe Mahallesi
Road: İlk Sokak
Military: Anıtkabir
photo-cli address -i DSC_7082.jpg -e 3
plus_code: WRGM+2W
administrative_area_level_2: Çankaya
administrative_area_level_1: Ankara
country: Turkey
route: Anıtkabir
administrative_area_level_4: Mebusevleri
postal_code: 06570
street_number: 108
photo-cli address -i DSC_7082.jpg -e 4
CountryCode: tr
Country: Türkiye
City: Ankara
Postcode: 06580
Suburb: Mebusevleri Mahallesi
photo-cli address -i DSC_7082.jpg -e 5
CountryCode: tr
Country: Turkey
Region: Central Anatolia Region
Province: Ankara
City: Ankara
Town: Çankaya
Postcode: 06570
Suburb: Yücetepe Mahallesi
Road: Ata Sokak
Barracks: Anıtkabir
You should inspect the reverse geocode provider's response with the locations you occasionally take photographs. After getting the response, you can send the properties as an argument of your choice of the administrative level you want to build an address for each photograph.
To trigger HTTP files you may use Visual Studio Code with the extension REST Client or any IntelliJ IDE's.
You may replace {{ApiKey}} in the address with your API key or you can use the IntelliJ environment variable file.
| Reverse Geocode Provider | HTTP File |
|---|---|
| BigDataCloud | big-data-cloud.http |
| Open Street Map Foundation - Nominatim | open-street-map.http |
| GoogleMaps | google-maps.http |
| LocationIq | location-iq.http |
You may import photo-cli | Reverse Geocode collection into Postman.
You should prepare the following environment variables on Postman.
| Reverse Geocode Provider | Postman Environment Variable |
|---|---|
| BigDataCloud | BigDataCloud-ApiKey |
| Open Street Map Foundation - Nominatim | - |
| GoogleMaps | GoogleMaps-ApiKey |
| LocationIq | LocationIq-ApiKey |
Sample responses in JSON format are listed below from each reverse geocode for the coordinate with latitude as 39.925054 and longitude as 32.8347552 (Coordinate in Google Maps).
| Reverse Geocode Provider | Sample Response |
|---|---|
| BigDataCloud | big-data-cloud.json |
| Open Street Map Foundation - Nominatim | open-street-map.json |
| GoogleMaps | google-maps.json |
| LocationIq | location-iq.json |
Every reverse geocode provider has its address building parameters. With address command you can inspect any photograph's reverse geocode response. These different levels of selected address properties will be used in exported into CSV file for info command or used as file and/or folder names depending on your naming strategies for copy command.
| Reverse Geocode Provider | Address Building Parameters |
|---|---|
| BigDataCloud | BigDataCloud Properties |
| Open Street Map Foundation - Nominatim | OpenStreet Properties |
| GoogleMaps | GoogleMaps Properties |
| LocationIq | OpenStreet Properties |
Getting a sample reverse geocoding response with all properties listed.
photo-cli address -i DSC_7082.jpg -e 1
AdminLevel2: Turkey
AdminLevel3: Central Anatolia Region
AdminLevel4: Ankara Province
AdminLevel6: Çankaya
AdminLevel8: Mebusevleri Mahallesi
If we want to build an address like with levels only contains Turkey, Ankara Province, and Çankaya, we should use levels 2,4,6. To verify our address is building correctly, you may use type parameter as SelectedProperties and bigdatacloud-levels arguments separated with space like the following example.
photo-cli address --input DSC_7082.jpg --reverse-geocode BigDataCloud --type SelectedProperties --bigdatacloud-levels 2 4 6
Turkey
Ankara Province
Çankaya
Getting a sample reverse geocoding response with all properties listed.
photo-cli address -i DSC_7082.jpg -e 2
CountryCode: tr
Country: Türkiye
Region: İç Anadolu Bölgesi
Province: Ankara
City: Ankara
Town: Çankaya
Postcode: 06430
Suburb: Yücetepe Mahallesi
Road: İlk Sokak
Military: Anıtkabir
If we want to build an address like with levels only contains tr, 06430, and Yücetepe Mahallesi, we should use properties CountryCode, Postcode, Suburb. To verify our address is building correctly, you may use type as SelectedProperties and openstreetmap-properties arguments separated with space like the following example.
photo-cli address --input DSC_7082.jpg --reverse-geocode OpenStreetMapFoundation --type SelectedProperties --openstreetmap-properties CountryCode Postcode Suburb
tr
06430
Yücetepe Mahallesi
Getting a sample reverse geocoding response with all properties listed.
photo-cli address -i DSC_7082.jpg -e GoogleMaps
plus_code: WRGM+2W
administrative_area_level_2: Çankaya
administrative_area_level_1: Ankara
country: Turkey
route: Anıtkabir
administrative_area_level_4: Mebusevleri
postal_code: 06570
street_number: 108
If we want to build an address like with levels only contains Mebusevleri, 108, and Anıtkabir, we should use properties administrative_area_level_4, street_number, route. To verify our address is building correctly, you may use type as SelectedProperties and googlemaps-types arguments separated with space like the following example.
photo-cli address --input DSC_7082.jpg --reverse-geocode GoogleMaps --type SelectedProperties --googlemaps-types administrative_area_level_4 street_number route
Mebusevleri
108
Anıtkabir
After selecting our properties specialized by our selected third-party reverse geocode provider, we can use our address in file and folder names. To merge address levels, - character is used as default.
Example merged address may used in file/folder names: Turkey-Ankara-Çankaya-Mebusevleri-Anıtkabir
You may change default separator (-) via settings command with a setting key AddressSeparator
Since the responses in close coordinate's requests in pretty close results, we implemented a caching mechanism for optimization. This is done by rounding the fractional digits of coordinates from the end. We are currently only use 4 fraction digits.
For example the original coordinate for 39.92501234567890, 32.83471234567890 will interpreted as 39.9250, 32.8347 internally before sending the request.
If you need more precise results in your reverse geocode responses, you can increase this value on settings with a key of CoordinatePrecision.
We can't cover all possible options, because there are so many option combination. Some important copy command examples with comparing of original photos directory structure and output directory of photo-cli listed below.
Preserve same folder hierarchy, copy photos with sequential number ordering by photo taken date.
photo-cli copy --process-type SubFoldersPreserveFolderHierarchy --naming-style Numeric --number-style PaddingZeroCharacter --input photos --output organized-albums
Click to expand
| Original Folder Hierarchy | After photo-cli |
|---|---|
├── DSC_5727.jpg
├── GOPR6742.jpg
├── Italy album
│ ├── DJI_01732.jpg
│ ├── DJI_01733.jpg
│ ├── DSC00001.JPG
│ ├── DSC03467.jpg
│ ├── DSC_1769.JPG
│ ├── DSC_1770.JPG
│ ├── DSC_1770_(same).jpg
│ ├── DSC_1771.JPG
│ ├── GOPR7496.jpg
│ ├── GOPR7497.jpg
│ ├── IMG_0747.JPG
│ └── IMG_2371.jpg
└── Spain Journey
├── DSC_1807.jpg
├── DSC_1808.jpg
└── IMG_5397.jpg
|
├── 1.jpg
├── 2.jpg
├── Italy album
│ ├── 01.jpg
│ ├── 02.jpg
│ ├── 03.jpg
│ ├── 04.jpg
│ ├── 05.jpg
│ ├── 06.jpg
│ ├── 07.jpg
│ ├── 08.jpg
│ ├── 09.jpg
│ ├── 10.jpg
│ ├── 11.jpg
│ └── 12.jpg
├── photo-cli-report.csv
└── Spain Journey
├── 1.jpg
├── 2.jpg
└── 3.jpg
|
Groups photos by photo taken year, month, day than copy on [year]/[month]/[day] directory with a file name as photo taken date.
photo-cli copy --process-type FlattenAllSubFolders --group-by YearMonthDay --naming-style DateTimeWithSeconds --number-style OnlySequentialNumbers --input photos --output organized-albums
Click to expand
| Original Folder Hierarchy | After photo-cli |
|---|---|
├── DSC_5727.jpg
├── GOPR6742.jpg
├── Italy album
│ ├── DJI_01732.jpg
│ ├── DJI_01733.jpg
│ ├── DSC00001.JPG
│ ├── DSC03467.jpg
│ ├── DSC_1769.JPG
│ ├── DSC_1770.JPG
│ ├── DSC_1770_(same).jpg
│ ├── DSC_1771.JPG
│ ├── GOPR7496.jpg
│ ├── GOPR7497.jpg
│ ├── IMG_0747.JPG
│ └── IMG_2371.jpg
└── Spain Journey
├── DSC_1807.jpg
├── DSC_1808.jpg
└── IMG_5397.jpg
|
├── 2005 │ ├── 08 │ │ └── 13 │ │ └── 2005.08.13_09.47.23.jpg │ └── 12 │ └── 14 │ └── 2005.12.14_14.39.47.jpg ├── 2008 │ ├── 07 │ │ └── 16 │ │ └── 2008.07.16_11.33.20.jpg │ └── 10 │ └── 22 │ ├── 2008.10.22_16.28.39.jpg │ ├── 2008.10.22_16.29.49.jpg │ ├── 2008.10.22_16.38.20.jpg │ ├── 2008.10.22_16.43.21.jpg │ ├── 2008.10.22_16.44.01.jpg │ ├── 2008.10.22_16.46.53.jpg │ ├── 2008.10.22_16.52.15.jpg │ ├── 2008.10.22_16.55.37.jpg │ ├── 2008.10.22_17.00.07-1.jpg │ └── 2008.10.22_17.00.07-2.jpg ├── 2012 │ └── 06 │ └── 22 │ └── 2012.06.22_19.52.31.jpg ├── 2015 │ └── 04 │ └── 10 │ ├── 2015.04.10_20.12.23-1.jpg │ └── 2015.04.10_20.12.23-2.jpg ├── IMG_5397.jpg └── photo-cli-report.csv |
Adding day range as a prefix to existing folder names and photos copied with a file name as address and day.
photo-cli copy --process-type SubFoldersPreserveFolderHierarchy --folder-append DayRange --folder-append-location Prefix --naming-style AddressDay --reverse-geocode OpenStreetMapFoundation --openstreetmap-properties country city town suburb --number-style AllNamesAreSameLength --input photos --output organized-albums
Click to expand
| Original Folder Hierarchy | After photo-cli |
|---|---|
├── DSC_5727.jpg
├── GOPR6742.jpg
├── Italy album
│ ├── DJI_01732.jpg
│ ├── DJI_01733.jpg
│ ├── DSC00001.JPG
│ ├── DSC03467.jpg
│ ├── DSC_1769.JPG
│ ├── DSC_1770.JPG
│ ├── DSC_1770_(same).jpg
│ ├── DSC_1771.JPG
│ ├── GOPR7496.jpg
│ ├── GOPR7497.jpg
│ ├── IMG_0747.JPG
│ └── IMG_2371.jpg
└── Spain Journey
├── DSC_1807.jpg
├── DSC_1808.jpg
└── IMG_5397.jpg
|
├── 2005.12.14-2008.10.22-Italy album │ ├── IMG_2371.jpg │ ├── Italia-Arezzo-2008.10.22-10.jpg │ ├── Italia-Arezzo-2008.10.22-11.jpg │ ├── Italia-Arezzo-2008.10.22-12.jpg │ ├── Italia-Arezzo-2008.10.22-13.jpg │ ├── Italia-Arezzo-2008.10.22-14.jpg │ ├── Italia-Arezzo-2008.10.22-15.jpg │ ├── Italia-Arezzo-2008.10.22-16.jpg │ ├── Italia-Arezzo-2008.10.22-17.jpg │ ├── Italia-Arezzo-2008.10.22-18.jpg │ ├── Italia-Arezzo-2008.10.22-19.jpg │ └── Italia-Firenze-Quartiere 1-2005.12.14.jpg ├── 2015.04.10-2015.04.10-Spain Journey │ ├── España-Madrid-2015.04.10-1.jpg │ ├── España-Madrid-2015.04.10-2.jpg │ └── IMG_5397.jpg ├── Kenya-2005.08.13.jpg ├── photo-cli-report.csv └── United Kingdom-Ascot-Sunninghill and Ascot-2012.06.22.jpg |
Preserve same folder hierarchy, copy photos with a file name as photo taken date, time and address. Possible file name will have number suffix. Photos that don't have any coordinate or photo taken date will be copied in a relative subfolder.
photo-cli copy --process-type SubFoldersPreserveFolderHierarchy --naming-style AddressDateTimeWithSeconds --reverse-geocode OpenStreetMapFoundation --openstreetmap-properties country city town suburb --number-style AllNamesAreSameLength --no-taken-date InSubFolder --no-coordinate InSubFolder --input photos --output organized-albums
Click to expand
| Original Folder Hierarchy | After photo-cli |
|---|---|
├── DSC_5727.jpg
├── GOPR6742.jpg
├── Italy album
│ ├── DJI_01732.jpg
│ ├── DJI_01733.jpg
│ ├── DSC00001.JPG
│ ├── DSC03467.jpg
│ ├── DSC_1769.JPG
│ ├── DSC_1770.JPG
│ ├── DSC_1770_(same).jpg
│ ├── DSC_1771.JPG
│ ├── GOPR7496.jpg
│ ├── GOPR7497.jpg
│ ├── IMG_0747.JPG
│ └── IMG_2371.jpg
└── Spain Journey
├── DSC_1807.jpg
├── DSC_1808.jpg
└── IMG_5397.jpg
|
├── Italy album │ ├── Italia-Arezzo-2008.10.22_16.28.39.jpg │ ├── Italia-Arezzo-2008.10.22_16.29.49.jpg │ ├── Italia-Arezzo-2008.10.22_16.38.20.jpg │ ├── Italia-Arezzo-2008.10.22_16.43.21.jpg │ ├── Italia-Arezzo-2008.10.22_16.44.01.jpg │ ├── Italia-Arezzo-2008.10.22_16.46.53.jpg │ ├── Italia-Arezzo-2008.10.22_16.52.15.jpg │ ├── Italia-Arezzo-2008.10.22_16.55.37.jpg │ ├── Italia-Arezzo-2008.10.22_17.00.07-1.jpg │ ├── Italia-Arezzo-2008.10.22_17.00.07-2.jpg │ ├── Italia-Firenze-Quartiere 1-2005.12.14_14.39.47.jpg │ └── no-address │ └── IMG_2371.jpg ├── Kenya-2005.08.13_09.47.23.jpg ├── photo-cli-report.csv ├── Spain Journey │ ├── España-Madrid-2015.04.10_20.12.23-1.jpg │ ├── España-Madrid-2015.04.10_20.12.23-2.jpg │ └── no-address-and-no-photo-taken-date │ └── IMG_5397.jpg └── United Kingdom-Ascot-Sunninghill and Ascot-2012.06.22_19.52.31.jpg |
Groups photos by address hierarchy than copy on [country]/[city]/[town] directory with a file name as photo taken date. Photos that don't have any coordinate will be copied in a relative subfolder.
photo-cli copy --process-type FlattenAllSubFolders --group-by AddressHierarchy --naming-style DayAddress --reverse-geocode OpenStreetMapFoundation --openstreetmap-properties country city town suburb --number-style OnlySequentialNumbers --no-taken-date AppendToEndOrderByFileName --no-coordinate InSubFolder --input photos --output organized-albums
Click to expand
| Original Folder Hierarchy | After photo-cli |
|---|---|
├── DSC_5727.jpg
├── GOPR6742.jpg
├── Italy album
│ ├── DJI_01732.jpg
│ ├── DJI_01733.jpg
│ ├── DSC00001.JPG
│ ├── DSC03467.jpg
│ ├── DSC_1769.JPG
│ ├── DSC_1770.JPG
│ ├── DSC_1770_(same).jpg
│ ├── DSC_1771.JPG
│ ├── GOPR7496.jpg
│ ├── GOPR7497.jpg
│ ├── IMG_0747.JPG
│ └── IMG_2371.jpg
└── Spain Journey
├── DSC_1807.jpg
├── DSC_1808.jpg
└── IMG_5397.jpg
|
├── España
│ └── Madrid
│ ├── 2015.04.10-España-Madrid-1.jpg
│ └── 2015.04.10-España-Madrid-2.jpg
├── Italia
│ ├── Arezzo
│ │ ├── 2008.10.22-Italia-Arezzo-10.jpg
│ │ ├── 2008.10.22-Italia-Arezzo-1.jpg
│ │ ├── 2008.10.22-Italia-Arezzo-2.jpg
│ │ ├── 2008.10.22-Italia-Arezzo-3.jpg
│ │ ├── 2008.10.22-Italia-Arezzo-4.jpg
│ │ ├── 2008.10.22-Italia-Arezzo-5.jpg
│ │ ├── 2008.10.22-Italia-Arezzo-6.jpg
│ │ ├── 2008.10.22-Italia-Arezzo-7.jpg
│ │ ├── 2008.10.22-Italia-Arezzo-8.jpg
│ │ └── 2008.10.22-Italia-Arezzo-9.jpg
│ └── Firenze
│ └── Quartiere 1
│ └── 2005.12.14-Italia-Firenze-Quartiere 1.jpg
├── Kenya
│ └── 2005.08.13-Kenya.jpg
├── no-address
│ ├── IMG_2371.jpg
│ └── IMG_5397.jpg
├── photo-cli-report.csv
└── United Kingdom
└── Ascot
└── Sunninghill and Ascot
└── 2012.06.22-United Kingdom-Ascot-Sunninghill and Ascot.jpg
|
| Subcommand | description |
|---|---|
archive |
Archives photos into same specific folder, optionally groups them by albums (date range, reverse geocode or individual), and indexes photo taken date, address (reverse geocode) information into SQLite database. |
copy |
Copies photos into new folder hierarchy with given arguments using photograph's taken date and coordinate address (reverse geocode). |
list |
List & open photos from archive folders. |
info |
Creates a report (CSV file) listing all photo taken date and address (reverse geocode). |
address |
Get address (reverse geocode) of single photo. |
settings |
Lists, saves and get settings. |
Archives photos into same specific folder, optionally groups them by albums (date range, reverse geocode or individual), and indexes photo taken date, address (reverse geocode) information into SQLite database.
photo-cli help archive
Click to expand
-o, --output (MUST) File system path to create new
organized folder. A new folder hierarchy
will be created on that location with new
file names. (will create folder if not
exist)
-i, --input (Default current executing folder) File
system path to read & copy photos from. (
there will be no modification on the input
path )
-d, --dry-run (Optional) Simulate the same process
without writing to the output folder. (no
extra parameter needed)
-x, --invalid-format (Optional) Action to do when a photo
format is invalid. ( Continue: 0
[default], PreventProcess: 1 )
-t, --no-taken-date (Optional) Action to do when a photo with
a no taken date. ( Continue: 0 [default],
PreventProcess: 1 )
-c, --no-coordinate (Optional) Action to do when a photo with
a no coordinate. ( Continue: 0 [default],
PreventProcess: 1 )
-w, --expected-day-range (Optional) Provide a maximum expected day
difference as number for your photos to
prevent processing if it's exceeding the
range
-y, --album-type (Optional) Whether you want to link photos
as album by picking the album type( Unset:
0 [default - no album linking], Individual
= 1, DateRange = 2 )
-a, --album-name (Optional) Album name to create a new one
for currently archiving photos[Can use
with `AlbumType` as `Individual` or
`DateRange`]
-p, --update-album (Optional) Existing Album ID number value
to link currently archiving photos[Can use
with `AlbumType` as `Individual` or
`DateRange`][Album IDs can be listed by
`photo-cli list --type Albums`]
-s, --auto-reverse-geocode-album (Optional) Automatically linking photos to
an album (creating or using existing) for
each reverse geocode property
individuallyFor example if you use reverse
geocode properties are country, city each
archive operation, you could have albums
for each country and city variants
-f, --delete-on-source (Optional) [Dangerous parameter] Deleting
the source folder on successful archive
operation
-e, --reverse-geocode (Optional) Third-party provider to resolve
photo taken address by photo's
coordinates. ( Disabled: 0 [default],
BigDataCloud: 1, OpenStreetMapFoundation:
2, GoogleMaps: 3, LocationIq: 5 )
-b, --bigdatacloud-key (Optional) API key needed to use
BigDataCloud.
https://www.bigdatacloud.com/geocoding-api
s/reverse-geocode-to-city-api/ (Instead of
using this option, environment name:
PHOTO_CLI_BIG_DATA_CLOUD_API_KEY can be
used or `BigDataCloudApiKey` key can be
set via settings command. )
-u, --bigdatacloud-levels (Optional) Admin levels separated with
space. ( To see which level correspond to
which address level, you may use
`photo-cli address` to see the full
response returned from BigDataCloud. )
-m, --googlemaps-types (Optional) GoogleMaps address types
separated with space. ( To see which level
correspond to which address level, you may
use `photo-cli address` to see the full
response returned from GoogleMaps. )
-k, --googlemaps-key (Optional) API key needed to use
GoogleMaps.
https://developers.google.com/maps/documen
tation/geocoding/overview/ (Instead of
using this option, environment name:
PHOTO_CLI_GOOGLE_MAPS_API_KEY can be used
or `GoogleMapsApiKey` key can be set via
settings command. )
-r, --openstreetmap-properties (Optional) OpenStreetMap properties
separated with space. ( To see which level
correspond to which address level, you may
use `photo-cli address` to see the full
response returned from OpenStreetMap
provider. )
-q, --locationiq-key (Optional) API key needed to use
LocationIq. https://locationiq.com/docs/
(Instead of using this option, environment
name: PHOTO_CLI_LOCATIONIQ_API_KEY can be
used or `LocationIqApiKey` key can be set
via settings command. )
-h, --has-paid-license (Optional) Bypass the free rate limit if
you have paid license. ( For LocationIq. )
-l, --language (Optional) Language/culture value to get
localized address result for BigDataCloud
(
https://www.bigdatacloud.com/supported-lan
guages/ ) and GoogleMaps
(https://developers.google.com/maps/faq#la
nguagesupport ).
-z, --missing-reverse-geocode (Optional) Action to do when any of the
photo has missing reserver geocode
information( Continue: 0 [default],
PreventProcess: 1 )
--help Display this help screen.
--version Display version information.
NOTES:
- Instead of option names (for ex: DateTimeWithMinutes), you may use options
values too. (for ex: 3)
- You can use relative folder paths. If you use the input folder as the working
directory, you don't need to use the input argument.
EXAMPLE USAGES:
- Archive all photos in current folder (and it's subfolders recursively) into
output folder by (year)/(month)/(day) hierarchy with a file name photo taken
date with seconds prefixed by file hash. Saves all photo taken information into
local SQLite database.
Example with long argument names;
photo-cli archive --output (output-folder)
Example with short argument names;
photo-cli archive -o (output-folder)
- Archive all photos in the input folder and its subfolders recursively by
fetching each photo's reverse geocode information, copying them into the output
folder organized by year/month/day hierarchy with filenames consisting of the
photo-taken date with seconds prefixed by file hash, and saving all photo
metadata and addresses into a local SQLite database.
Example with long argument names;
photo-cli archive --no-coordinate PreventProcess --reverse-geocode
OpenStreetMapFoundation --input (input-folder) --output (output-folder)
--openstreetmap-properties country city town suburb --no-taken-date
PreventProcess --invalid-format PreventProcess --album-type Unset
Example with short argument names;
photo-cli archive -c PreventProcess -e OpenStreetMapFoundation -i (input-folder)
-o (output-folder) -r country city town suburb -t PreventProcess -x
PreventProcess -y Unset
- Archive all photos in the input folder (and its subfolders recursively) by
creating a date range album named 'album 1', fetching each photo's reverse
geocode information using Google Maps with properties administrative area level
1 and 2 to create location-based albums, and copying them into the output folder
with a (year)/(month)/(day) hierarchy using filenames formatted as
photo-taken-date-with-seconds prefixed by file hash but the process won't start
if any photo has an invalid format, missing photo-taken date or coordinates, or
if the photo-taken date span exceeds 30 days and upon successful operation, it
would delete the input source folder.
Example with long argument names;
photo-cli archive --album-name "album 1" --no-coordinate PreventProcess
--reverse-geocode GoogleMaps --delete-on-source --input (input-folder)
--googlemaps-types administrative_area_level_1 administrative_area_level_2
--output (output-folder) --auto-reverse-geocode-album --no-taken-date
PreventProcess --expected-day-range 30 --invalid-format PreventProcess
--album-type DateRange --missing-reverse-geocode PreventProcess
Example with short argument names;
photo-cli archive -a "album 1" -c PreventProcess -e GoogleMaps -f -i
(input-folder) -m administrative_area_level_1 administrative_area_level_2 -o
(output-folder) -s -t PreventProcess -w 30 -x PreventProcess -y DateRange -z
PreventProcess
Copies photos into new folder hierarchy with given arguments using photograph's taken date and coordinate address (reverse geocode).
photo-cli help copy
Click to expand
-o, --output (MUST) File system path to create new
organized folder. A new folder hierarchy
will be created on that location with new
file names. (will create folder if not
exist)
-s, --naming-style (MUST) Naming strategy of newly copied file
name. ( Numeric: 1, Day: 2,
DateTimeWithMinutes: 3, DateTimeWithSeconds:
4, Address: 5, DayAddress: 6,
DateTimeWithMinutesAddress: 7,
DateTimeWithSecondsAddress: 8, AddressDay:
9, AddressDateTimeWithMinutes: 10,
AddressDateTimeWithSeconds: 11 )
-f, --process-type (MUST) Reading photos strategy from input
folder. ( Single: 1,
SubFoldersPreserveFolderHierarchy: 2,
FlattenAllSubFolders: 3 )
-n, --number-style (MUST) Number naming strategy when using
`NamingStyle` as `Numeric` or using to
numbering the possible same names. (
AllNamesAreSameLength: 1,
PaddingZeroCharacter: 2,
OnlySequentialNumbers: 3 )
-x, --invalid-format (Optional) Action to do when a photo format
is invalid. ( Continue: 0 [default],
PreventProcess: 1, DontCopyToOutput: 2,
InSubFolder: 3 )
-t, --no-taken-date (Optional) Action to do when a photo with a
no taken date. ( Continue: 0 [default],
PreventProcess: 1, DontCopyToOutput: 2,
InSubFolder: 3, AppendToEndOrderByFileName:
4, InsertToBeginningOrderByFileName: 5 )
-c, --no-coordinate (Optional) Action to do when a photo with a
no coordinate. ( Continue: 0 [default],
PreventProcess: 1, DontCopyToOutput: 2,
InSubFolder: 3 )
-i, --input (Default current executing folder) File
system path to read & copy photos from. (
there will be no modification on the input
path )
-d, --dry-run (Optional) Simulate the same process without
writing to the output folder. (no extra
parameter needed)
-g, --group-by (Optional) Strategy to group photos into
folders. [Can't use with `FolderProcessType`
is `SubFoldersPreserveFolderHierarchy`] (
YearMonthDay: 1, YearMonth: 2, Year: 3,
AddressFlat: 4, AddressHierarchy: 5 )
-a, --folder-append (Optional) Appending name strategy to folder
names cloned from source folder hierarchy.
[Can use with `FolderProcessType` as
`SubFoldersPreserveFolderHierarchy`] (
FirstYearMonthDay: 1, FirstYearMonth: 2,
FirstYear: 3, DayRange: 4,
MatchingMinimumAddress: 5 )
-p, --folder-append-location (Optional) Append location for
`FolderAppendType`. [Can use with
`FolderProcessType` as
`SubFoldersPreserveFolderHierarchy`] (
Prefix: 1, Suffix: 2 )
-v, --verify (Optional) Verify that all photo files
copied successfully by comparing file
hashes. (no extra parameter needed)
-w, --expected-day-range (Optional) Provide a maximum expected day
difference as number for your photos to
prevent processing if it's exceeding the
range
-e, --reverse-geocode (Optional) Third-party provider to resolve
photo taken address by photo's coordinates.
( Disabled: 0 [default], BigDataCloud: 1,
OpenStreetMapFoundation: 2, GoogleMaps: 3,
LocationIq: 5 )
-b, --bigdatacloud-key (Optional) API key needed to use
BigDataCloud.
https://www.bigdatacloud.com/geocoding-apis/
reverse-geocode-to-city-api/ (Instead of
using this option, environment name:
PHOTO_CLI_BIG_DATA_CLOUD_API_KEY can be used
or `BigDataCloudApiKey` key can be set via
settings command. )
-u, --bigdatacloud-levels (Optional) Admin levels separated with
space. ( To see which level correspond to
which address level, you may use `photo-cli
address` to see the full response returned
from BigDataCloud. )
-m, --googlemaps-types (Optional) GoogleMaps address types
separated with space. ( To see which level
correspond to which address level, you may
use `photo-cli address` to see the full
response returned from GoogleMaps. )
-k, --googlemaps-key (Optional) API key needed to use GoogleMaps.
https://developers.google.com/maps/documenta
tion/geocoding/overview/ (Instead of using
this option, environment name:
PHOTO_CLI_GOOGLE_MAPS_API_KEY can be used or
`GoogleMapsApiKey` key can be set via
settings command. )
-r, --openstreetmap-properties (Optional) OpenStreetMap properties
separated with space. ( To see which level
correspond to which address level, you may
use `photo-cli address` to see the full
response returned from OpenStreetMap
provider. )
-q, --locationiq-key (Optional) API key needed to use LocationIq.
https://locationiq.com/docs/ (Instead of
using this option, environment name:
PHOTO_CLI_LOCATIONIQ_API_KEY can be used or
`LocationIqApiKey` key can be set via
settings command. )
-h, --has-paid-license (Optional) Bypass the free rate limit if you
have paid license. ( For LocationIq. )
-l, --language (Optional) Language/culture value to get
localized address result for BigDataCloud (
https://www.bigdatacloud.com/supported-langu
ages/ ) and GoogleMaps
(https://developers.google.com/maps/faq#lang
uagesupport ).
-z, --missing-reverse-geocode (Optional) Action to do when any of the
photo has missing reserver geocode
information( Continue: 0 [default],
PreventProcess: 1 )
--help Display this help screen.
--version Display version information.
NOTES:
- Instead of option names (for ex: DateTimeWithMinutes), you may use options
values too. (for ex: 3)
- You can use relative folder paths. If you use the input folder as the working
directory, you don't need to use the input argument.
EXAMPLE USAGES:
- Preserve same folder hierarchy, copy photos with sequential number ordering by
photo taken date.
Example with long argument names;
photo-cli copy --process-type SubFoldersPreserveFolderHierarchy --input
(input-folder) --number-style PaddingZeroCharacter --output (output-folder)
--naming-style Numeric
Example with short argument names;
photo-cli copy -f SubFoldersPreserveFolderHierarchy -i (input-folder) -n
PaddingZeroCharacter -o (output-folder) -s Numeric
- Groups photos by photo taken year, month, day than copy on
(year)/(month)/(day) directory with a file name as photo taken date.
Example with long argument names;
photo-cli copy --process-type FlattenAllSubFolders --group-by YearMonthDay
--input (input-folder) --number-style OnlySequentialNumbers --output
(output-folder) --naming-style DateTimeWithSeconds
Example with short argument names;
photo-cli copy -f FlattenAllSubFolders -g YearMonthDay -i (input-folder) -n
OnlySequentialNumbers -o (output-folder) -s DateTimeWithSeconds
- Adding day range as a prefix to existing folder names and photos copied with a
file name as address and day.
Example with long argument names;
photo-cli copy --folder-append DayRange --no-coordinate InSubFolder
--reverse-geocode GoogleMaps --process-type SubFoldersPreserveFolderHierarchy
--input (input-folder) --googlemaps-key google-api-key --googlemaps-types
administrative_area_level_1 administrative_area_level_2
administrative_area_level_3 --number-style AllNamesAreSameLength --output
(output-folder) --folder-append-location Prefix --naming-style AddressDay
--no-taken-date InSubFolder --invalid-format PreventProcess
Example with short argument names;
photo-cli copy -a DayRange -c InSubFolder -e GoogleMaps -f
SubFoldersPreserveFolderHierarchy -i (input-folder) -k google-api-key -m
administrative_area_level_1 administrative_area_level_2
administrative_area_level_3 -n AllNamesAreSameLength -o (output-folder) -p
Prefix -s AddressDay -t InSubFolder -x PreventProcess
- Preserve the same folder hierarchy while copying photos with filenames
consisting of the photo-taken date, time, and address (with possible number
suffixes), and copy photos without coordinates or photo-taken dates into a
relative subfolder.
Example with long argument names;
photo-cli copy --no-coordinate InSubFolder --reverse-geocode
OpenStreetMapFoundation --process-type SubFoldersPreserveFolderHierarchy --input
(input-folder) --number-style AllNamesAreSameLength --output (output-folder)
--openstreetmap-properties country city town suburb --naming-style
AddressDateTimeWithSeconds --no-taken-date InSubFolder --invalid-format
PreventProcess
Example with short argument names;
photo-cli copy -c InSubFolder -e OpenStreetMapFoundation -f
SubFoldersPreserveFolderHierarchy -i (input-folder) -n AllNamesAreSameLength -o
(output-folder) -r country city town suburb -s AddressDateTimeWithSeconds -t
InSubFolder -x PreventProcess
- Groups photos by photo-taken year, month, and day, then copies them into a
year/month/day directory structure with filenames as the photo-taken date, while
photos without coordinates are copied into a relative subfolder.
Example with long argument names;
photo-cli copy --no-coordinate InSubFolder --reverse-geocode BigDataCloud
--process-type FlattenAllSubFolders --group-by AddressHierarchy --input
(input-folder) --number-style OnlySequentialNumbers --output (output-folder)
--naming-style DayAddress --bigdatacloud-levels 2 4 6 8 --invalid-format
PreventProcess
Example with short argument names;
photo-cli copy -c InSubFolder -e BigDataCloud -f FlattenAllSubFolders -g
AddressHierarchy -i (input-folder) -n OnlySequentialNumbers -o (output-folder)
-s DayAddress -u 2 4 6 8 -x PreventProcess
List & open photos from archive folders.
photo-cli help list
Click to expand
-t, --type (Optional) Listing type for archive folder( Summary: 0
[default], Albums: 1, PhotosByAlbum: 2, PhotosByDate: 3 )
-i, --input (Default current executing folder) Archive path to list & open
photos from
-n, --id (Optional) Album ID to be used while using the type of
PhotosByAlbum
-y, --year (Optional) Year as number to be used while using the type of
PhotosByDate
-m, --month (Optional) Month as number to be used while using the type of
PhotosByDate
-d, --day (Optional) Day as number to be used while using the type of
PhotosByDate
-r, --raw (Optional) Listing photo paths each on new line instead of
trying to open the default OS app while using the type of
PhotosByAlbum or PhotosByDate
--help Display this help screen.
--version Display version information.
NOTES:
- Instead of option names (for ex: DateTimeWithMinutes), you may use options
values too. (for ex: 3)
- You can use relative folder paths. If you use the input folder as the working
directory, you don't need to use the input argument.
EXAMPLE USAGES:
- List statistics of the archive folder
Example with long argument names;
photo-cli list --input (input-folder)
Example with short argument names;
photo-cli list -i (input-folder)
- List all the album information of the archive folder
Example with long argument names;
photo-cli list --input (input-folder) --type Albums
Example with short argument names;
photo-cli list -i (input-folder) -t Albums
- List paths (to be send as process arguments to photo viewers) or open (only
supporting in macOS , Preview app for now) for the given album id
Example with long argument names;
photo-cli list --input (input-folder) --id 1 --type PhotosByAlbum
Example with short argument names;
photo-cli list -i (input-folder) -n 1 -t PhotosByAlbum
- List paths (to be send as process arguments to photo viewers) or open (only
supporting in macOS , Preview app for now) for the given year
Example with long argument names;
photo-cli list --input (input-folder) --type PhotosByDate --year 2007
Example with short argument names;
photo-cli list -i (input-folder) -t PhotosByDate -y 2007
- List paths (to be send as process arguments to photo viewers) or open (only
supporting in macOS , Preview app for now) for the given year & month
Example with long argument names;
photo-cli list --input (input-folder) --month 8 --type PhotosByDate --year 2007
Example with short argument names;
photo-cli list -i (input-folder) -m 8 -t PhotosByDate -y 2007
- List paths (to be send as process arguments to photo viewers) or open (only
supporting in macOS , Preview app for now) for the given year, month & day
Example with long argument names;
photo-cli list --day 19 --input (input-folder) --month 8 --type PhotosByDate
--year 2007
Example with short argument names;
photo-cli list -d 19 -i (input-folder) -m 8 -t PhotosByDate -y 2007
Creates a report (CSV file) listing all photo taken date and address (reverse geocode).
photo-cli help info
Click to expand
-o, --output (MUST) File system path to write report
file.
-i, --input (Default current executing folder) File
system path to read & copy photos from. (
there will be no modification on the input
path )
-a, --all-folders (Optional) Read & list all photos in all
subfolders (no extra parameter needed)
-x, --invalid-format (Optional) Action to do when a photo format
is invalid. ( Continue: 0 [default],
PreventProcess: 1 )
-t, --no-taken-date (Optional) Action to do when a photo with a
no taken date. ( Continue: 0 [default],
PreventProcess: 1 )
-c, --no-coordinate (Optional) Action to do when a photo with a
no coordinate. ( Continue: 0 [default],
PreventProcess: 1 )
-e, --reverse-geocode (Optional) Third-party provider to resolve
photo taken address by photo's coordinates.
( Disabled: 0 [default], BigDataCloud: 1,
OpenStreetMapFoundation: 2, GoogleMaps: 3,
LocationIq: 5 )
-b, --bigdatacloud-key (Optional) API key needed to use
BigDataCloud.
https://www.bigdatacloud.com/geocoding-apis/
reverse-geocode-to-city-api/ (Instead of
using this option, environment name:
PHOTO_CLI_BIG_DATA_CLOUD_API_KEY can be used
or `BigDataCloudApiKey` key can be set via
settings command. )
-u, --bigdatacloud-levels (Optional) Admin levels separated with
space. ( To see which level correspond to
which address level, you may use `photo-cli
address` to see the full response returned
from BigDataCloud. )
-m, --googlemaps-types (Optional) GoogleMaps address types
separated with space. ( To see which level
correspond to which address level, you may
use `photo-cli address` to see the full
response returned from GoogleMaps. )
-k, --googlemaps-key (Optional) API key needed to use GoogleMaps.
https://developers.google.com/maps/documenta
tion/geocoding/overview/ (Instead of using
this option, environment name:
PHOTO_CLI_GOOGLE_MAPS_API_KEY can be used or
`GoogleMapsApiKey` key can be set via
settings command. )
-r, --openstreetmap-properties (Optional) OpenStreetMap properties
separated with space. ( To see which level
correspond to which address level, you may
use `photo-cli address` to see the full
response returned from OpenStreetMap
provider. )
-q, --locationiq-key (Optional) API key needed to use LocationIq.
https://locationiq.com/docs/ (Instead of
using this option, environment name:
PHOTO_CLI_LOCATIONIQ_API_KEY can be used or
`LocationIqApiKey` key can be set via
settings command. )
-h, --has-paid-license (Optional) Bypass the free rate limit if you
have paid license. ( For LocationIq. )
-l, --language (Optional) Language/culture value to get
localized address result for BigDataCloud (
https://www.bigdatacloud.com/supported-langu
ages/ ) and GoogleMaps
(https://developers.google.com/maps/faq#lang
uagesupport ).
-z, --missing-reverse-geocode (Optional) Action to do when any of the
photo has missing reserver geocode
information( Continue: 0 [default],
PreventProcess: 1 )
--help Display this help screen.
--version Display version information.
NOTES:
- Instead of option names (for ex: DateTimeWithMinutes), you may use options
values too. (for ex: 3)
- You can use relative folder paths. If you use the input folder as the working
directory, you don't need to use the input argument.
EXAMPLE USAGES:
- Photos located on all subfolders will be processed and their photograph's
taken date and address information will be saved on CSV file using BigDataCloud
reverse geocode provider.
Example with long argument names;
photo-cli info --all-folders --reverse-geocode OpenStreetMapFoundation --input
(input-folder) --output (output-file).csv --openstreetmap-properties country
city town suburb
Example with short argument names;
photo-cli info -a -e OpenStreetMapFoundation -i (input-folder) -o
(output-file).csv -r country city town suburb
- Using Google Maps reverse geocode provider (need api key) with an option to
prevent processing if there is no coordinate or no photo taken date found on any
photo.
Example with long argument names;
photo-cli info --no-coordinate PreventProcess --reverse-geocode GoogleMaps
--input (input-folder) --googlemaps-key google-api-key --googlemaps-types
administrative_area_level_1 administrative_area_level_2 --output
(output-file).csv --no-taken-date PreventProcess
Example with short argument names;
photo-cli info -c PreventProcess -e GoogleMaps -i (input-folder) -k
google-api-key -m administrative_area_level_1 administrative_area_level_2 -o
(output-file).csv -t PreventProcess
Get address (reverse geocode) of single photo.
photo-cli help address
-i, --input (Default current executing folder) File system path to read & copy photos from. ( there will be no modification on the input path )
-e, --reverse-geocode (Optional) Third-party provider to resolve photo taken address by photo's coordinates. ( Disabled: 0 [default], BigDataCloud: 1, OpenStreetMapFoundation: 2, GoogleMaps: 3, LocationIq: 5 )
-t, --type (MUST) Response list detail level. ( AllAvailableProperties: 0, SelectedProperties: 1, FullResponse: 2 )
-b, --bigdatacloud-key (Optional) API key needed to use BigDataCloud. https://www.bigdatacloud.com/geocoding-apis/reverse-geocode-to-city-api/ (Instead of using this option, environment name: PHOTO_CLI_BIG_DATA_CLOUD_API_KEY can be used. )
-u, --bigdatacloud-levels (Optional) Admin levels separated with space. ( To see which level correspond to which address level, you may use `photo-cli address` to see full response returned from BigDataCloud. )
-m, --googlemaps-types (Optional) GoogleMaps address types separated with space. ( To see which level correspond to which address level, you may use `photo-cli address` to see full response returned from GoogleMaps. )
-k, --googlemaps-key (Optional) API key needed to use GoogleMaps. https://developers.google.com/maps/documentation/geocoding/overview/ (Instead of using this option, environment name: PHOTO_CLI_GOOGLE_MAPS_API_KEY can be used. )
-r, --openstreetmap-properties (Optional) OpenStreetMap properties separated with space. ( To see which level correspond to which address level, you may use `photo-cli address` to see full response returned from OpenStreetMap provider. )
-q, --locationiq-key (Optional) API key needed to use LocationIq. https://locationiq.com/docs/ (Instead of using this option, environment name: PHOTO_CLI_LOCATIONIQ_API_KEY can be used. )
-h, --has-paid-license (Optional) Bypass rate limit if you have paid license. ( For LocationIq. )
-l, --language (Optional) Language/culture value to get localized address result for BigDataCloud ( https://www.bigdatacloud.com/supported-languages/ ) and GoogleMaps (https://developers.google.com/maps/faq#languagesupport ).
--help Display this help screen.
--version Display version information.
NOTES:
- Instead of option names (for ex: DateTimeWithMinutes), you may use options values too. (for ex: 3)
- You can use relative folder paths. If you use the input folder as the working directory, you don't need to use the input argument.
EXAMPLE USAGES:
- All properties
Example with long argument names;
photo-cli address --reverse-geocode OpenStreetMapFoundation --input [photo-path].jpg
Example with short argument names;
photo-cli address -e OpenStreetMapFoundation -i [photo-path].jpg
- Selected properties
Example with long argument names;
photo-cli address --reverse-geocode OpenStreetMapFoundation --input [photo-path].jpg --openstreetmap-properties country city town suburb --type SelectedProperties
Example with short argument names;
photo-cli address -e OpenStreetMapFoundation -i [photo-path].jpg -r country city town suburb -t SelectedProperties
- Show full response
Example with long argument names;
photo-cli address --reverse-geocode OpenStreetMapFoundation --input [photo-path].jpg --type FullResponse
Example with short argument names;
photo-cli address -e OpenStreetMapFoundation -i [photo-path].jpg -t FullResponse
List, save and get settings.
photo-cli help settings
-k, --key (Optional) Setting property name to change.
-v, --value (Optional) Setting value to set.
-r, --reset (Optional) Reset all settings value to default ones. (no extra parameter needed)
--help Display this help screen.
--version Display version information.
NOTES:
- Instead of option names (for ex: DateTimeWithMinutes), you may use options values too. (for ex: 3)
- You can use relative folder paths. If you use input folder as working directory, you don't need to use input argument.
EXAMPLE USAGES:
- List all settings
Example with long argument names;
photo-cli settings
Example with short argument names;
photo-cli settings
- Get a setting
Example with long argument names;
photo-cli settings --key YearFormat
Example with short argument names;
photo-cli settings -k YearFormat
- Save a setting
Example with long argument names;
photo-cli settings --key YearFormat --value yyyy
Example with short argument names;
photo-cli settings -k YearFormat -v yyyy
- Reset all settings
Example with long argument names;
photo-cli settings --reset
Example with short argument names;
photo-cli settings -r
Optional use for copy, archive, info verb. Must be used on address verb. File system path to read & copy photos from. If not given, the current executing folder will be used. There will be no
modification on input path.
Must be used on copy, archive, info verbs. File system path to write output. For copy, new folder hierarchy created on that location with new file names. It will create folder if not exists. For info, report csv file path to write.
Optional use for copy, archive verb. Simulate the same process without writing to output folder. No extra parameter needed.
Optional use for copy, archive, info verb. Must be used on address verb. Third-party provider to resolve photo taken address by photo's coordinates.
| Option | Value |
|---|---|
| BigDataCloud | 1 |
| OpenStreetMapFoundation | 2 |
| GoogleMaps | 3 |
| LocationIq | 5 |
Optional use for copy, archive, info verb.
Sets Big Data Cloud reverse geocode API key. Alternatively, you may use the environment variable PHOTO_CLI_BIG_DATA_CLOUD_API_KEY.
Sets Google Maps reverse geocode API key. Alternatively, you may use the environment variable PHOTO_CLI_GOOGLE_MAPS_API_KEY.
Sets Location Iq reverse geocode API key. Alternatively, you may use the environment variable PHOTO_CLI_LOCATIONIQ_API_KEY.
Must be used when BigDataCloud is selected as reverse geocode provider. Big Data Cloud admin levels are separated with space. ( To see which level correspond to which address level, you may use photo-cli address to see the full response returned from BigDataCloud. )
Must be used when any of OpenStreetMapFoundation, LocationIq is selected as reverse geocode provider. OpenStreetMap properties separated with space. ( To see which level correspond to which address level, you may use photo-cli address to see the full response returned from OpenStreetMap provider. )
Must be used when GoogleMaps selected as reverse geocode provider. Google Maps address types separated with space. ( To see which level correspond to which address level, you may use photo-cli address to see full the response returned from GoogleMaps. )
Bypass the free rate limit if you have paid license. (For LocationIq reverse geocode provider)
Whether you want to link photos as album by picking the album type.
| Option | Value |
|---|---|
| NoAlbumLinking (default) | 0 |
| Individual | 1 |
| DateRange | 2 |
Optional action to do when a photograph with a no taken date. Default is Continue.
| Option | Value |
|---|---|
| Continue (default) | 0 (default) |
| PreventProcess | 1 |
Optional action to do when a photo with a no coordinate.
| Option | Value |
|---|---|
| Continue | 0 |
| PreventProcess | 1 |
You must select folder process behavior to whether use original folder hierarchy or flatten into single folder/grouped folder by Group By Folder.
| Option | Name |
|---|---|
| Single | 1 |
| SubFoldersPreserveFolderHierarchy | 2 |
| FlattenAllSubFolders | 3 |
While copying to a new organized folder, you must select one of these file naming strategies for a newly copied photo file name.
| Option | Value |
|---|---|
| Numeric | 1 |
| Day | 2 |
| DateTimeWithMinutes | 3 |
| DateTimeWithSeconds | 4 |
| Address | 5 |
| DayAddress | 6 |
| DateTimeWithMinutesAddress | 7 |
| DateTimeWithSecondsAddress | 8 |
| AddressDay | 9 |
| AddressDateTimeWithMinutes | 10 |
| AddressDateTimeWithSeconds | 11 |
Optional use for copy verb. While copying to a new organized folder (you should select Folder Process Type as SubFoldersPreserveFolderHierarchy ), you may select one of these file folder naming strategies. Must used with Folder Append Location Type
| Option | Value |
|---|---|
| FirstYearMonthDay | 1 |
| FirstYearMonth | 2 |
| FirstYear | 3 |
| DayRange | 4 |
| MatchingMinimumAddress | 5 |
While copying to a new organized folder (you should select Folder Process Type as SubFoldersPreserveFolderHierarchy ), you may select one of these file folder naming strategies. Must used with Folder Append Location
| Option | Value |
|---|---|
| Prefix | 1 |
| Suffix | 2 |
If you want to group photos by EXIF data, you may select one of these strategies.
| Option | Value |
|---|---|
| YearMonthDay | 1 |
| YearMonth | 2 |
| Year | 3 |
| Address | 4 |
Number naming strategy must be selected when using Naming Style as Numeric or using to numbering the possible same names.
| Option | Value |
|---|---|
| AllNamesAreSameLength | 1 |
| PaddingZeroCharacter | 2 |
| OnlySequentialNumbers | 3 |
Verify that all photo files copied successfully by comparing file hashes. (no extra parameter needed)
Optional action to do when a photograph with a no taken date. Default is Continue.
| Option | Value |
|---|---|
| Continue (default) | 0 (default) |
| PreventProcess | 1 |
| DontCopyToOutput | 2 |
| InSubFolder | 3 |
| AppendToEndOrderByFileName | 4 |
| InsertToBeginningOrderByFileName | 5 |
Optional action to do when a photo with a no coordinate.
| Option | Value |
|---|---|
| Continue | 0 |
| PreventProcess | 1 |
| DontCopyToOutput | 2 |
| InSubFolder | 3 |
Optional behavior to read & list all photos in all subfolders. Default behavior is to read & list only photos in current working folder. (no extra parameter needed)
Optional action to do when a photograph with a no taken date. Default is Continue.
| Option | Value |
|---|---|
| Continue (default) | 0 (default) |
| PreventProcess | 1 |
Optional action to do when a photo with a no coordinate.
| Option | Value |
|---|---|
| Continue | 0 |
| PreventProcess | 1 |
User can customize & set these options via settings command.
- All date & time formats. Reference values: MSDN Date Time Format Strings
- File & folder naming separators
- Report file names
No Photo Taken Date Actionfolder name.No Address Actionfolder name.- Third-party reverse geocode API keys.
- Change maximum concurrent connection limit to connect third-party reverse geocode provider.
photo-cli settings
LogLevel.Default=Error
LogLevel.Microsoft=Warning
LogLevel.PhotoCli=Warning
LogLevel.PhotoCli.Services.Implementations.ReverseGeocodes=Warning
LogLevel.Polly=Warning
LogLevel.System.Net.Http.HttpClient=Warning
YearFormat=yyyy
MonthFormat=MM
DayFormat=dd
DateFormatWithMonth=yyyy.MM
DateFormatWithDay=yyyy.MM.dd
DateTimeFormatWithMinutes=yyyy.MM.dd_HH.mm
DateTimeFormatWithSeconds=yyyy.MM.dd_HH.mm.ss
AddressSeparator=-
FolderAppendSeparator=-
DayRangeSeparator=-
SameNameNumberSeparator=-
PhotoFormatInvalidFolderName=invalid-photo-format
NoPhotoTakenDateFolderName=no-photo-taken-date
NoAddressFolderName=no-address
NoAddressAndPhotoTakenDateFolderName=no-address-and-no-photo-taken-date
CsvReportFileName=photo-cli-report.csv
DryRunCsvReportFileName=photo-cli-dry-run.csv
ConnectionLimit=4
BigDataCloudApiKey=
GoogleMapsApiKey=
LocationIqApiKey=
CoordinatePrecision=4
ArchivePhotoTakenDateHashSeparator=-
SupportedExtensions=jpg,jpeg,heic,png,hif
CompanionExtensions=mov
LogCategoryNameOutput=False
MacOsCommand=open
MacOsArgumentPrefix=-a Preview
photo-cli settings --key YearFormat
YearFormat=yyyy
photo-cli settings --key YearFormat --value y
No output when successful.
photo-cli settings --reset
No output when successful
Process exit codes listed below;
| Option | Value |
|---|---|
| Success | 0 |
| ParseArgsFailed | 1 |
| AppSettingsInvalidFile | 2 |
| UnexpectedError | 3 |
| ApiKeyStoreValidationFailed | 10 |
| AddressOptionsValidationFailed | 11 |
| InfoOptionsValidationFailed | 12 |
| CopyOptionsValidationFailed | 13 |
| SettingsOptionsValidationFailed | 14 |
| ArchiveOptionsValidationFailed | 15 |
| InputFolderNotExists | 20 |
| NoPhotoFoundOnDirectory | 21 |
| OutputFolderIsNotEmpty | 22 |
| OutputPathIsExists | 23 |
| OutputPathDontHaveWriteFilePermission | 24 |
| OutputPathDontHaveCreateDirectoryPermission | 25 |
| InputFileNotExists | 26 |
| FileVerifyErrors | 27 |
| PhotosWithNoDatePreventedProcess | 30 |
| PhotosWithNoCoordinatePreventedProcess | 31 |
| PhotosWithNoCoordinateAndNoDatePreventedProcess | 32 |
| PhotosWithInvalidFileFormatPreventedProcess | 33 |
| PhotosWithMissingReverseGeocodeInfoAsRequested | 34 |
| PhotosWithUnexpectedDateRangePreventedProcess | 35 |
| PropertyNotFound | 40 |
| InvalidSettingsValue | 41 |
| InvalidSettingsLogLevelChange | 42 |
| AlbumExist | 50 |
| InvalidSettingsLogLevelChange | 50 |
| InconsistencyOnSavingPhotosToDatabase | 51 |
| InconsistencyOnSavingUserDefinedAlbumToDatabase | 52 |
| AlbumNameMustBeUniqueWhileAddingOrUseUpdate | 53 |
| AlbumNotFoundById | 54 |
| NoPhotosToAddInAlbum | 55 |
| NoDataRangeFoundOnPhotos | 56 |
| ExistingAlbumConfigurationNotValid | 57 |
| NoArchiveDatabaseFound | 60 |
| NoPhotoFoundToList | 61 |
See the contributing.
See the code of conduct.
See the changelog.
Many thanks to these open source libraries. This work can not be done without these beautiful libraries and their contributors.
- CommandLineParser
- coverlet
- CsvHelper
- FluentAssertions
- FluentValidation
- MetadataExtractor
- Moq
- Polly
- System.IO.Abstractions
- xunit
Also thanks exif-samples for sample images, to make project test various EXIF data variations.
Everything inside this repository is Apache 2.0 licensed.
dotnet tool uninstall -g photo-cli
This tool is currently developed by Alp Coker and is open for contributors.