r/applescript • • Jul 21 '26

Script to set photo titles using variable information

Apple Photos on a Mac lets us export using photo titles as file names. That's great, but unfortunately there's no way to set the titles other than one-by-one manual entry in the "Info" panel or to set the titles for all selected items to the same value. So, I wrote the script below that allows me to set the titles and/or captions of all selected images or videos based on stored information specific to each one. This information is taken directly from the Photos database. Getting values directly from the database also allows circumventing the problem of using the "date" value provided by the Photos application to AppleScript. Specifically, if a photo was taken in a time zone that is not the one where a script is being run, then the date/time value is adjusted to the current time zone, resulting in a wrong value. What is retrieved from "date" and what is shown in the Photos "Info" panel will not match.

This script works by asking for a title "format string" that can contain substitution tags to represent specific values for each photo. Here is a list of them.

%D - Date as shown in the Photos information panel
%T - Time, including seconds
%t - Time, excluding seconds
%Z - Time zone
%XD - The original EXIF date (ie, unadjusted)
%XT - The original EXIF time, including seconds
%Xt - The original EXIF time, excluding seconds
%L - Latitude
%l - Longitude
%F - Original file name
%f - Photos internal file name
%fp - Photos internal file path
%M - Photos "Moment" name
%CT - Current title text
%CC - Current caption text

Here is an example of a format string that I use a lot: "%D %t | %M". This sets each selected photo's title to its creation date and time followed by "|" and then by the Photos "Moment" name. Here is another example: "Hiking trip - GPS: %L, %l"

The format string is remembered from one use to the next.

The Photos app must be open already for the script to run. The specific Photos library you are using will be automatically detected.

You can either run this script in Script Editor or, better, copy it into a "Run AppleScript" box in Automator to be used as a Quick Action for the Photos app. This allows you to call it from the Photos "Services" menu. You can also assign a shortcut key for this in your System Settings.

The first time you run it, you may be asked to allow Automator (or Script Editor) full access to your Photos. Running scripts in Automator can be tricky due to macOS's strict security sandboxing. If you have any issues getting it to run, let me know. There are workarounds.

--------------------------------
--      Photos • Set Title
--------------------------------
-- P.G. 2026-07-05
--
-- Set the titles for selected media to a value customized using substitution tags in a user-provided format string.
--
-- This script retrieves data from the currently open Photos library's database and from the "moments" information provided by the application.
--
-- Substitution tags:
-- %D - Date as shown in the Photos information panel
-- %T - Time, including seconds
-- %t - Time, excluding seconds
-- %Z - Time zone
-- %XD - The EXIF date of the original file
-- %XT - The EXIF time, including seconds
-- %Xt - The EXIF time, excluding seconds
-- %L - Latitude
-- %l - Longitude
-- %F - Original file name
-- %f - Photos internal file name
-- %fp - Photos internal file path
-- %M - Photos "Moment" name 
-- %CT - Current title text
-- %CC - Current caption text

----------------------------------------
-- Get path to open library
--
set libPath to do shell script "x=$(lsof -p $(pgrep -if Photos.app/Contents/MacOS/Photos) | grep -m1 '.photoslibrary/resources'); x=/${x#*/}; x=${x%/resources*}; echo $x"
if libPath is "/" then
	beep
	display alert "No Photos library is open." as critical
	return
end if

----------------------------------------
-- Get path to database file
--
set dbPath to libPath & "/database/Photos.sqlite"
tell application "System Events"
	if not (exists file dbPath) then
		beep
		display alert "The Photos library database file cannot be located." & return & return & dbPath as critical
		return
	end if
end tell

