Coder Social home page Coder Social logo

rhettbull / photos_time_warp Goto Github PK

View Code? Open in Web Editor NEW
11.0 4.0 1.0 20.69 MB

Batch adjust the date, time, or timezone of photos in Apple Photos from the Mac command line.

License: MIT License

Python 100.00%
photos metadata apple macos exif exiftool osx cli command-line

photos_time_warp's Introduction

photos_time_warp

Code style: black License: MIT

All Contributors

Batch adjust the date, time, or timezone for photos in Apple Photos. Works on macOS only.

Note: All functionality in photos_time_warp has been added to osxphotos as of 1 May 2022. I will no longer be maintaining photos_time_warp and will eventually archive this repository. If you need the functionality in photos_time_warp, I recommend you take a look at osxphotos which offers this same functionality in addition to many other useful tools for working with Photos.

photos_time_warp operates on photos selected in Apple Photos. To use it, open Photos, select the photos for which you'd like to adjust the date/time/timezone, then run photos_time_warp from the command line:

photos_time_warp --date 2021-09-10 --time-delta "-1 hour" --timezone -0700 --verbose

This example sets the date for all selected photos to 2021-09-10, subtracts 1 hour from the time of each photo, and sets the timezone of each photo to GMT -07:00 (Pacific Daylight Time).

photos_time_warp has been well tested on macOS Catalina (10.15). It should work on macOS Big Sur (11.0) and macOS Monterey (12.0) but I have not been able to test this. It will not work on macOS Mojave (10.14) or earlier as the Photos database format is different.

Caution: This app directly modifies your Photos library database using undocumented features. It may corrupt, damage, or destroy your Photos library. Use at your own caution. I strongly recommend you make a backup of your Photos library before using this script (e.g. use Time Machine). See also Warranty.

Examples

Add 1 day to the date of each photo

photos_time_warp --date-delta 1

or

photos_time_warp --date-delta "+1 day"

Set the date of each photo to 23 April 2020 and add 3 hours to the time

photos_time_warp --date 2020-04-23 --time-delta "+3 hours"

or

photos_time_warp --date 2020-04-23 --time-delta "+03:00:00"

Set the time of each photo to 14:30 and set the timezone to UTC +1:00 (Central European Time)

photos_time_warp --time 14:30 --timezone +01:00

or

photos_time_warp --time 14:30 --timezone +0100

Subtract 1 week from the date for each photo, add 3 hours to the time, set the timezone to UTC -07:00 (Pacific Daylight Time) and also use exiftool to update the EXIF metadata accordingly in the original file; use --verbose to print additional details

photos_time_warp --date-delta "-1 week" --time-delta "+3 hours" --timezone -0700 --push-exif --verbose

For this to work, you'll need to install the third-party exiftool utility. If you use homebrew you can do this with brew install exiftool.

