{"id":13478688,"url":"https://github.com/jmathai/elodie","last_synced_at":"2026-10-19T12:42:46.882Z","repository":{"id":2731161,"uuid":"42206855","full_name":"jmathai/elodie","owner":"jmathai","description":"An EXIF-based photo assistant, organizer  and workflow automation tool.","archived":false,"fork":false,"pushed_at":"2026-02-15T06:56:00.000Z","size":3978,"stargazers_count":1504,"open_issues_count":122,"forks_count":160,"subscribers_count":30,"default_branch":"master","last_synced_at":"2026-09-21T07:14:46.561Z","etag":null,"topics":["automation","cli","command-line","dropbox","elodie","exif","exif-information","exiftool","google-drive","google-photos","google-photos-api","organize-photos","photo-manager","photo-organizer","photography","photos","python","python3","synology","synology-nas"],"latest_commit_sha":null,"homepage":"https://bit.ly/introducing-elodie","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jmathai.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"jmathai"}},"created_at":"2015-09-09T21:53:43.000Z","updated_at":"2026-09-19T08:19:51.000Z","dependencies_parsed_at":"2023-01-13T12:03:23.829Z","dependency_job_id":"de0078ea-b32c-4212-832c-c117d6800a33","html_url":"https://github.com/jmathai/elodie","commit_stats":{"total_commits":300,"total_committers":32,"mean_commits":9.375,"dds":0.26,"last_synced_commit":"76ad82303fcbf0e4ab421dbfbb39e4a4cc76aecc"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/jmathai/elodie","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmathai%2Felodie","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmathai%2Felodie/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmathai%2Felodie/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmathai%2Felodie/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jmathai","download_url":"https://codeload.github.com/jmathai/elodie/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmathai%2Felodie/sbom","scorecard":{"id":524015,"data":{"date":"2025-08-11","repo":{"name":"github.com/jmathai/elodie","commit":"9f79a7a740fc9f43636303d7d710e05c10953fb4"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2,"checks":[{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":1,"reason":"0 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":2,"reason":"Found 8/30 approved changesets -- score normalized to 2","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: containerImage not pinned by hash: Dockerfile:1: pin your Docker image by updating debian:jessie to debian:jessie@sha256:32ad5050caffb2c7e969dac873bce2c370015c2256ff984b70c1c08b3a2816a0","Warn: pipCommand not pinned by hash: Dockerfile:5-9","Warn: pipCommand not pinned by hash: Dockerfile:31-33","Warn: pipCommand not pinned by hash: Dockerfile:31-33","Info:   0 out of   1 containerImage dependencies pinned","Info:   0 out of   3 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact 1.0.0-alpha1 not signed: https://api.github.com/repos/jmathai/elodie/releases/2812387","Warn: release artifact 1.0.0-alpha1 does not have provenance: https://api.github.com/repos/jmathai/elodie/releases/2812387"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 27 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"59 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2022-42991 / GHSA-v3c5-jqr6-7qm8","Warn: Project is vulnerable to: GHSA-3c5c-7235-994j","Warn: Project is vulnerable to: GHSA-3f63-hfp8-52jq","Warn: Project is vulnerable to: PYSEC-2021-41 / GHSA-3wvg-mj6g-m9cv","Warn: Project is vulnerable to: PYSEC-2020-77 / GHSA-3xv8-3j54-hgrp","Warn: Project is vulnerable to: PYSEC-2020-80 / GHSA-43fq-w8qq-v88h","Warn: Project is vulnerable to: GHSA-44wm-f244-xhp3","Warn: Project is vulnerable to: GHSA-4fx9-vc88-q2xc","Warn: Project is vulnerable to: PYSEC-2021-35 / GHSA-57h3-9rgr-c24m","Warn: Project is vulnerable to: PYSEC-2020-172 / GHSA-5gm3-px64-rw72","Warn: Project is vulnerable to: PYSEC-2021-331 / GHSA-7534-mm45-c74v","Warn: Project is vulnerable to: PYSEC-2021-92 / GHSA-7r7m-5h27-29hp","Warn: Project is vulnerable to: PYSEC-2020-78 / GHSA-8843-m7mw-mxqm","Warn: Project is vulnerable to: PYSEC-2023-227 / GHSA-8ghj-p4vj-mr35","Warn: Project is vulnerable to: PYSEC-2014-87 / GHSA-8m9x-pxwq-j236","Warn: Project is vulnerable to: PYSEC-2022-10 / GHSA-8vj2-vxx3-667w","Warn: Project is vulnerable to: PYSEC-2021-36 / GHSA-8xjq-8fcg-g5hw","Warn: Project is vulnerable to: PYSEC-2016-6 / GHSA-8xjv-v9xq-m5h9","Warn: Project is vulnerable to: PYSEC-2021-42 / GHSA-95q3-8gr9-gm8w","Warn: Project is vulnerable to: PYSEC-2022-168 / GHSA-9j59-75qj-795w","Warn: Project is vulnerable to: PYSEC-2014-10 / GHSA-cfmr-38g9-f2h7","Warn: Project is vulnerable to: PYSEC-2020-76 / GHSA-cqhg-xjhh-p8hf","Warn: Project is vulnerable to: PYSEC-2021-40 / GHSA-f4w8-cv6p-x6r5","Warn: Project is vulnerable to: PYSEC-2021-69 / GHSA-f5g8-5qq7-938w","Warn: Project is vulnerable to: PYSEC-2021-139 / GHSA-g6rj-rv7j-xwp4","Warn: Project is vulnerable to: PYSEC-2015-16 / GHSA-h5rf-vgqx-wjv2","Warn: Project is vulnerable to: PYSEC-2016-5 / GHSA-hggx-3h72-49ww","Warn: Project is vulnerable to: PYSEC-2020-84 / GHSA-hj69-c76v-86wr","Warn: Project is vulnerable to: PYSEC-2016-7 / GHSA-hvr8-466p-75rh","Warn: Project is vulnerable to: PYSEC-2015-15 / GHSA-j6f7-g425-4gmx","Warn: Project is vulnerable to: GHSA-j7hp-h8jx-5ppr","Warn: Project is vulnerable to: PYSEC-2019-110 / GHSA-j7mj-748x-7p78","Warn: Project is vulnerable to: GHSA-jgpv-4h4c-xhw3","Warn: Project is vulnerable to: PYSEC-2022-42979 / GHSA-m2vv-5vj5-2hm7","Warn: Project is vulnerable to: PYSEC-2021-37 / GHSA-mvg9-xffr-p774","Warn: Project is vulnerable to: PYSEC-2020-83 / GHSA-p49h-hjvm-jg3h","Warn: Project is vulnerable to: PYSEC-2022-8 / GHSA-pw3c-h7wp-cvhx","Warn: Project is vulnerable to: PYSEC-2021-93 / GHSA-q5hq-fp76-qmrc","Warn: Project is vulnerable to: PYSEC-2020-82 / GHSA-r7rm-8j6h-r933","Warn: Project is vulnerable to: PYSEC-2014-23 / GHSA-r854-96gq-rfg3","Warn: Project is vulnerable to: PYSEC-2016-8 / GHSA-rwr3-c2q8-gm56","Warn: Project is vulnerable to: PYSEC-2020-81 / GHSA-vcqg-3p29-xw73","Warn: Project is vulnerable to: PYSEC-2020-79 / GHSA-vj42-xq3r-hr3r","Warn: Project is vulnerable to: PYSEC-2021-70 / GHSA-vqcj-wrf2-7v73","Warn: Project is vulnerable to: PYSEC-2016-9 / GHSA-w4vg-rf63-f3j3","Warn: Project is vulnerable to: PYSEC-2014-22 / GHSA-x895-2wrm-hvp7","Warn: Project is vulnerable to: PYSEC-2022-9 / GHSA-xrcv-f9gm-v42c","Warn: Project is vulnerable to: OSV-2022-715","Warn: Project is vulnerable to: PYSEC-2021-137","Warn: Project is vulnerable to: PYSEC-2021-138","Warn: Project is vulnerable to: PYSEC-2021-317","Warn: Project is vulnerable to: PYSEC-2021-38","Warn: Project is vulnerable to: PYSEC-2021-39","Warn: Project is vulnerable to: PYSEC-2021-94","Warn: Project is vulnerable to: PYSEC-2022-42980","Warn: Project is vulnerable to: PYSEC-2023-175","Warn: Project is vulnerable to: GHSA-9hjg-9r4m-mvj7","Warn: Project is vulnerable to: GHSA-9wx4-h78v-vm56","Warn: Project is vulnerable to: PYSEC-2023-74 / GHSA-j8r2-6x86-q33q"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-20T03:52:37.176Z","repository_id":2731161,"created_at":"2025-08-20T03:52:37.176Z","updated_at":"2025-08-20T03:52:37.176Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":344872684,"owners_count":38356471,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-10-03T21:59:58.778Z","status":"online","status_checked_at":"2026-10-19T02:00:06.414Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["automation","cli","command-line","dropbox","elodie","exif","exif-information","exiftool","google-drive","google-photos","google-photos-api","organize-photos","photo-manager","photo-organizer","photography","photos","python","python3","synology","synology-nas"],"created_at":"2024-07-31T16:02:00.818Z","updated_at":"2026-10-19T12:42:46.875Z","avatar_url":"https://github.com/jmathai.png","language":"Python","funding_links":["https://github.com/sponsors/jmathai"],"categories":["Python","Building"],"sub_categories":["Workflows"],"readme":"# Hello, I'm Elodie\n~~ *Your Personal EXIF-based Photo, Video and Audio Assistant* ~~\n\n[![CircleCI](https://dl.circleci.com/status-badge/img/gh/jmathai/elodie/tree/master.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/jmathai/elodie/tree/master) [![Coverage Status](https://coveralls.io/repos/github/jmathai/elodie/badge.svg?branch=master)](https://coveralls.io/github/jmathai/elodie?branch=master) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/jmathai/elodie/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/jmathai/elodie/?branch=master)\n\n\u003cp align=\"center\"\u003e\u003cimg src =\"https://jmathai.s3.amazonaws.com/github/elodie/elodie-folder-anim.gif\" /\u003e\u003c/p\u003e\n\n## Quickstart guide\n\nGetting started takes just a few minutes.\n\n### Install ExifTool\n\nElodie relies on the great [ExifTool library by Phil Harvey](http://www.sno.phy.queensu.ca/~phil/exiftool/). You'll need to install it for your platform.\n\n* Some video features require version 10.20 or higher\n* Support for HEIC files requires version 11.50 or higher\n* Translating geolocation to city name requires version 13.13 or higher\n\nCheck your version by typing `exiftool -ver` and see the [manual installation instructions for ExifTool](http://www.sno.phy.queensu.ca/~phil/exiftool/install.html#Unix) if needed.\n\n```\n# OSX (uses homebrew, http://brew.sh/)\nbrew install exiftool\n\n# Debian / Ubuntu\napt-get install libimage-exiftool-perl\n\n# Fedora / Redhat\ndnf install perl-Image-ExifTool\n\n# Windows users can install the binary\n# http://www.sno.phy.queensu.ca/~phil/exiftool/install.html\n```\n\n### Clone the Elodie repository\n\nYou can clone Elodie from GitHub. You'll need `git` installed ([instructions](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)).\n\n```\ngit clone https://github.com/jmathai/elodie.git\ncd elodie\npip install -r requirements.txt\n```\n\n### Give Elodie a test drive\n\nNow that you've got the minimum dependencies installed you can start using Elodie. You'll need a photo, video or audio file and a folder you'd like Elodie to organize them into.\n\n```\n# Run these commands from the root of the repository you just cloned.\n./elodie.py import --debug --destination=\"/where/i/want/my/photos/to/go\" /where/my/photo/is.jpg\n```\n\n### Testing changes safely with dry-run mode\n\nBefore running any command that modifies your photos, you can use the `--dry-run` flag to see exactly what Elodie would do without actually making any changes. This is especially useful when organizing large photo collections for the first time.\n\n```\n# See what would happen during import without actually copying files\n./elodie.py import --dry-run --destination=\"/where/i/want/my/photos/to/go\" /where/my/photos/are\n\n# See what EXIF changes would be made without modifying files  \n./elodie.py update --dry-run --location=\"Las Vegas, NV\" /path/to/photo.jpg\n\n# See what plugin operations would run without executing them\n./elodie.py batch --dry-run\n```\n\nThe dry-run mode shows you detailed output of what operations would be performed, letting you verify the organization structure and catch any issues before making actual changes to your photo library.\n\nNow you're ready to learn more about Elodie.\n\n\u003cp align=\"center\"\u003e\u003cimg src =\"creative/logo@300x.png\" /\u003e\u003c/p\u003e\n\n## Slowstart guide\n\n[Read a 3 part blog post on why I was created](https://medium.com/vantage/understanding-my-need-for-an-automated-photo-workflow-a2ff95b46f8f#.dmwyjlc57) and how [I can be used with Immich](https://jaisenmathai.com/articles/my-ridiculously-robust-photo-management-system-immich-edition/).\n\nI work tirelessly to make sure your photos are always sorted and organized so you can focus on more important things. By photos I mean JPEG, DNG, NEF and common video and audio files.\n\nYou don't love me yet but you will.\n\nI only do 3 things.\n\n* Firstly I organize your existing collection of photos into a customizable folder structure.\n* Second I help make it easy for all the photos you haven't taken yet to flow into the exact location they belong.\n* Third but not least I promise to do all this without a yucky propietary database that some friends of mine use.\n\n*NOTE: make sure you've installed everything I need before running the commands below. [Instructions](#quickstart-guide) at the top of this page.*\n\n## Let's organize your existing photos\n\nMy guess is you've got quite a few photos scattered around. The first thing I'll help you do is to get those photos organized. It doesn't matter if you have hundreds, thousands or tens of thousands of photos; the more the merrier.\n\nFire up your terminal and run this command which *__copies__* your photos into something a bit more structured.\n\n```\n./elodie.py import --destination=\"/where/i/want/my/photos/to/go\" /where/my/photos/are\n```\n\nI'm pretty fast but depending on how many photos you have you might want to grab a snack. When you run this command I'll `print` out my work as I go along. If you're bored you can open `/where/i/want/my/photos/to/go` in *Finder* and watch as I effortlessly copy your photos there.\n\nYou'll notice that your photos are now organized by date and location. Some photos do not have proper dates or location information in them. I do my best and in the worst case scenario I'll use the earlier of the files access or modified time. Ideally your photos have dates and location in the EXIF so my work is more accurate.\n\nDon't fret if your photos don't have much EXIF information. I'll show you how you can fix them up later on but let's walk before we run.\n\nBack to your photos. When I'm done you should see something like this. Notice that I've renamed your files by adding the date and time they were taken. This helps keep them in chronological order when using most viewing applications. You'll thank me later.\n\n```\n├── 2015-06-Jun\n│   ├── California\n│   │   ├── 2015-06-29_16-34-14-img_3900.jpg\n│   │   └── 2015-06-29_17-07-06-img_3901.jpg\n│   └── Paris\n│       └── 2015-06-30_02-40-43-img_3903.jpg\n├── 2015-07-Jul\n│   ├── Mountain View\n│   │   ├── 2015-07-19_17-16-37-img_9426.jpg\n│   │   └── 2015-07-24_19-06-33-img_9432.jpg\n└── 2015-09-Sep\n│   ├── Unknown Location\n    │   ├── 2015-09-27_01-41-38-_dsc8705.dng\n    │   └── 2015-09-27_01-41-38-_dsc8705.nef\n```\n\nNot too bad, eh? Wait a second, what's *Unknown Location*? If I'm not able to figure out where a photo was taken I'll place it into a folder named *Unknown Location*. This typically happens when photos do not have GPS information in their EXIF. You shouldn't see this for photos taken on a smartphone but it's often the case with digital cameras and SLRs. \n\n**Important:** If you're importing photos that are missing location or time information in their EXIF, you can specify these during import using `--location` and `--time` options. Note that `--location` will overwrite any existing location data and `--time` will overwrite any existing time data, so only use them when the photos lack this information or when you know the existing EXIF data is incorrect.\n\n```\n# For photos missing location data\n./elodie.py import --destination=\"/where/i/want/my/photos/to/go\" --location=\"Las Vegas, NV\" /where/my/photos/are\n\n# For photos with incorrect or missing timestamps\n./elodie.py import --destination=\"/where/i/want/my/photos/to/go\" --time=\"2015-06-29 16:30:00\" /where/my/photos/are\n```\n\nAlternatively, I can help you add GPS information to those photos after importing and get them organized better. Let me show you how.\n\n### Usage Instructions\n\nYou can view these instructions on the command line by typing `./elodie.py import --help`, `./elodie.py update --help` or `./elodie.py generate-db --help`.\n\n#### Import photos\n\n```\nUsage: elodie.py import [OPTIONS] [PATHS]...\n\n  Import files or directories by reading their EXIF and organizing them\n  accordingly.\n\nOptions:\n  --destination DIRECTORY  Copy imported files into this directory.\n                           [required]\n  --source DIRECTORY       Import files from this directory, if specified.\n  --file PATH              Import this file, if specified.\n  --album-from-folder      Use images' folders as their album names.\n  --trash                  After copying files, move the old files to the\n                           trash.\n  --allow-duplicates       Import the file even if it's already been imported.\n  --location TEXT          Update the image location. Location should be the\n                           name of a place, like \"Las Vegas, NV\".\n  --time TEXT              Update the image time. Time should be in YYYY-mm-dd\n                           hh:ii:ss or YYYY-mm-dd format.\n  --debug                  Show more verbose debug output.\n  --dry-run                Show what would be done without making any changes.\n  --exclude-regex TEXT     Regular expression for directories or files to\n                           exclude.\n  --help                   Show this message and exit.\n```\n\n#### Update photos\n\n```\nUsage: elodie.py update [OPTIONS] FILES...\n\n  Update a file's EXIF. Automatically modifies the file's location and file\n  name accordingly.\n\nOptions:\n  --album TEXT     Update the image album.\n  --location TEXT  Update the image location. Location should be the name of a\n                   place, like \"Las Vegas, NV\".\n  --time TEXT      Update the image time. Time should be in YYYY-mm-dd\n                   hh:ii:ss or YYYY-mm-dd format.\n  --title TEXT     Update the image title.\n  --debug          Show more verbose debug output.\n  --dry-run        Show what would be done without making any changes.\n  --help           Show this message and exit.\n```\n\n#### (Re)Generate checksum database\n\n```\nUsage: elodie.py generate-db [OPTIONS]\n\n  Regenerate the hash.json database which contains all of the sha256\n  signatures of media files. The hash.json file is located at ~/.elodie/.\n\nOptions:\n  --source DIRECTORY  Source of your photo library.  [required]\n  --help              Show this message and exit.\n```\n\n#### Run batch operations for all plugins\n\n```\nUsage: elodie.py batch [OPTIONS]\n\n  Run batch() for all plugins.\n\nOptions:\n  --debug    Show more verbose debug output.\n  --dry-run  Show what would be done without making any changes.\n  --help     Show this message and exit.\n```\n\n#### Verify library against bit rot / data rot\n\n```\nUsage: elodie.py verify\n```\n\n### Excluding folders and files from being imported\n\nIf you have specific folders or files which you would like to prevent from being imported you can provide regular expressions which will be used to match and skip files from being imported.\n\nYou can specify an exclusion at run time by using the `--exclude-regex` argument of the `import` command. You can pass multiple `--exclude-regex` arguments and all folder/file paths which match will be (silently) skipped.\n\nIf there are certain file or folder paths you *never* want to import then you can also add an `[Exclusions]` section to your `config.ini` file. Similar to the command line argument you can provide multiple exclusions. Here is an example.\n\n```\n[Exclusions]\nsynology_folders=@eaDir\nthumbnails=.thumbnails\n```\n\n### Create your own folder structure\n\nOK, so what if you don't like the folders being named `2015-07-Jul/Mountain View`? No problem!\n\nYou can add a custom folder structure by editing your `config.ini` file (which should be placed under `~/.elodie/config.ini`). If you'd like to use a different folder for your configuration file then set an environment variable named `ELODIE_APPLICATION_DIRECTORY` with the fully qualified directory path.\n\n#### Custom folder examples\n\nSometimes examples are easier to understand than explainations so I'll start there. If you'd like to understand my magic I explain it in more detail below these examples. You customize your folder structure in the `Directory` section of your `config.ini`. For details of the supported formats see [strftime.org](http://strftime.org/)\n\n```\n[Directory]\nlocation=%city, %state\nyear=%Y\nfull_path=%year/%location\n# -\u003e 2015/Sunnyvale, California\n\nlocation=%city, %state\nmonth=%B\nyear=%Y\nfull_path=%year/%month/%location\n# -\u003e 2015/December/Sunnyvale, California\n\nlocation=%city, %state\nmonth=%m\nyear=%Y\nfull_path=%year-%month/%location\n# -\u003e 2015-12/Sunnyvale, California\n\ndate=%Y\nlocation=%city, %state\ncustom=%date %album\nfull_path=%location/%custom\n# -\u003e Sunnyvale, California/2015 Birthday Party\n```\n\n#### Using fallback folders\n\nThere are times when the EXIF needed to correctly name a folder doesn't exist on a photo. I came up with fallback folders to help you deal with situations such as this. Here's how it works.\n\nYou can specify a series of folder names by separating them with a `|`. That's a pipe, not an L. Let's look at an example.\n\n```\n[Directory]\nmonth=%m\nyear=%Y\nlocation=%city\nfull_path=%month/%year/%album|%location|%\"Beats me\"\n```\n\nWhat this asks me to do is to name the last folder the same as the album I find in EXIF. If I don't find an album in EXIF then I should use the location. If there's no GPS in the EXIf then I should name the last folder `Beats me`.\n\n#### How folder customization works\n\nYou can construct your folder structure using a combination of the location, dates and camera make/model. Under the `Directory` section of your `config.ini` file you can define placeholder names and assign each a value. For example, `date=%Y-%m` would create a date placeholder with a value of YYYY-MM which would be filled in with the date from the EXIF on the photo.\n\nThe placeholders can be used to define the folder structure you'd like to create. The default structure would look like `2015-07-Jul/Mountain View`.\n\nI have some date placeholders you can customize. You can use any of [the standard Python time directives](https://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior) to customize the date format to your liking.\n\n* `%day` the day the photo was taken.\n* `%month` the month the photo was taken.\n* `%year` the year the photo was taken.\n\nI have camera make and model placeholders which can be used to include the camera make and model into the folder path.\n\n* `%camera_make` the make of the camera which took the photo.\n* `%camera_model` the model of the camera which took the photo.\n\nI also have a few built-in location placeholders you can use. Use this to construct the `%location` you use in `full_path`.\n\n* `%city` the name of the city the photo was taken. Requires geolocation data in EXIF.\n* `%state` the name of the state the photo was taken. Requires geolocation data in EXIF.\n* `%country` the name of the country the photo was taken. Requires geolocation data in EXIF.\n\nIn addition to my built-in and date placeholders you can combine them into a single folder name using my complex placeholders.\n\n* `%location` can be used to combine multiple values of `%city`, `%state` and `%country`. For example, `location=%city, %state` would result in folder names like `Sunnyvale, California`.\n* `%date` can be used to combine multiple values from [the standard Python time directives](https://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior). For example, `date=%Y-%m` would result in folder names like `2015-12`.\n* `%custom` can be used to combine multiple values from anything else. Think of it as a catch-all when `%location` and `%date` don't meet your needs.\n\n#### How file customization works\n\nYou can configure how Elodie names your files using placeholders. This works similarly to how folder customization works. The default naming format is what's referred to elsewhere in this document and has many thought through benefits. Using the default will gives you files named like `2015-09-27_01-41-38-_dsc8705.jpg`.\n\n\n* Minimizes the likelihood of naming conflicts.\n* Encodes important EXIF information into the file name.\n* Optimizes for sort order when listing in most file and photo viewers.\n\nIf you'd like to specify your own naming convention it's recommended you include something that's mostly unique like the time including seconds. You'll need to include a `[File]` section in your `config.ini` file with a name attribute. If a placeholder doesn't have a value then it plus any preceding characters which are not alphabetic are removed.\n\nBy default the resulting filename is all lowercased. To change this behavior to uppercasing add capitalization=upper.\n\n```\n[File]\ndate=%Y-%m-%b-%H-%M-%S\nname=%date-%original_name-%title.%extension\n# -\u003e 2012-05-mar-12-59-30-dsc_1234-my-title.jpg\n\ndate=%Y-%m-%b-%H-%M-%S\nname=%date-%original_name-%album.%extension\ncapitalization=upper\n# -\u003e 2012-05-MAR-12-59-30-DSC_1234-MY-ALBUM.JPG\n```\n\n### Reorganize by changing location and dates\n\nIf you notice some photos were incorrectly organized you should definitely let me know. In the example above I put two photos into an *Unknown Location* folder because I didn't find GPS information in their EXIF. To fix this I'll help you add GPS information into the photos' EXIF and then I'll reorganize them.\n\n#### Tell me where your photos were taken\nRun the command below if you want to tell me the photos were taken in Las Vegas. You don't have to type all that in though. It's easier to just type `./elodie.py update --location=\"Las Vegas, NV\" ` and select and drag the files from *OS X Finder* into the terminal.\n\n```\n./elodie.py update --location=\"Las Vegas, NV\" /where/i/want/my/photos/to/go/2015-09-Sep/Unknown\\ Location/2015-09-27_01-41-38-_dsc8705.dng /where/i/want/my/photos/to/go/2015-09-Sep/Unknown\\ Location/2015-09-27_01-41-38-_dsc8705.nef\n```\n\nYou should see this after running that command.\n\n```\n└── 2015-09-Sep\n│   ├── Las Vegas\n    │   ├── 2015-09-27_01-41-38-_dsc8705.dng\n    │   └── 2015-09-27_01-41-38-_dsc8705.nef\n```\n\n#### Tell me when you took your photos\nRun the command below if I got the date wrong when organizing your photos. Similarly to the above command you can drag files from *Finder* into your terminal.\n\n```\n./elodie.py update --time=\"2015-04-15\" /where/i/want/my/photos/to/go/2015-09-Sep/Unknown\\ Location/2015-09-27_01-41-38-_dsc8705.dng /where/i/want/my/photos/to/go/2015-09-Sep/Unknown\\ Location/2015-09-27_01-41-38-_dsc8705.nef\n```\n\nThat will change the date folder like so.\n\n```\n└── 2015-04-Apr\n│   ├── Las Vegas\n    │   ├── 2015-09-27_01-41-38-_dsc8705.dng\n    │   └── 2015-09-27_01-41-38-_dsc8705.nef\n```\n\nYou can, of course, ask me to change the location and time. I'll happily update the photos and move them around accordingly.\n\n```\n./elodie.py update --location=\"Las Vegas, NV\" --time=\"2015-04-15\" /where/i/want/my/photos/to/go/2015-09-Sep/Unknown\\ Location/2015-09-27_01-41-38-_dsc8705.dng /where/i/want/my/photos/to/go/2015-09-Sep/Unknown\\ Location/2015-09-27_01-41-38-_dsc8705.nef\n```\n\n## What about photos I take in the future?\n\nOrganizing your existing photos is great. But I'd be lying if I said I was the only one who could help you with that. Unlike other programs I put the same effort into keeping your library organized into the future as I have in getting it organized in the first place.\n\n### Letting me know when you've got more photos to organize\n\nIn order to sort new photos that I haven't already organized I need someone to tell me about them. There's no single way to do this. You could use inotify, cron, Automator or my favorite app - Hazel; it doesn't matter.\n\nIf you'd like to let me know of a specific photo or group of photos to add to your library you would run one of the following commands. Use fully qualified paths for everything since you won't be running this manually.\n\n```\n# I can import a single file into your library.\n./elodie.py import --destination=\"/where/i/want/my/photo/to/go\" /full/path/to/file.jpg\n\n# I can also import all the photos from a directory into your library.\n./elodie.py import --destination=\"/where/i/want/my/photo/to/go\" /where/my/photos/are\n```\n\n## Why not use a database?\n\nLook, it's not that I think databases are evil. One of my friends is a database. It's just that I've been doing this for a long time and I've always used a database for it. In the end they're more trouble than they're worth. I should have listened to my mother when she told me to not date a database.\n\nIt's a lot more work to organize photos without a database. No wonder everyone else uses them. But your happiness is my happiness. If a little elbow grease on my part makes you happy then I'm glad to do it.\n\n### A bit on how I do all this without a database\n\nEvery photo is essentially a database. So it's more accurate to say I use the thousands of tiny databases you already have and use them to organize your photos.\n\nI'm simple. I put a photo into its proper location. I can update a photo to have the right date or location. The latter triggers the first; creating a nice tidy loop of organizational goodness.\n\nI don't do anything else so don't bother asking.\n\n## EXIF and XMP tags\n\nWhen I organize photos I look at the embedded metadata. Here are the details of how I determine what information to use in order of precedence.\n\n| Dimension | Fields | Notes |\n|---|---|---|\n| Date Taken (photo) | EXIF:DateTimeOriginal, EXIF:CreateDate, EXIF:ModifyDate, file created, file modified |   |\n| Date Taken (video, audio) | QuickTime:CreationDate, QuickTime:CreateDate, QuickTime:CreationDate-und-US, QuickTime:MediaCreateDate, H264:DateTimeOriginal, file created, file modified |   |\n| Location (photo) | EXIF:GPSLatitude/EXIF:GPSLatitudeRef, EXIF:GPSLongitude/EXIF:GPSLongitudeRef  |   |\n| Location (video, audio) | XMP:GPSLatitude, Composite:GPSLatitude, XMP:GPSLongitude, Composite:GPSLongitude | Composite tags are read-only |\n| Title (photo) | XMP:Title |   |\n| Title (video, audio) | XMP:DisplayName |   |\n| Album | XMP-xmpDM:Album, XMP:Album | XMP:Album is user defined in `configs/ExifTool_config` for backwards compatability |\n| Camera Make (photo, video) | EXIF:Make, QuickTime:Make |   |\n| Camera Model (photo, video) | EXIF:Model, QuickTime:Model |   |\n\n## Deprecated Use of MapQuest\n\nI used to use the MapQuest API to help me organize your photos by location. I've deprecated this feature and will remove support from the code base. If you're already using MapQuest, after the removal of MapQuest support I'll automatically fall back to Exiftool which is the new default. Follow [pull request #518](https://github.com/jmathai/elodie/issues/518) to know when it gets removed.\n\n## Questions, comments or concerns?\n\nThe best ways to provide feedback is by opening a [GitHub issue](https://github.com/jmathai/elodie/issues) or emailing me at [jaisen@jmathai.com](mailto:jaisen@jmathai.com).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjmathai%2Felodie","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjmathai%2Felodie","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjmathai%2Felodie/lists"}