Skip to content

Structure of the code

Rhet Turnbull edited this page May 25, 2020 · 22 revisions

osxphotos is built around a set of classes that each handle a particular aspect of the Photos library:

osxphotos classes

  • PhotosDB: reads the Photos library database and extract all relevant metadata about each photo. This is starting point for any use of osxphotos.
  • PhotoInfo: represents an individual photo in the library and all associated metadata.
  • AlbumInfo: represents an album in the library.
  • FolderInfo: represents a folder in the library.
  • PlaceInfo: represents the reverse geolocation info (e.g. the address or place name) associated with a photo.
  • ExifTool: provides an interface to the exiftool tool for manipulating EXIF information associated with a photo.

The package code is organized in osxphotos per the table below. Most of the files contain doc strings describing what they contain.

Source files

file description
__init__.py this is what gets read by "import osxphotos"
__main__.py the command line interface, what gets executed with python3 -m osxphotos
_applescript/ contains a copy of py-applescript used to interact directly with Photos via applescript to force the download of photos from iCloud if needed
_constants.py constants used by the other modules
_version.py version string
albuminfo.py AlbumInfo and FolderInfo classes
datetime_formatter.py utility methods for formatting datetime objects
exiftool.py ExifTool class
photoinfo/ PhotoInfo class and associated mixin classes
photosdb/ PhotosDB class and associated mixin classes
placeinfo.py PlaceInfo class
templates contains mako templates for sidecar export (though the name is similar, these templates are not associated with templates.py
templates/xmp_sidecar.mako mako template used to create XMP sidecar files
utils.py various utility methods--this file is getting bloated and needs to be refactored

How osxphotos works

Broadly, osxphotos works like this:

  1. Opens a readonly copy of photos.db. If the file is locked, a temporary copy will be created then opened.
  2. Reads photos.db to determine which version of Photos created the library (PhotosDB._get_db_version())
  3. If library created with Photos version 2, 3, or 4, PhotosDB._process_database4() processes the database file to extract all required information.
  4. If library created with Photos 5, the actual database is Photos.sqlite so this is opened readonly or copied to a temporary file if locked, then PhotosDB._process_database5() is called to process the database file and extra all required information.
  5. _process_database4 and _process_database5 execute several SQL queries to extract the required data. These methods populate a series of data structures in PhotosDB which are then used by the rest of code to create the PhotoInfo, AlbumInfo, and FolderInfo objects. These database structures are ugly...in general, they flatten the SQL database into several different python dicts. The "master" dict is called _dbphotos. The key is the UUID of the photo and the value is another dict containing details about the photo. Read the code for PhotosDB.__init__() for additional details. Each of the data structures is documented in the code.
  6. The PhotoInfo, AlbumInfo, and FolderInfo objects are created as needed. For example, PhotosDB.photos() returns a list of PhotoInfo objects representing the photos in the database and PhotoInfo.album_info returns a list of AlbumInfo objects representing the albums the photo is contained in.

Modifying the code

As an example, here's a rough outline of what you'd need to do to add support for a new metadata attribute:

  1. Reverse engineer Photos.sqlite and develop an sql query that extracts the data
  2. Add this query to PhotosDB._process_database5 (assuming a Photos 5 library) and store the data in appropriate data structure accessible through _dbphotos
  3. Add a property to PhotoInfo which accesses _dbphotos through self._db which points to the PhotosDB object
  4. If exposing the property to the templating system (PhotoInfo.render_template()) and add it to templates.py (which is relatively self-documenting)
  5. If exposing the property to the command line interface, add it to __main__.py -- you'll want to look at query_options, _query(), export(), and export_photo() at a minimum to ensure the command line tool can access your new attribute.
  6. Add at least one test to tests/!
  7. Ensure all tests pass python3 -m pytest tests/
  8. Submit a pull request :-)

home

Clone this wiki locally