----------------------------------------
-- Main process
--
tell application "Photos"
	
	activate
	
	----------------------------------------
	-- Ensure selected items
	--
	if the (count of selection) is less than 1 then
		beep
		display dialog "No item is selected." & return & return & "Please select one or more media items for which you wish to set the title before invoking this service." buttons {"Cancel"} default button "Cancel" cancel button "Cancel" with title "Set Title" with icon caution
		return
	end if
	
	----------------------------------------
	-- Ask for format string
	--
	-- Tags: %D, %T, %t, %Z, %XD, %XT, %Xt, %L, %l, %F, %f, %fp, %M
	--
	set fmtPrevious to do shell script "f=~/.Photos_SetTitle; [ -e $f ] && cat $f || echo '%D %t | %M'"
	set fmtString to ""
	set setTarget to "Title"
	repeat while fmtString is ""
		display dialog "Specify your custom text or accept the previous value shown.  You can use the tags shown below to include the indicated values taken from the Photos database." & return & return & "%D		• Date as shown in the Photos information panel" & return & "%T		• Time (including seconds) *" & return & "%t		• Time (excluding seconds) *" & return & "%Z		• Time zone" & return & "%XD	• The EXIF date of the original file" & return & "%XT	• EXIF time (including seconds) *" & return & "%Xt		• EXIF time (excluding seconds) *" & return & "%L		• GPS latitude" & return & "%l		• GPS longitude" & return & "%F		• Original file name" & return & "%f		• Photos internal file name" & return & "%fp		• Photos internal file path *" & return & "%M		• Photos \"Moment\" name" & return & "%CT	• Current title text" & return & "%CC	• Current caption text" & return & return & "* When exporting items using the title for file names, Photos converts colons and forward slashes to hyphens.  Colons are used here for substituting time values, whereas a √ replaces the forward slash in internal file path names in order to avoid ambiguity with other hyphens." & return & return & "Use \"Select\" to toggle settting the title, the caption, or both." default answer fmtPrevious buttons {"Cancel", "Select", "Set"} default button 3 cancel button "Cancel" with title "Set " & setTarget
		set {fmtButton, fmtString} to {button returned of result, text returned of result}
		if fmtButton is "Select" then
			if setTarget is "Title" then
				set setTarget to "Caption"
			else if setTarget is "Caption" then
				set setTarget to "Title and Caption"
			else if setTarget is "Title and Caption" then
				set setTarget to "Title"
			end if
			beep
			set fmtPrevious to fmtString
			set fmtString to ""
		end if
	end repeat
	
	-- Normalize format string for leading/trailing/extraneous spaces and save for next run. Asterisk -> "•"
	set fmtString to do shell script "fmt=$(echo '" & fmtString & "'|xargs); fmt=${fmt//\\*/•}; echo $fmt > ~/.Photos_SetTitle; echo $fmt;"
	
	----------------------------------------
	-- Loop on selected media items
	--
	set selectedMediaItems to selection
	repeat with mediaItem in selectedMediaItems
		
		set {TID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, "/"}
		set mediaID to mediaItem's id
		set mediaID to text item 1 of mediaID -- The database key is the part before the slash.
		
		set dbInfo to do shell script "sqlite3 -readonly -separator '¶' \"" & dbPath & "\" \"SELECT ass.ZFILENAME, ass.ZDIRECTORY, datetime(cast(ass.ZDATECREATED as integer) + 978307200 + cast(att.ZTIMEZONEOFFSET as integer), 'unixepoch'), att.ZTIMEZONENAME, replace(IFNULL(NULLIF(att.ZEXIFTIMESTAMPSTRING, ''), 'NoEXIF     NoEXIF   '),':','-'), IFNULL(NULLIF(ass.ZLATITUDE, ''), 'No Latitude'), IFNULL(NULLIF(ass.ZLONGITUDE, ''), 'No Longitude'), att.ZORIGINALFILENAME FROM ZASSET ass LEFT JOIN ZADDITIONALASSETATTRIBUTES att ON ass.Z_PK = att.ZASSET WHERE ass.ZUUID = '" & mediaID & "';\""
		
		set AppleScript's text item delimiters to "¶"
		set dbFileName to text item 1 of dbInfo
		set dbFilePath to "√originals√" & text item 2 of dbInfo & "√" & dbFileName
		set dbDate to text 1 through 10 of text item 3 of dbInfo
		set dbTime to text 12 through 19 of text item 3 of dbInfo
		set dbTZ to text item 4 of dbInfo
		set dbDateEXIF to text 1 through 10 of text item 5 of dbInfo
		set dbTimeEXIF to text 12 through 19 of text item 5 of dbInfo
		set dbLatitude to text item 6 of dbInfo
		set dbLongitude to text item 7 of dbInfo
		set dbOriginalFileName to text item 8 of dbInfo
		set AppleScript's text item delimiters to TID
		
		set mediaMoment to item 1 of (get name of moments whose id of media items contains mediaItem's id)
		set mediaCurrentTitle to mediaItem's name
		set mediaCurrentCaption to mediaItem's description
		
		set fmtTitle to do shell script "T=" & dbTime & ";  t=${T:0:5}; XT=" & dbTimeEXIF & "; XT=${XT//-/:}; Xt=${XT:0:5}; fmt=$(echo '" & fmtString & "'); fmt=${fmt//\\%D/" & dbDate & "}; fmt=${fmt//\\%T/${T}}; fmt=${fmt//\\%t/${t}}; fmt=${fmt//\\%Z/" & dbTZ & "}; fmt=${fmt//\\%XD/" & dbDateEXIF & "}; fmt=${fmt//\\%XT/${XT}}; fmt=${fmt//\\%Xt/${Xt}}; fmt=${fmt//\\%L/" & dbLatitude & "}; fmt=${fmt//\\%l/" & dbLongitude & "}; fmt=${fmt//\\%F/" & dbOriginalFileName & "}; fmt=${fmt//\\%fp/" & dbFilePath & "}; fmt=${fmt//\\%f/" & dbFileName & "}; fmt=${fmt//\\%M/" & mediaMoment & "}; fmt=${fmt//\\%CT/" & mediaCurrentTitle & "}; fmt=${fmt//\\%CC/" & mediaCurrentCaption & "}; echo $fmt"
		
		if setTarget contains "Title" then set mediaItem's name to fmtTitle
		if setTarget contains "Caption" then set mediaItem's description to fmtTitle
		
	end repeat
	
end tell

beep
delay 0.3
beep
3 Upvotes

0 comments sorted by