{"id":20249902,"url":"https://github.com/aaronkollasch/photomanager","last_synced_at":"2026-05-20T07:34:24.221Z","repository":{"id":37895221,"uuid":"349859112","full_name":"aaronkollasch/photomanager","owner":"aaronkollasch","description":"Collect photos into a central repository","archived":false,"fork":false,"pushed_at":"2025-05-21T02:33:34.000Z","size":534,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-07-17T21:13:01.187Z","etag":null,"topics":["async","checksum","database","photo"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aaronkollasch.png","metadata":{"files":{"readme":"README.rst","changelog":"CHANGES.rst","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2021-03-20T23:43:51.000Z","updated_at":"2025-05-21T02:33:35.000Z","dependencies_parsed_at":"2023-12-01T00:39:56.488Z","dependency_job_id":null,"html_url":"https://github.com/aaronkollasch/photomanager","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/aaronkollasch/photomanager","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aaronkollasch%2Fphotomanager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aaronkollasch%2Fphotomanager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aaronkollasch%2Fphotomanager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aaronkollasch%2Fphotomanager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aaronkollasch","download_url":"https://codeload.github.com/aaronkollasch/photomanager/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aaronkollasch%2Fphotomanager/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266604009,"owners_count":23954725,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-23T02:00:09.312Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"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":["async","checksum","database","photo"],"created_at":"2024-11-14T09:56:05.363Z","updated_at":"2026-05-20T07:34:24.213Z","avatar_url":"https://github.com/aaronkollasch.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"============\nPhotoManager\n============\n\n.. image:: https://github.com/aaronkollasch/photomanager/workflows/CI/badge.svg?branch=main\n     :target: https://github.com/aaronkollasch/photomanager/actions?workflow=CI\n     :alt: CI Status\n\n.. image:: http://www.mypy-lang.org/static/mypy_badge.svg\n     :target: http://mypy-lang.org/\n     :alt: Checked with mypy\n\n.. image:: https://img.shields.io/badge/code%20style-black-000000.svg\n     :target: https://github.com/psf/black\n     :alt: Code style: black\n\n.. image:: https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat\u0026labelColor=ef8336\n     :target: https://pycqa.github.io/isort/\n     :alt: Imports: isort\n\nA manager for photos and other media files.\n\nIndexes photos, adds them to a database, and\ncollects them in a specified directory.\nVerifies stored photos against bitrot or modification\nbased on their checksum.\nDatabase is stored in a non-proprietary, human-readable JSON format.\nPhotoManager is inspired by `elodie \u003chttps://github.com/jmathai/elodie\u003e`_,\nbut it is intended for archiving and will not modify any file contents,\nincluding metadata.\n\nPhotos are organized by the best available date\nobtained from metadata or file information.\nThey can be prioritized so that only the best available version\nwill be collected. Alternate copies of photos\nare identified by matching filenames and timestamps.\n\nInstallation\n============\n\nRequires Python 3.10, 3.11, or 3.12.\n\nClone the repository\n--------------------\n\n.. code-block:: bash\n\n    git clone https://github.com/aaronkollasch/photomanager.git\n    cd photomanager\n    pip install .\n\n\nInstall ExifTool\n----------------\n\nExifTool is required to index,\nbut not to collect or verify photos.\n\n.. code-block:: bash\n\n    # macOS\n    brew install exiftool\n\n    # Debian / Ubuntu\n    apt install libimage-exiftool-perl\n\n    # Fedora / Redhat\n    dnf install perl-Image-ExifTool\n\nOr download from `\u003chttps://exiftool.org\u003e`_\n\nExtras\n------\n\nThe database JSON file can optionally be compressed as a zstd\nor gzip file. Zstandard is available in most package managers,\ne.g. ``brew install zstd``.\nFilenames ending in ``.gz`` will be read as gzip archives and\nnames ending in ``.zst`` will be read as zstd archives.\n\nTo enable photo integrity checking, additional dependencies\nmust be installed with ``pip install .[check-mi]``.\n\nUsage\n=====\n\nAdd photos to the database\n--------------------------\n\n.. code-block:: bash\n\n    photomanager index --db db.json /path/to/directory /path/to/photo.jpg\n\nPhotoManager will search for media files in any supplied directories\nand also index single files supplied directly as arguments.\nRepeat with as many sources as desired.\n\nFor lower-quality versions of source photos such as downstream edits\nor previews, provide a lower priority such as ``--priority 30``\n(default is 10). These will be collected if the original (high-priority)\ncopy is unavailable. Alternate versions are matched using their\ntimestamp and filename.\n\nPrevious versions of the database are given unique names and not overwritten.\n\nIf the photos are stored on an SSD or RAID array, use\n``--storage-type SSD`` or ``--storage-type RAID`` and\nchecksum and EXIF checks will be performed by multiple workers.\n\nTo check the integrity of media files before indexing them,\nuse the ``--check-integrity`` flag.\nIntegrity checking has additional dependencies; install them with\n``pip install .[check-mi]``\n\nCollect files into a storage folder\n-----------------------------------\n\nNow that PhotoManager knows what photos you want to store,\ncollect them into a storage folder:\n\n.. code-block:: bash\n\n    photomanager collect --db db.json --destination /path/to/destination\n\nThis will copy the highest-priority versions of photos\nnot already stored into the destination folder and\ngive them consistent paths based on their\ntimestamps, checksums, and original names.\n\n::\n\n    ├── 2015\n    │   ├── 01-Jan\n    │   │   ├── 2015-01-04_10-22-03-a927bc3-IMG_0392.JPG\n    │   │   └── 2015-01-31_19-20-13-ce028af-IMG_0782.JPG\n    │   └── 02-Feb\n    │       └── 2015-02-30_02-40-43-9637179-AWK_0060.jpg\n    ├── 2016\n    │   └── 05-May\n    │       ├── 2018-05-24_00-31-08-bf3ed29-IMG_8213.JPG\n    │       └── 2018-05-29_20-13-16-39a4187-IMG_8591.MOV\n    ├── 2017\n    │   └── 12-Dec\n    │       ├── 2017-12-25_20-32-41-589c151-DSC_8705.JPG\n    │       └── 2017-12-25_20-32-41-4bb6987-DSC_8705.NEF\n\nStored photo paths in the database are relative to the ``destination`` folder,\nso the library is portable, and the same database can be shared across\nlibrary copies. Recommended syncing tools are ``rsync`` and ``rclone``.\n\nIndexing and collection can be repeated\nas new sources of photos are found and collected.\nThe ``import`` command performs both these actions in a single command:\n\n.. code-block:: bash\n\n    photomanager import --db db.json --destination /path/to/destination /path/to/source/directory\n\nVerify stored photos against bit rot or modification\n----------------------------------------------------\n\n.. code-block:: bash\n\n    photomanager verify --db db.json --destination /path/to/destination\n\nIf the photos are stored on an SSD or RAID array,\nuse ``--storage-type SSD`` or ``--storage-type RAID`` and\nmultiple files will be verified in parallel.\n\nNote that this can only detect unexpected modifications;\nit cannot undo changes it detects.\nTherefore, backing up the storage directory to multiple locations\n(such as with a `3-2-1 backup \u003chttps://github.com/geerlingguy/my-backup-plan\u003e`_) is recommended.\n\nUsage instructions\n==================\n\nUse the ``--help`` argument to see instructions for each command\n\n::\n\n    photomanager --help\n    Usage: photomanager [OPTIONS] COMMAND [ARGS]...\n\n    Options:\n      --help  Show this message and exit.\n\n    Commands:\n      clean    Remove lower-priority alternatives of stored items\n      collect  Collect highest-priority items into storage\n      create   Create an empty database\n      import   Index items and collect to directory\n      index    Find and add items to database\n      stats    Get database statistics\n      verify   Verify checksums of stored items\n\nCreate database\n---------------\n`This command is only needed if you want to specify a\nnon-default hashing algorithm or timezone.`\n\nSupported hashes are blake2b-256 (the default) and sha256.\nThese are equivalent to ``b2sum -l 256`` and ``sha256sum``, respectively.\nBLAKE2b is recommended as it is faster (and stronger) than SHA-2,\nresulting in noticeably faster indexing/verification on fast storage,\nand less CPU usage on slow storage.\n\n::\n\n    Usage: photomanager create [OPTIONS]\n\n      Create a database. Save a new version if it already exists.\n\n    Options:\n      --db FILE                       PhotoManager database filepath (.json). Add\n                                      extensions .zst or .gz to compress.\n                                      [required]\n      --hash-algorithm [sha256|blake2b-256|blake3]\n                                      Hash algorithm (default=blake2b-256)\n      --timezone-default TEXT         Timezone to use when indexing timezone-naive\n                                      photos (example=\"-0400\", default=\"local\")\n      --debug                         Run in debug mode\n      -h, --help                      Show this message and exit.\n\nIndex photos\n------------\n\n::\n\n    Usage: photomanager index [OPTIONS] [PATHS]...\n\n      Index and add items to database\n\n    Options:\n      --db FILE                       PhotoManager database filepath (.json). Add\n                                      extensions .zst or .gz to compress.\n      --source DIRECTORY              Directory to index\n      --file FILE                     File to index\n      --exclude TEXT                  Name patterns to exclude\n      --skip-existing                 Don't index files that are already in the\n                                      database\n      --check-integrity               Check media integrity and don't index bad\n                                      files\n      --priority INTEGER              Priority of indexed photos (lower is\n                                      preferred, default=10)\n      --timezone-default TEXT         Timezone to use when indexing timezone-naive\n                                      photos (example=\"-0400\", default=\"local\")\n      --hash-algorithm [sha256|blake2b-256|blake3]\n                                      Hash algorithm to use if no database\n                                      provided (default=blake2b-256)\n      --storage-type [HDD|SSD|RAID]   Class of storage medium (HDD, SSD, RAID)\n      --debug                         Run in debug mode\n      --dump                          Print photo info to stdout\n      --dry-run                       Perform a dry run that makes no changes\n      -h, --help                      Show this message and exit.\n\nCollect photos\n--------------\n\n::\n\n    Usage: photomanager collect [OPTIONS]\n\n      Collect highest-priority items into storage\n\n    Options:\n      --db FILE                PhotoManager database path  [required]\n      --destination DIRECTORY  Photo storage base directory  [required]\n      --debug                  Run in debug mode\n      --dry-run                Perform a dry run that makes no changes\n      --collect-db             Also save the database within destination\n      -h, --help               Show this message and exit.\n\nVerify photos\n-------------\n\n::\n\n    Usage: photomanager verify [OPTIONS]\n\n      Verify checksums of stored items\n\n    Options:\n      --db FILE                      PhotoManager database path  [required]\n      --destination DIRECTORY        Photo storage base directory  [required]\n      --subdir TEXT                  Verify only items within subdirectory\n      --storage-type [HDD|SSD|RAID]  Class of storage medium (HDD, SSD, RAID)\n      --random-fraction FLOAT        Verify a randomly sampled fraction of the\n                                     photos\n      --debug                        Run in debug mode\n      -h, --help                     Show this message and exit.\n\nRemove unnecessary duplicates\n-----------------------------\n\n::\n\n    Usage: photomanager clean [OPTIONS]\n\n      Remove lower-priority alternatives of stored items\n\n    Options:\n      --db FILE                PhotoManager database path  [required]\n      --destination DIRECTORY  Photo storage base directory  [required]\n      --subdir TEXT            Remove only items within subdirectory\n      --debug                  Run in debug mode\n      --dry-run                Perform a dry run that makes no changes\n      -h, --help               Show this message and exit.\n\nDatabase file format\n====================\n\nThe database is a json file, optionally gzip or zstd-compressed.\nIt takes this form:\n\n.. code-block:: json\n\n    {\n      \"version\": 3,\n      \"hash_algorithm\": \"blake2b-256\",\n      \"timezone_default\": \"local\",\n      \"photo_db\": {\n        \"\u003cuid\u003e\": [\n          \"\u003cphoto\u003e\",\n          \"\u003cphoto\u003e\",\n          \"...\"\n        ]\n      },\n      \"command_history\": {\n        \"\u003ctimestamp\u003e\": \"\u003ccommand\u003e\"\n      }\n    }\n\nwhere an example photo has the form:\n\n.. code-block:: json\n\n    {\n      \"chk\": \"881f279108bcec5b6e...\",\n      \"src\": \"/path/to/photo_123.jpg\",\n      \"dt\": \"2021:03:29 06:40:00+00:00\",\n      \"ts\": 1617000000,\n      \"fsz\": 123456,\n      \"sto\": \"2021/03-Mar/2021-03-29_02-40-00-881f279-photo_123.jpg\",\n      \"prio\": 10,\n      \"tzo\": -14400.0\n    }\n\nAttributes:\n\n:chk (str):   Checksum of photo file\n:src (str):   Absolute path where photo was found\n:dt (str):    Datetime string for best estimated creation date (original)\n:ts (float):  POSIX timestamp of best estimated creation date (derived)\n:fsz (int):   Photo file size, in bytes\n:sto (str):   Relative path where photo is stored, empty if not stored\n:prio (int):  Photo priority (lower is preferred)\n:tzo (float): Local time zone offset (optional)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faaronkollasch%2Fphotomanager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faaronkollasch%2Fphotomanager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faaronkollasch%2Fphotomanager/lists"}