Remove Duplicate photos from Photos library of Photos.app

One cool script I wrote just now.
Use on your risk. You can undo the action from trash anyway.


use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

-- classes, constants, and enums used
property NSURLAddedToDirectoryDateKey : a reference to current application's NSURLAddedToDirectoryDateKey
property NSSortDescriptor : a reference to current application's NSSortDescriptor
property NSMutableArray : a reference to current application's NSMutableArray
property NSURLIsDirectoryKey : a reference to current application's NSURLIsDirectoryKey
property |NSURL| : a reference to current application's |NSURL|
property NSFileManager : a reference to current application's NSFileManager
property NSURLIsPackageKey : a reference to current application's NSURLIsPackageKey
property NSURLPathKey : a reference to current application's NSURLPathKey
property NSArray : a reference to current application's NSArray
property NSString : a reference to current application's NSString
property NSCalendar : a reference to current application's NSCalendar
property photosLibraryPath : POSIX path of (path to pictures folder) & "Screenshots/Photos Library.photoslibrary/"
property originals : photosLibraryPath & "originals"
property derivatives : photosLibraryPath & "resources/derivatives"

-- get all photos and sort them by date added
set theURL to my (|NSURL|'s fileURLWithPath:originals)
set theOptions to 6 -- NSDirectoryEnumerationSkipsPackageDescendants & NSDirectoryEnumerationSkipsHiddenFiles
set theEnumerator to NSFileManager's |defaultManager|()'s enumeratorAtURL:theURL includingPropertiesForKeys:{} options:theOptions errorHandler:(missing value)
set thePhotos to my filesAndPackagesInURLArray:(theEnumerator's allObjects())
set theURL to my (|NSURL|'s fileURLWithPath:derivatives)
set theEnumerator to NSFileManager's |defaultManager|()'s enumeratorAtURL:theURL includingPropertiesForKeys:{} options:theOptions errorHandler:(missing value)
set otherPhotos to my filesAndPackagesInURLArray:(theEnumerator's allObjects())
set thePhotos to NSArray's arrayWithArray:((thePhotos as list) & (otherPhotos as list))
set thePhotos to my sortFilesOrPaths:thePhotos
set theCount to thePhotos count

-- get date added of each photo. (We delete only the later added photos)
set theDatesAdded to {}
repeat with i from 1 to theCount
	set aPhoto to item i of thePhotos
	set aDate to (aPhoto's resourceValuesForKeys:{NSURLAddedToDirectoryDateKey} |error|:(missing value))
	set end of theDatesAdded to (my makeASDateFrom:(aDate as date))
end repeat
set thePhotos to thePhotos as list

-- with System Events getting the sizes of the files is faster than with AsObjC
set theSizes to {}
repeat with aPhoto in thePhotos
	tell application "System Events" to set end of theSizes to size of (aPhoto as alias)
end repeat

-- collect duplicate photos order numbers
set forDeleteFlags to {}
repeat with i from 1 to theCount - 1
	if i is in forDeleteFlags then set i to i + 1
	set aPhoto to (item i of thePhotos) as alias
	set aDate to item i of theDatesAdded
	set aSize to item i of theSizes
	set MD5checkSum to last word of (do shell script "md5 '" & POSIX path of aPhoto & "'")
	repeat with j from i + 1 to theCount
		set bPhoto to (item j of thePhotos) as alias
		set bSize to item j of theSizes
		if bSize = aSize then
			if not (j is in forDeleteFlags) then
				set MD5checkSum2 to last word of (do shell script "md5 '" & POSIX path of bPhoto & "'")
				if (MD5checkSum2 is MD5checkSum) then set end of forDeleteFlags to j
			end if
		end if
	end repeat
end repeat

-- delete duplicates
repeat with i from 1 to theCount
	if i is in forDeleteFlags then tell application "Finder" to delete (item i of thePhotos)
end repeat

on sortFilesOrPaths:listOfFilesOrPaths
	set theResults to NSMutableArray's array()
	repeat with aFile in listOfFilesOrPaths
		set theValues to (aFile's resourceValuesForKeys:{NSURLAddedToDirectoryDateKey} |error|:(missing value))
		set theValues to theValues's mutableCopy()
		(theValues's setObject:aFile forKey:"theURLKey")
		(theResults's addObject:theValues)
	end repeat
	set sortDesc to NSSortDescriptor's sortDescriptorWithKey:NSURLAddedToDirectoryDateKey ascending:true selector:"compare:"
	theResults's sortUsingDescriptors:{sortDesc}
	return theResults's valueForKey:"theURLKey"
end sortFilesOrPaths:

on makeASDateFrom:theNSDate
	set theCalendar to NSCalendar's currentCalendar()
	set comps to theCalendar's componentsInTimeZone:(missing value) fromDate:theNSDate
	tell (current date) to set {theASDate, year, day, its month, day, time} to ¬
		{it, comps's |year|(), 1, comps's |month|(), comps's |day|(), (comps's hour()) * hours + (comps's minute()) * minutes + (comps's |second|())}
	return theASDate
end makeASDateFrom:

on filesAndPackagesInURLArray:theURLs
	set itemURLs to NSMutableArray's array()
	repeat with aURL in theURLs -- is it a directory?
		set {theResult, theValue, theError} to (aURL's getResourceValue:(reference) forKey:NSURLIsDirectoryKey |error|:(reference))
		if theValue as boolean then -- is it a package?
			set {theResult, theValue, theError} to (aURL's getResourceValue:(reference) forKey:NSURLIsPackageKey |error|:(reference))
			if theValue as boolean then (itemURLs's addObject:aURL)
		else
			(itemURLs's addObject:aURL)
		end if
	end repeat
	return itemURLs
end filesAndPackagesInURLArray: