Avoid Importing Duplicate Images in Hazel

The Problem

For a long time, I've had an issue where I download an image that is already in my Photos library, and Hazel tries to import it. When that occurs, Photos will pop up a window prompting me for the approriate action - "Skip or import anyway?"

In the past, I'd either manually fix the issue or I would turn the photos import rule off and fix it later. Sometime months later! But today, I decided to slay this dragon by automating the process of checking if an image exists in Photos before trying to import it.

With some searching, I found a Python library called osxphotos for working with the Photos Library. Python is my language of choice, so this was a good find. There were some other potential solutions using AppleScript and JavaScript which you might want to look into if you prefer those languages.

I wrote a bash script as a wrapper for Hazel to run, and I wrote a python script that searches the default Photos Library. You can find the source code in my Photos Utilities Repository. My script takes several seconds to run, 4-7 seconds during testing. If I dumped a large number of images into my downloads folder, it could take a while to process them. However, I don't download many images to put into my Photos libray, so this latency is ok. It's definitely better than having the import rule turned off altogether, which is where the state of things - of for 3 months - because of this issue.

Hazel Rules

In Hazel, I used the "Passes shell script" condition to run my script that returns 0 if the file is in the library, 1 if it is not.

1Rule #1 If image file is older than 1 day and in photo library, move to Trash folder.
2Rule #2 If image file is older than 1 day, import into Photos and move to backup images library folder on NAS.

Shell Script

The shell script sets paths , then activate the Python virtual environment. After runnign the Python script saving the return code, it deactivates the virtual env and exits.

 1#!/bin/bash
 2  
 3DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
 4PROJECTDIR=$(dirname $DIR)
 5LOGFILE="${PROJECTDIR}/output.log"
 6FILENAME=$(basename ${1})
 7
 8echo "Running script $0 with arguments ${@}" # >> "${LOGFILE}" 2>&1
 9source $PROJECTDIR/venv/bin/activate
10python $PROJECTDIR/photos_utils/image_exists.py --image_name ${FILENAME} # >> "${LOGFILE}" 2>&1
11RC=$?
12echo "Return code is ${RC}" # >> "${LOGFILE}" # 2>&1
13echo " " #>> "${LOGFILE}" 2>&1
14echo " " #>> "${LOGFILE}" 2>&1
15deactivate
16exit $RC  

Python Script

The python script searches for the image by name and returns 0 if file is found, otherwise it returns a 1.

 1import re 
 2import sys
 3import time 
 4import osxphotos
 5import argparse
 6from datetime import datetime
 7
 8
 9def create_arguments():
10    parser = argparse.ArgumentParser(description='Returns 0 if image file exists in Photos Library. Else returns 1.')
11    parser.add_argument('--image_name', action='store', dest='image_name',required=True, help='Name of image file')
12    return parser.parse_args()
13
14def main():
15    args = create_arguments()
16    photosdb = osxphotos.PhotosDB()    
17    results =  [ photo for photo in photosdb.photos( movies=False, images=True ) if photo.original_filename == args.image_name ]
18    if results:
19        print("File exists in library")
20        return 0
21    else:
22        print("File does not exist in library")
23        return 1
24    # print(results)
25    return  
26
27
28if __name__ == "__main__":
29    start_time = datetime.now()
30    results = main()  
31    print(f"Script completed in {(datetime.now() - start_time).seconds} seconds.")
32    sys.exit(results)

Conclusion

Hazel is a great tool for automating repeating tasks, but there are times where you need to write code to extend its functionalities. Learning a few scripting languages comes in handy in these situations.

References