Set the timezone to UTC +03:00 for each photo but keep the time the same (that is, don't adjust time for the new timezone)

photos_time_warp --timezone 0300 --match-time

Note on timezones and times: In Photos, when you change the timezone, Photos assumes the time itself was correct for the previous timezone and adjusts the time accordingly to the new timezone. E.g. if the photo's time is 13:00 and the timezone is GMT -07:00 and you adjust the timezone one hour east to GMT -06:00, Photos will change the time of the photo to 14:00. photos_time_warp follows this behavior. Using --match-time allows you to adjust the timezone but keep the same time without adjustment. For example, if your camera clock was correct but lacked timezone information and you took photos in one timezone but imported them to photos in another, Photos will add the timezone of the computer at time of import. You can use photos_time_warp to adjust the timezone but keep the time using --match-time.

Compare the date/time/timezone of selected photos with the date/time/timezone in the photos' original EXIF metadata

photos_time_warp --compare-exif

--compare-exif output

Installation

I recommend you install photos_time_warp with pipx. The easiest way to do this on a Mac is to use homebrew:

  • Open Terminal (search for Terminal in Spotlight or look in Applications/Utilities)
  • Install homebrew according to instructions at https://brew.sh/
  • Type the following into Terminal: brew install pipx
  • Then type this: pipx install git+https://github.com/RhetTbull/photos_time_warp.git
  • Now you should be able to run photos_time_warp by typing: photos_time_warp.
  • Note: photos_time_warp will also install a shortcut command ptw that can be used to start photos_time_warp.

Once you've installed photos_time_warp with pipx, to upgrade to the latest version:

pipx upgrade photos_time_warp

Usage

$ photos_time_warp --help
Usage: photos_time_warp [OPTIONS]

  photos_time_warp: adjust date/time/timezone of photos in Apple Photos.
  Changes will be applied to all photos currently selected in Photos.
  photos_time_warp cannot operate on photos selected in a Smart Album; select
  photos in a regular album or in the 'All Photos' view.

Specify one or more command: [at least 1 required]
  -d, --date DATE             Set date for selected photos. Format is 'YYYY-MM-
                              DD'.
  -D, --date-delta DELTA      Adjust date for selected photos by DELTA. Format
                              is one of: '±D days', '±W weeks', '±D' where D is
                              days
  -t, --time TIME             Set time for selected photos. Format is one of
                              'HH:MM:SS', 'HH:MM:SS.fff', 'HH:MM'.
  -T, --time-delta DELTA      Adjust time for selected photos by DELTA time.
                              Format is one of '±HH:MM:SS', '±H hours' (or hr),
                              '±M minutes' (or min), '±S seconds' (or sec),
                              '±S' (where S is seconds)
  -z, --timezone TIMEZONE     Set timezone for selected photos as offset from
                              UTC. Format is one of '±HH:MM', '±H:MM', or
                              '±HHMM'. The actual time of the photo is not
                              adjusted which means, somewhat
                              counterintuitively, that the time in the new
                              timezone will be different. For example, if photo
                              has time of 12:00 and timezone of GMT+01:00 and
                              new timezone is specified as '--timezone +02:00'
                              (one hour ahead of current GMT+01:00 timezone),
                              the photo's new time will be 13:00 GMT+02:00,
                              which is equivalent to the old time of
                              12:00+01:00. This is the same behavior exhibited
                              by Photos when manually adjusting timezone in the
                              Get Info window. See also --match-time.
  -i, --inspect               Print out the date/time/timezone for each
                              selected photo without changing any information.
  -c, --compare-exif          Compare the EXIF date/time/timezone for each
                              selected photo to the same data in Photos.
                              Requires the third-party exiftool utility be
                              installed (see https://exiftool.org/). See also
                              --add-to-album.
  -p, --push-exif             Push date/time and timezone for selected photos
                              from Photos to the EXIF metadata in the original
                              file in the Photos library. Requires the third-
                              party exiftool utility be installed (see
                              https://exiftool.org/). Using this option
                              modifies the *original* file of the image in your
                              Photos library. --push-exif will be executed
                              after any other updates are performed on the
                              photo. See also --pull-exif.
  -P, --pull-exif             Pull date/time and timezone for selected photos
                              from EXIF metadata in the original file into
                              Photos and update the associated data in Photos
                              to match the EXIF data. --pull-exif will be
                              executed before any other updates are performed
                              on the photo. It is possible for images to have
                              missing EXIF data, for example the date/time
                              could be set but there might be no timezone set
                              in the EXIF metadata. Missing data will be
                              handled thusly: if date/time/timezone are all
                              present in the EXIF data, the photo's
                              date/time/timezone will be updated. If timezone
                              is missing but date/time is present, only the
                              photo's date/time will be updated.  If date/time
                              is missing but the timezone is present, only the
                              photo's timezone will be updated unless --use-
                              file-time is set in which case, the photo's file
                              modification time will be used in place of EXIF
                              date/time. If the date is present but the time is
                              missing, the time will be set to 00:00:00.
                              Requires the third-party exiftool utility be
                              installed (see https://exiftool.org/). See also
                              --push-exif.

Options:
  -m, --match-time            When used with --timezone, adjusts the photo time
                              so that the timestamp in the new timezone matches
                              the timestamp in the old timezone. For example,
                              if photo has time of 12:00 and timezone of
                              GMT+01:00 and new timezone is specified as '--
                              timezone +02:00' (one hour ahead of current
                              GMT+01:00 timezone), the photo's new time will be
                              12:00 GMT+02:00. That is, the timezone will have
                              changed but the timestamp of the photo will match
                              the previous timestamp. Use --match-time when the
                              camera's time was correct for the time the photo
                              was taken but the timezone was missing or wrong
                              and you want to adjust the timezone while
                              preserving the photo's time. See also --timezone.
  -f, --use-file-time         When used with --pull-exif, the file modification
                              time will be used if date/time is missing from
                              the EXIF data.
  -a, --add-to-album ALBUM    When used with --compare-exif, adds any photos
                              with date/time/timezone differences between
                              Photos/EXIF to album ALBUM.  If ALBUM does not
                              exist, it will be created.
  -V, --verbose               Show verbose output.
  -L, --library PHOTOS_LIBRARY_PATH
                              Path to Photos library (e.g. '~/Pictures/Photos\
                              Library.photoslibrary'). This is not likely
                              needed as photos_time_warp will usually be able
                              to locate the path to the open Photos library.
                              Use --library only if you get an error that the
                              Photos library cannot be located.
  -e, --exiftool-path PATH    Optional path to exiftool executable (will look
                              in $PATH if not specified) for those options
                              which require exiftool.
  --plain                     Plain text mode.  Do not use rich output.
  --mono                      Monochrome mode.  Do not use colored output.
  --dark                      Dark mode.  Use dark colors.
  --light                     Light mode.  Use light colors.
  -o, --output-file FILENAME  Output file. If not specified, output is written
                              to stdout.

Other options:
  --version                   Show the version and exit.
  --help                      Show this message and exit.

photos_time_warp uses colored output which is controlled by the --dark,
--light, --plain, and --mono flags.

If you don't like the default colors you can specify your own by creating a
file named '~/.config/photos_time_warp/colors.cfg'. If this file exists,
photos_time_warp will use the colors specified in this file as the default
colors.

See https://github.com/RhetTbull/photos_time_warp/blob/main/colors.cfg for
sample colors.cfg file.

Implementation Details

This app is a bit of a hack. Photos provides a way to change the date and time of a photo using AppleScript but does not provide a way to change the timezone. Date/time adjustments are completed using AppleScript (via python using PhotoScript) and timezone adjustments are done by directly modifying the underlying Photos database (e.g. ~/Pictures/Photos\ Library.photoslibrary/database/Photos.sqlite). Apple does not document the structure of this database--a sqlite database which is actually a CoreData store--so it's possible this script modifies something it shouldn't (or fails to modify something it should) and thus corrupts the database. I've spent considerable time reverse engineering the Photos database for the osxphotos project so I am fairly confident the modifications are safe...but, see the Warranty.

If you want to peek even further under the hood, read on:

Photos maintains a lock on the database, even when Photos is closed, and the python sqlite3 API will not open the database while Photo's maintains its lock. This issue is unique to the python sqlite API; sqlite itself has no problem with this. To get around this limitation, I use a custom sqlite python wrapper that calls the system sqlite library directly using python's python-to-C API. It's very hacky but appears to work OK (at least in my testing on macOS Catalina).

Contributing

Feedback and contributions of all kinds welcome! Please open an issue if you would like to suggest enhancements or bug fixes.

Related Projects

  • osxphotos Export photos and metadata from Apple Photos.
  • exif2findertags Read EXIF metadata from image and video files and convert it to macOS Finder tags and/or Finder comments.

Warranty

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Contributors ✨

Thanks goes to these wonderful people (emoji key):


John Muccigrosso

🐛 🤔

Thomas K. Running

💻 🐛

This project follows the all-contributors specification. Contributions of any kind welcome!

photos_time_warp's People

Contributors

allcontributors[bot] avatar rhettbull avatar tkrunning avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

Forkers

tkrunning

photos_time_warp's Issues

--pull-exif fails if photo has invalid EXIF date

I would like to selectively pull from the EXIF information of the original image files to restore their original date/timestamp in Photos.

I let my daughter use an old camera with a dead battery, so the EXIF information had all the timestamps set to the factory default (January 1st, 1970 00:00:00 UTC) when she went to import them. She used ⌘A (which selected ALL photos, not just those from the camera), and set them to the current date. Somehow Apple Photos set the oldest photo to and then added time so we now have pictures well into 2073.

I would like to fix this situation by using photos_time_warp if possible.

Add --compare-exif feature

It would be possible to add a --compare-exif feature that compared Photos data to the exif data. My photoscript wrapper for Photos' AppleScript can create albums and add photos to it (osxphotos) uses this for the --add-to-album feature which could be added to photos_time_warp so you could do this:

photos_time_warp --compare-exif --add-to-album "Photos with mismatched time"

And get a report that showed current time/TZ in photos, current time/TZ in exif and adds all those photos to an album named "Photos with mismatched time"

Originally posted by @RhetTbull in #13 (reply in thread)

Add --exiftool option

Add --exiftool to also modify the EXIF data in the original image in the library.

Add --json output for testing

The tests fail if terminal window isn't configured correctly. This would be eliminated if output had a --json format that the tests could parse.

--exiftool gets timezones wrong in image file

I have an image that I took in Turkey. Photos thinks it's from one timezone west of that, since I imported it in Italy. (I get that by looking at "Adjust Date and Time".) exiftool reports this:

Modify Date                     : 2019:10:17 12:17:31
Date/Time Original              : 2019:10:17 12:17:31
Create Date                     : 2019:10:17 12:17:31

You can see that there's no TZ info. I'd like to fix that by simply adding a TZ. The time is correct for that TZ, that is, I took the photo at 12:17 in Turkey.

I duplicated the image to have something to test on. Photos puts the duplicate file into my current TZ of New York, which seems a bit odd to me, but whatever. The displayed time is 6 hours earlier than the one in the file, or 6:17, so it seems to be internally tracking the file's original TZ or at least using it to set a new time for the duplicate.

If I run exiftool directly on this duplicate image file: exiftool '-OffsetTimeOriginal=+03:00', I get this:

Modify Date                     : 2019:10:17 12:17:31
Date/Time Original              : 2019:10:17 12:17:31
Create Date                     : 2019:10:17 12:17:31
Date/Time Original              : 2019:10:17 12:17:31+03:00

So just the TZ bit gets added. Perfect. Photos doesn't know about this of course, so nothing changes there.

If I use photos_time_warp to do the update: photos_time_warp -V --timezone +0300 --exiftool.

Photos now updates both the TZ and the time it displays for the photo to match this shift. Because it's an 7-hour difference, Photos shows the time as 13:17. If I run exiftool on the file, I get this:

Modify Date                     : 2019:10:17 12:17:31
Date/Time Original              : 2019:10:17 06:17:31
Create Date                     : 2019:10:17 06:17:31
Date Created                    : 2019:10:17
Date/Time Original              : 2019:10:17 06:17:31+03:00
Date/Time Created               : 2019:10:17 06:17:31+03:00

There are two new tags in addition to the modified Date/Time Created, but what's off is the time. The TZ is there correctly, but instead of leaving the rest of the time alone, photos_time_warp has used the displayed time in Photos to replace it. That's not right. Note too that Photos thinks this photo was taken at 1317 in GMT+3, which is not what the exif says.

I suspect that this might be as simple as not setting the time when it's not included in the command, but it may be trickier than that.

`--push-exif` errors with `OperationalError: no such column: ZDETECTEDFACE.ZBALDTYPE`

First off, thanks for a very useful tool!

I'm correcting the time of some images in Photos.app, which works fine, however if I try to sync those changes to the original file I get the following error/traceback:

Ξ ~ → photos_time_warp --push-exif
2021-12-26 14:11:15,649 - WARNING - photosdb.py - 100 - WARNING: This module has only been tested with macOS versions [10.12, 10.13, 10.14, 10.15, 10.16, 11.0, 11.1, 11.2, 11.3, 11.4, 11.5]: you have Darwin, OS version: 12.0
╭───────────────────── Traceback (most recent call last) ──────────────────────╮
│ /Users/tkrunning/.local/bin/photos_time_warp:8 in <module>                   │
│                                                                              │
│   5 from photos_time_warp.cli import cli                                     │
│   6 if __name__ == '__main__':                                               │
│   7 │   sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])     │
│ ❱ 8 │   sys.exit(cli())                                                      │
│   9                                                                          │
│                                                                              │
│ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
│ │ __annotations__ = {}                                                     │ │
│ │    __builtins__ = <module 'builtins' (built-in)>                         │ │
│ │      __cached__ = None                                                   │ │
│ │         __doc__ = None                                                   │ │
│ │        __file__ = '/Users/tkrunning/.local/bin/photos_time_warp'         │ │
│ │      __loader__ = <_frozen_importlib_external.SourceFileLoader object at │ │
│ │                   0x102a110f0>                                           │ │
│ │        __name__ = '__main__'                                             │ │
│ │     __package__ = None                                                   │ │
│ │        __spec__ = None                                                   │ │
│ │             cli = <PhotosTimeWarpCommand cli>                            │ │
│ │              re = <module 're' from                                      │ │
│ │                   '/opt/homebrew/Cellar/[email protected]/3.10.1/Frameworks/P… │ │
│ │             sys = <module 'sys' (built-in)>                              │ │
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
│                                                                              │
│ /Users/tkrunning/.local/pipx/venvs/photos-time-warp/lib/python3.10/site-pack │
│ ages/click/core.py:1137 in __call__                                          │
│                                                                              │
│   1134 │                                                                     │
│   1135 │   def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any:       │
│   1136 │   │   """Alias for :meth:`main`."""                                 │
│ ❱ 1137 │   │   return self.main(*args, **kwargs)                             │
│   1138                                                                       │
│   1139                                                                       │
│   1140 class Command(BaseCommand):                                           │
│                                                                              │
│ ╭─────────────── locals ───────────────╮                                     │
│ │   args = ()                          │                                     │
│ │ kwargs = {}                          │                                     │
│ │   self = <PhotosTimeWarpCommand cli> │                                     │
│ ╰──────────────────────────────────────╯                                     │
│                                                                              │
│ /Users/tkrunning/.local/pipx/venvs/photos-time-warp/lib/python3.10/site-pack │
│ ages/click/core.py:1062 in main                                              │
│                                                                              │
│   1059 │   │   try:                                                          │
│   1060 │   │   │   try:                                                      │
│   1061 │   │   │   │   with self.make_context(prog_name, args, **extra) as c │
│ ❱ 1062 │   │   │   │   │   rv = self.invoke(ctx)                             │
│   1063 │   │   │   │   │   if not standalone_mode:                           │
│   1064 │   │   │   │   │   │   return rv                                     │
│   1065 │   │   │   │   │   # it's not safe to `ctx.exit(rv)` here!           │
│                                                                              │
│ ╭─────────────────────────────── locals ───────────────────────────────╮     │
│ │                args = []                                             │     │
│ │        complete_var = None                                           │     │
│ │                 ctx = <cloup._context.Context object at 0x102b260b0> │     │
│ │               extra = {}                                             │     │
│ │           prog_name = 'photos_time_warp'                             │     │
│ │                self = <PhotosTimeWarpCommand cli>                    │     │
│ │     standalone_mode = True                                           │     │
│ │ windows_expand_args = True                                           │     │
│ ╰──────────────────────────────────────────────────────────────────────╯     │
│                                                                              │
│ /Users/tkrunning/.local/pipx/venvs/photos-time-warp/lib/python3.10/site-pack │
│ ages/click/core.py:1404 in invoke                                            │
│                                                                              │
│   1401 │   │   │   echo(style(message, fg="red"), err=True)                  │
│   1402 │   │                                                                 │
│   1403 │   │   if self.callback is not None:                                 │
│ ❱ 1404 │   │   │   return ctx.invoke(self.callback, **ctx.params)            │
│   1405 │                                                                     │
│   1406 │   def shell_complete(self, ctx: Context, incomplete: str) -> t.List │
│   1407 │   │   """Return a list of completions for the incomplete value. Loo │
│                                                                              │
│ ╭─────────────────────── locals ────────────────────────╮                    │
│ │  ctx = <cloup._context.Context object at 0x102b260b0> │                    │
│ │ self = <PhotosTimeWarpCommand cli>                    │                    │
│ ╰───────────────────────────────────────────────────────╯                    │
│                                                                              │
│ /Users/tkrunning/.local/pipx/venvs/photos-time-warp/lib/python3.10/site-pack │
│ ages/click/core.py:763 in invoke                                             │
│                                                                              │
│    760 │   │                                                                 │
│    761 │   │   with augment_usage_errors(__self):                            │
│    762 │   │   │   with ctx:                                                 │
│ ❱  763 │   │   │   │   return __callback(*args, **kwargs)                    │
│    764 │                                                                     │
│    765 │   def forward(                                                      │
│    766 │   │   __self, __cmd: "Command", *args: t.Any, **kwargs: t.Any  # no │
│                                                                              │
│ ╭────────────────────────────── locals ───────────────────────────────╮      │
│ │ _Context__callback = <function cli at 0x1097e4550>                  │      │
│ │     _Context__self = <cloup._context.Context object at 0x102b260b0> │      │
│ │               args = ()                                             │      │
│ │                ctx = <cloup._context.Context object at 0x102b260b0> │      │
│ │             kwargs = {                                              │      │
│ │                      │   'push_exif': True,                         │      │
│ │                      │   'date': None,                              │      │
│ │                      │   'date_delta': None,                        │      │
│ │                      │   'time': None,                              │      │
│ │                      │   'time_delta': None,                        │      │
│ │                      │   'timezone': None,                          │      │
│ │                      │   'inspect': False,                          │      │
│ │                      │   'compare_exif': False,                     │      │
│ │                      │   'pull_exif': False,                        │      │
│ │                      │   'match_time': False,                       │      │
│ │                      │   ... +5                                     │      │
│ │                      }                                              │      │
│ ╰─────────────────────────────────────────────────────────────────────╯      │
│                                                                              │
│ /Users/tkrunning/.local/pipx/venvs/photos-time-warp/lib/python3.10/site-pack │
│ ages/photos_time_warp/cli.py:482 in cli                                      │
│                                                                              │
│   479 │   │   )                                                              │
│   480 │                                                                      │
│   481 │   if any([push_exif, pull_exif]):                                    │
│ ❱ 482 │   │   exif_updater = ExifUpdater(                                    │
│   483 │   │   │   library_path=library, verbose=verbose, exiftool_path=exift │
│   484 │   │   )                                                              │
│   485                                                                        │
│                                                                              │
│ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
│ │                      add_to_album = None                                 │ │
│ │                      compare_exif = False                                │ │
│ │                              date = None                                 │ │
│ │                        date_delta = None                                 │ │
│ │                     exiftool_path = '/opt/homebrew/bin/exiftool'         │ │
│ │                           inspect = False                                │ │
│ │                           library = None                                 │ │
│ │                        match_time = False                                │ │
│ │                            photos = [                                    │ │
│ │                                     │   <photoscript.Photo object at     │ │
│ │                                     0x1097e01f0>                         │ │
│ │                                     ]                                    │ │
│ │                             plain = False                                │ │
│ │                         pull_exif = False                                │ │
│ │                         push_exif = True                                 │ │
│ │                              time = None                                 │ │
│ │                        time_delta = None                                 │ │
│ │                          timezone = None                                 │ │
│ │           update_photo_date_time_ = functools.partial(<function          │ │
│ │                                     update_photo_date_time at            │ │
│ │                                     0x1097d7400>, date=None, time=None,  │ │
│ │                                     date_delta=None, time_delta=None)    │ │
│ │ update_photo_time_for_new_timezone_ functools.partial(<function          │ │
│ │                                   = update_photo_time_for_new_timezone   │ │
│ │                                     at 0x1097d7d00>, library_path=None)  │ │
│ │                          verbose_ = False                                │ │
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
│                                                                              │
│ /Users/tkrunning/.local/pipx/venvs/photos-time-warp/lib/python3.10/site-pack │
│ ages/photos_time_warp/exif_updater.py:50 in __init__                         │
│                                                                              │
│    47 │   │   exiftool_path: Optional[str] = None,                           │
│    48 │   ):                                                                 │
│    49 │   │   self.library_path = library_path                               │
│ ❱  50 │   │   self.db = PhotosDB(self.library_path)                          │
│    51 │   │   self.verbose = verbose or noop                                 │
│    52 │   │   self.exiftool_path = exiftool_path                             │
│    53 │   │   self.tzinfo = PhotoTimeZone(library_path=self.library_path)    │
│                                                                              │
│ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
│ │ exiftool_path = '/opt/homebrew/bin/exiftool'                             │ │
│ │  library_path = None                                                     │ │
│ │          self = <photos_time_warp.exif_updater.ExifUpdater object at     │ │
│ │                 0x10979ffa0>                                             │ │
│ │       verbose = <function verbose at 0x102b31bd0>                        │ │
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
│                                                                              │
│ /Users/tkrunning/.local/pipx/venvs/photos-time-warp/lib/python3.10/site-pack │
│ ages/osxphotos/photosdb/photosdb.py:331 in __init__                          │
│                                                                              │
│    328 │   │   if int(self._db_version) <= int(_PHOTOS_4_VERSION):           │
│    329 │   │   │   self._process_database4()                                 │
│    330 │   │   else:                                                         │
│ ❱  331 │   │   │   self._process_database5()                                 │
│    332 │   │                                                                 │
│    333 │   │   self._db_connection, _ = self.get_db_connection()             │
│    334                                                                       │
│                                                                              │
│ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
│ │            _ = 'database'                                                │ │
│ │       dbfile = PosixPath('/Volumes/Photos/Photos                         │ │
│ │                Library.photoslibrary/database/Photos.sqlite')            │ │
│ │       dbpath = PosixPath('/Volumes/Photos/Photos                         │ │
│ │                Library.photoslibrary/database')                          │ │
│ │     exiftool = None                                                      │ │
│ │ library_path = '/Volumes/Photos/Photos Library.photoslibrary'            │ │
│ │        major = '0'                                                       │ │
│ │ masters_path = '/Volumes/Photos/Photos Library.photoslibrary/originals'  │ │
│ │         self = osxphotos.PhotosDB(dbfile='/Volumes/Photos/Photos         │ │
│ │                Library.photoslibrary/database/photos.db')                │ │
│ │       system = 'Darwin'                                                  │ │
│ │          ver = '12'                                                      │ │
│ │      verbose = <function noop at 0x107308280>                            │ │
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
│                                                                              │
│ /Users/tkrunning/.local/pipx/venvs/photos-time-warp/lib/python3.10/site-pack │
│ ages/osxphotos/photosdb/photosdb.py:2476 in _process_database5               │
│                                                                              │
│   2473 │   │                                                                 │
│   2474 │   │   # process face info                                           │
│   2475 │   │   verbose("Processing face details.")                           │
│ ❱ 2476 │   │   self._process_faceinfo()                                      │
│   2477 │   │                                                                 │
│   2478 │   │   # process search info                                         │
│   2479 │   │   verbose("Processing photo labels.")                           │
│                                                                              │
│ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
│ │               album = '4ABB0E8B-915A-4734-9F6A-5D7E7C7222B5'             │ │
│ │            album_id = '4ABB0E8B-915A-4734-9F6A-5D7E7C7222B5'             │ │
│ │          album_join = 'Z_27ASSETS.Z_3ASSETS'                             │ │
│ │          album_sort = 'Z_27ASSETS.Z_FOK_3ASSETS'                         │ │
│ │          album_uuid = 'EB51DAC7-3EB5-43DC-9CEB-D0C48A698693'             │ │
│ │    asset_album_join = 'Z_27ASSETS.Z_27ALBUMS'                            │ │
│ │   asset_album_table = 'Z_27ASSETS'                                       │ │
│ │         asset_table = 'ZASSET'                                           │ │
│ │          burst_uuid = '679B8980-724F-4A0E-9817-50EAD34221D5'             │ │
│ │                   c = <sqlite3.Cursor object at 0x1097569c0>             │ │
│ │                conn = <sqlite3.Connection object at 0x109788340>         │ │
│ │               delta = datetime.timedelta(seconds=7200)                   │ │
│ │         depth_state = 'ZASSET.ZDEPTHTYPE'                                │ │
│ │             details = {                                                  │ │
│ │                       │   '_uuid':                                       │ │
│ │                       '4ABB0E8B-915A-4734-9F6A-5D7E7C7222B5',            │ │
│ │                       │   'title': None,                                 │ │
│ │                       │   'cloudlocalstate': 0,                          │ │
│ │                       │   'cloudownerfirstname': None,                   │ │
│ │                       │   'cloudownderlastname': None,                   │ │
│ │                       │   'cloudownerhashedpersonid': None,              │ │
│ │                       │   'cloudlibrarystate': None,                     │ │
│ │                       │   'cloudidentifier': None,                       │ │
│ │                       │   'kind': 1506,                                  │ │
│ │                       │   'parentfolder': None,                          │ │
│ │                       │   ... +7                                         │ │
│ │                       }                                                  │ │
│ │                face = (23, 'B9D9FE59-9F78-4011-AA63-99754501D681')       │ │
│ │    folder_hierarchy = {                                                  │ │
│ │                       │   '256F09FC-1A80-4E0B-9552-4B7276B3BB92': {      │ │
│ │                       │   │   'D17BA6FC-70E2-44D5-9D2A-74BFAE2B57AC': {  │ │
│ │                       │   │   │                                          │ │
│ │                       '1091F303-6DEB-4892-9DCE-373CBC824ADD': {          │ │
│ │                       │   │   │   │                                      │ │
│ │                       '4E8361E9-5501-4F95-A85C-47263E8A4504': {          │ │
│ │                       │   │   │   │   │                                  │ │
│ │                       '6DB06A7A-52B6-44C7-89A0-8425DAF92B36': None       │ │
│ │                       │   │   │   │   }                                  │ │
│ │                       │   │   │   }                                      │ │
│ │                       │   │   }                                          │ │
│ │                       │   }                                              │ │
│ │                       }                                                  │ │
│ │            fullname = '_UNKNOWN_'                                        │ │
│ │     hdr_type_column = 'ZHDRTYPE'                                         │ │
│ │           imagedate = datetime.datetime(2016, 9, 1, 18, 25, 33)          │ │
│ │          import_fok = 'null'                                             │ │
│ │                info = {                                                  │ │
│ │                       │   '_uuid':                                       │ │
│ │                       'FFFF3748-ECE6-4E7D-B1E1-A04A9331E15A',            │ │
│ │                       │   'modelID': None,                               │ │
│ │                       │   'masterUuid': None,                            │ │
│ │                       │   'masterFingerprint':                           │ │
│ │                       'AbkCqk/ezuiyhagwcqZhwyvqAIJI',                    │ │
│ │                       │   'name': None,                                  │ │
│ │                       │   'lastmodifieddate_timestamp': None,            │ │
│ │                       │   'lastmodifieddate': None,                      │ │
│ │                       │   'imageTimeZoneOffsetSeconds': 7200,            │ │
│ │                       │   'imageDate_timestamp': 494439933,              │ │
│ │                       │   'imageDate': datetime.datetime(2016, 9, 1, 18, │ │
│ │                       25, 33,                                            │ │
│ │                       tzinfo=datetime.timezone(datetime.timedelta(secon… │ │
│ │                       │   ... +81                                        │ │
│ │                       }                                                  │ │
│ │             keyword = (                                                  │ │
│ │                       │   'Last days',                                   │ │
│ │                       │   'BA79808B-79DD-479A-BDD6-002E836C093D'         │ │
│ │                       )                                                  │ │
│ │        keyword_join = 'Z_1KEYWORDS.Z_38KEYWORDS'                         │ │
│ │       keyword_title = 'Last days'                                        │ │
│ │              parent = '6DB06A7A-52B6-44C7-89A0-8425DAF92B36'             │ │
│ │              person = (                                                  │ │
│ │                       │   25554,                                         │ │
│ │                       │   57512,                                         │ │
│ │                       │   'B7706124-392C-475D-A157-3C7BA1B1F0D7',        │ │
│ │                       │   'E2A8D747-2361-44D8-B221-9F9F08D15077'         │ │
│ │                       )                                                  │ │
│ │          photo_uuid = '6AB93675-DB48-4F5C-B7A7-15B205E16EFD'             │ │
│ │          photos_ver = 7                                                  │ │
│ │                  pk = 23                                                 │ │
│ │           pk_parent = None                                               │ │
│ │           root_uuid = ['B09B120A-F973-4C04-A96B-FE3EFDA83AFF']           │ │
│ │                 row = (                                                  │ │
│ │                       │   '368D2C0F-607B-4618-A304-B42318BFF50F',        │ │
│ │                       │   12146964,                                      │ │
│ │                       │   None,                                          │ │
│ │                       │   17,                                            │ │
│ │                       │   4,                                             │ │
│ │                       │   None                                           │ │
│ │                       )                                                  │ │
│ │             seconds = 7200                                               │ │
│ │                self = osxphotos.PhotosDB(dbfile='/Volumes/Photos/Photos  │ │
│ │                       Library.photoslibrary/database/photos.db')         │ │
│ │           signature = (                                                  │ │
│ │                       │   6527006,                                       │ │
│ │                       │   datetime.datetime(2016, 9, 1, 18, 25, 33,      │ │
│ │                       tzinfo=datetime.timezone(datetime.timedelta(secon… │ │
│ │                       │   3648,                                          │ │
│ │                       │   5472,                                          │ │
│ │                       │   'public.jpeg',                                 │ │
│ │                       │   0                                              │ │
│ │                       )                                                  │ │
│ │          sort_order = 3072                                               │ │
│ │         sql_missing = ' SELECT \n                ZASSET.ZUUID, \n        │ │
│ │                       ZINTERNALRESOURCE.ZLOCAL'+494                      │ │
│ │             sql_raw = ' SELECT\n                ZASSET.ZUUID,\n          │ │
│ │                       ZINTERNALRESOURCE.ZDATALEN'+490                    │ │
│ │               title = None                                               │ │
│ │                  tz = datetime.timezone(datetime.timedelta(seconds=7200… │ │
│ │ uti_original_column = 'ZINTERNALRESOURCE.ZCOMPACTUTI'                    │ │
│ │                uuid = 'FFFF3748-ECE6-4E7D-B1E1-A04A9331E15A'             │ │
│ │             verbose = <function noop at 0x107308280>                     │ │
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
│                                                                              │
│ /Users/tkrunning/.local/pipx/venvs/photos-time-warp/lib/python3.10/site-pack │
│ ages/osxphotos/photosdb/_photosdb_process_faceinfo.py:35 in                  │
│ _process_faceinfo                                                            │
│                                                                              │
│    32 │   if self._db_version <= _PHOTOS_4_VERSION:                          │
│    33 │   │   _process_faceinfo_4(self)                                      │
│    34 │   else:                                                              │
│ ❱  35 │   │   _process_faceinfo_5(self)                                      │
│    36                                                                        │
│    37                                                                        │
│    38 def _process_faceinfo_4(photosdb):                                     │
│                                                                              │
│ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
│ │ self = osxphotos.PhotosDB(dbfile='/Volumes/Photos/Photos                 │ │
│ │        Library.photoslibrary/database/photos.db')                        │ │
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
│                                                                              │
│ /Users/tkrunning/.local/pipx/venvs/photos-time-warp/lib/python3.10/site-pack │
│ ages/osxphotos/photosdb/_photosdb_process_faceinfo.py:188 in                 │
│ _process_faceinfo_5                                                          │
│                                                                              │
│   185 │                                                                      │
│   186 │   (conn, cursor) = _open_sql_file(db)                                │
│   187 │                                                                      │
│ ❱ 188 │   result = cursor.execute(                                           │
│   189 │   │   f"""                                                           │
│   190 │   │   SELECT                                                         │
│   191 │   │   ZDETECTEDFACE.Z_PK,                                            │
│                                                                              │
│ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
│ │ asset_table = 'ZASSET'                                                   │ │
│ │        conn = <sqlite3.Connection object at 0x112b3d740>                 │ │
│ │      cursor = <sqlite3.Cursor object at 0x110cbef40>                     │ │
│ │          db = '/var/folders/gq/1rqy_8g977g90cxn8mfylp5c0000gn/T/osxphot… │ │
│ │    photosdb = osxphotos.PhotosDB(dbfile='/Volumes/Photos/Photos          │ │
│ │               Library.photoslibrary/database/photos.db')                 │ │
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
╰──────────────────────────────────────────────────────────────────────────────╯
OperationalError: no such column: ZDETECTEDFACE.ZBALDTYPE

Any idea what's going on?

I have exiftool version 12.30 in case that matters (although, looking at the error I doubt that's the case). I'm running MacOS 12.0.1.

--timezone shows the wrong starting zone (I think)

Just doing a little testing before I go all in on this great tool, and I noticed what I think is a bug.

I have a bunch of photos that I took in city X with a Canon that didn't TZ-stamp the time. I loaded them into Photos in city Y, which is one time zone west of X. When I look at the time in Photos, the TZ seems to have been set to city Y's (I guess since that's where I was using Photos). Now when I use photos_time_warp to fix the time zone, Photos updates the time to +1 from what it was, consistent with the idea that it set the time zone to city Y's when I loaded the photos and now is correctly at city X's.

The bug is that photos_time_warp reports this in verbose mode:

Updated timezone for photo IMG_7655.JPG (185BB35F-43F2-4500-9A5E-926A3D77EA51) from America/New_York, offset=-14400 to GMT+0300, offset=10800

America/New_York is where I am now, which is a lot more than 1 hour away from GMT+0300, so it seems unlikely that that was the starting point.

Fix dependencies

Dependency hell with osxphotos: rich, click, etc. Maybe just update to osxphotos to >= dependencies

Update help to reference Smart Albums

Photos doesn't seem to like giving out info on images in a smart folder. I have this problem with my applescripts and photos_time_warp also has it. It gives an error when you run it when the selected image is in a smart folder:

>photos_time_warp -V --timezone +0300
Could not get selected photos. Ensure Photos is open and photos to process are selected. Photos got an error: Can’t get media item id "0443D7E6-B7BF-448A-B993-D78814CB8987/L0/001" of album id "4C5C4E62-780B-40C5-AC2D-31FCCA924EA5/L0/040". (-1728) app='Photos' range=12071-12118

So it might be worth catching this kind of error and giving a more helpful error, and also including this info in the instructions.

FYI, I try to catch this in my scripts by switching to the main album and highlighting the same photo via interaction with the menus. Probably not an option for this scenario:

tell application "System Events" to tell process "Photos" to click menu item "Show in All Photos" of menu 1 of menu bar item "File" of menu bar 1

Originally posted by @Jmuccigr in #10

Make colors in --compare-exif easier to read

HUGE fan of this feature. Can I suggest that the yellow currently used for the UUID be made darker, maybe more orange-y? I'm not much interested in it, though I think it's useful. However, it's really hard to read and distractingly so.

The green and blue are legible enough, but I don't think it would hurt if they were a tad darker as well.

Originally posted by @Jmuccigr in #13 (comment)

Feature request: Display option?

Would it be feasible to add some kind of --display option to simply show the current settings of the selection? I realize you can do that in Photos by looking at info and also using "Adjust date and time" to see the time zone, but that's a bit pesky.

Add --sync-exif option to push changes to exif without updating Photos

Something I have done with a lot of photos is to tweak the time because the internal clock was off.

This makes me wonder whether options like push-Photos-time or push-Photos-zone might not be useful. In this case the exif data would be changed to match what's in Photos without changing the Photos database at all.

Along with this a report that showed where the exif data differed from the Photos data would be great.

Originally posted by @Jmuccigr in #13

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.