Move Image Files to Landscape and Portrait Folders

The script included below identifies and moves image files to landscape and portrait folders, which are created in the same folder as the image files. In my testing, this script works reliably with regular PNG and TIFF files and with JPG files taken with a digital camera. The timing result with 100 digital photos was 2.0 seconds.

The script was tested on Monterey, and it can be tested on earlier versions of macOS by deleting the first line of the script. Please use this script with file copies until you are comfortable that the script works as desired.

The idea for this script came from the thread linked below, which contains two other scripts that might be considered if the script included below doesn’t do the job.

https://macscripter.net/viewtopic.php?id=49050

-- revised 2022.07.08

use AppleScript version "2.8" -- remove to test with earlier versions of macOS
use framework "AppKit"
use framework "Foundation"
use scripting additions

on main()
	set fileExtensions to {"jpg", "png"} -- edit as desired
	set theFolder to (choose folder)
	set landscapeFolder to (theFolder as text) & "Landscape:"
	set portraitFolder to (theFolder as text) & "Portrait:"
	try
		set {landscapeFiles, portraitFiles} to getFiles(theFolder, fileExtensions)
	on error
		errorAlert("A file could not be processed. This typically happens when the image's width and height is not available.")
	end try
	if landscapeFiles = {} and portraitFiles = {} then errorAlert("No images with a supported file extension were found in the selected folder")
	makeFolders(theFolder, landscapeFolder, portraitFolder)
	moveFiles(landscapeFiles, landscapeFolder)
	moveFiles(portraitFiles, portraitFolder)
end main

on getFiles(theFolder, fileExtensions)
	set fileManager to current application's NSFileManager's defaultManager()
	set theFolder to current application's |NSURL|'s fileURLWithPath:(POSIX path of theFolder)
	set folderContents to fileManager's contentsOfDirectoryAtURL:theFolder includingPropertiesForKeys:{} options:4 |error|:(missing value)
	set thePredicate to current application's NSPredicate's predicateWithFormat_("pathExtension.lowercaseString IN %@", fileExtensions)
	set theFiles to folderContents's filteredArrayUsingPredicate:thePredicate
	
	set landscapeImages to current application's NSMutableArray's new()
	set portraitImages to current application's NSMutableArray's new()
	repeat with aFile in theFiles
		set imageRep to (current application's NSBitmapImageRep's imageRepWithContentsOfURL:aFile)
		set theWidth to imageRep's pixelsWide()
		set theHeight to imageRep's pixelsHigh()
		if theWidth ≥ theHeight then -- square files are considered landscape
			(landscapeImages's addObject:aFile)
		else
			(portraitImages's addObject:aFile)
		end if
	end repeat
	return {landscapeImages as list, portraitImages as list}
end getFiles

on makeFolders(theFolder, landscapeFolder, portraitFolder)
	tell application "Finder"
		if not (exists folder portraitFolder) then make new folder in theFolder with properties {name:"Portrait"}
		if not (exists folder landscapeFolder) then make new folder in theFolder with properties {name:"Landscape"}
	end tell
end makeFolders

on moveFiles(theFiles, theFolder)
	tell application "Finder"
		repeat with aFile in theFiles
			try
				move file aFile to theFolder
			on error
				set aFileName to name of file aFile
				display dialog "The file " & quote & aFileName & quote & " already exists in a destination folder" buttons {"Cancel", "Overwrite", "Skip"} cancel button 1 default button 3 with icon caution
				if button returned of result = "Overwrite" then move file aFile to theFolder with replacing
			end try
		end repeat
	end tell
end moveFiles

on errorAlert(dialogMessage)
	display alert "An error has occurred" message dialogMessage as critical
	error number -128
end errorAlert

main()
1 Like

This script is similar to the above, differing in that it:

  • copies rather than moves files,
  • reports an error if either of the destination folders exists, and
  • is significantly faster.
use AppleScript version "2.8" -- remove to test with earlier versions of macOS
use framework "AppKit"
use framework "Foundation"
use scripting additions

on main()
	set fileExtensions to {"jpg", "png"} -- edit as desired
	set fileManager to current application's NSFileManager's defaultManager()
	
	set theFolder to POSIX path of (choose folder)
	set theFolder to current application's |NSURL|'s fileURLWithPath:theFolder
	set landscapeFolder to theFolder's URLByAppendingPathComponent:"Landscape" isDirectory:true
	set portraitFolder to theFolder's URLByAppendingPathComponent:"Portrait" isDirectory:true
	
	try
		set {landscapeFiles, portraitFiles} to getFiles(theFolder, fileExtensions, fileManager)
	on error
		errorAlert("A file could not be processed. This typically happens when the image's width and height is not available.")
	end try
	if landscapeFiles's |count|() = 0 and portraitFiles's |count|() = 0 then errorAlert("No images with a supported file extension were found in the selected folder")
	
	makeFolders(landscapeFolder, portraitFolder, fileManager)
	
	repeat with aFile in landscapeFiles
		copyFile(aFile, landscapeFolder, fileManager)
	end repeat
	repeat with aFile in portraitFiles
		copyFile(aFile, portraitFolder, fileManager)
	end repeat
end main

on getFiles(theFolder, fileExtensions, fileManager)
	set folderContents to fileManager's contentsOfDirectoryAtURL:theFolder includingPropertiesForKeys:{} options:4 |error|:(missing value)
	set thePredicate to current application's NSPredicate's predicateWithFormat_("pathExtension.lowercaseString IN %@", fileExtensions)
	set theFiles to folderContents's filteredArrayUsingPredicate:thePredicate
	
	set landscapeImages to current application's NSMutableArray's new()
	set portraitImages to current application's NSMutableArray's new()
	repeat with aFile in theFiles
		set imageRep to (current application's NSBitmapImageRep's imageRepWithContentsOfURL:aFile)
		set theWidth to imageRep's pixelsWide()
		set theHeight to imageRep's pixelsHigh()
		if theWidth ≥ theHeight then -- square files are considered landscape
			(landscapeImages's addObject:aFile)
		else
			(portraitImages's addObject:aFile)
		end if
	end repeat
	return {landscapeImages, portraitImages}
end getFiles

on makeFolders(landscapeFolder, portraitFolder, fileManager)
	set landscapeFolderExists to landscapeFolder's checkResourceIsReachableAndReturnError:(missing value)
	set portraitFolderExists to portraitFolder's checkResourceIsReachableAndReturnError:(missing value)
	if landscapeFolderExists as boolean is true or portraitFolderExists as boolean is true then errorAlert("A destination folder already exists")
	fileManager's createDirectoryAtURL:landscapeFolder withIntermediateDirectories:false attributes:(missing value) |error|:(missing value)
	fileManager's createDirectoryAtURL:portraitFolder withIntermediateDirectories:false attributes:(missing value) |error|:(missing value)
end makeFolders

on copyFile(sourceFile, targetFolder, fileManager)
	set sourceName to sourceFile's lastPathComponent()
	set targetFile to targetFolder's URLByAppendingPathComponent:sourceName
	set {theResult, theError} to fileManager's copyItemAtURL:sourceFile toURL:targetFile |error|:(reference)
	if theResult as boolean is false then errorAlert(theError's localizedDescription() as text)
end copyFile

on errorAlert(dialogMessage)
	display alert "An error has occurred" message dialogMessage as critical
	error number -128
end errorAlert

main()
1 Like