Getting data from Photos.app into Tinderbox

At the last meet-up, @dmrogers (I hope I tagged the right guy!) was describing problems getting photo data out of Apple’s Photos.app into Tinderbox. Most pertinently, a photo’s title and caption (description): Photos’ on-screen terminology for these is title and caption.

Context note: here, I’m using Tinderbox v9.2.1, on macOS 12.4.0 with Photos v7.0

Above (be aware I’m a novice at using Photos) I added a title and description in Photos. Depending on your digital workflow, these could have been added to (embedded in) the image by some upstream app (camera, Lightroom, etc., etc.)

Challenge: can we get a an images title & caption out of Photos using scripting?

It turns out we can. Looking at and Discarding Apple automation apps in reverse order of triviality of functionality, both Shortcuts.app and its macOS predecessor Automator.app came up blank in terms of ‘canned’ functions to do this. Next stop AppleScript proper, long neglected by Apple but still a useful avenue for automation. Apple provides Script Editor.app (in the ~/Applications/Utilities/ folder), though here I’ve used the rather more powerful (but not free) AppleScript Debugger. A nifty feature is the ability to explore an app’s ‘dictionary’§:

We see above that what we think of as a photo is a media item, which exposes a list of metadata including:

  • description. The item’s description.
  • filename. The name of the stored data file, i.e. the photo’s OS disk file
  • name. The item’s title [sic].

Note the inconsistent naming between front-of-house (‘name’) and back-of-house (‘title’) that can occur in AppleScript dictionaries. This design laziness often requires the user to resort to text export of fields just to be able to build a consistent terminology map.

Also in the above, is that the photo is in an Album called ‘blog’. This isn’t built-in. Past experience suggests we’ll needed easy way to find just the photos needed, so I added a smart Album ‘blog’

Again, Script Debugger’s Explorer tool lets me look at open app’s data. Here is the photo I showed above:

Now we can scrip. I’ll start a new post as this is quite long already…

†. See Meetup: Tinderbox and History (11 June 2022) on Vimeo c1:21:08–1:26.25

‡. Another wonderful long-lived tool in the world of Mac indie apps. Overkill if you don’t need it but excellent if you need a bit more info on what’s going on.

§. Apart from very top-level aspect like app windows, how (if!) you can script within an app is in the gift of the developer who writes/codes the app’s ‘dictionary’ which essentially defines what things are called in code and how to address then: indeed, what subset of app functioning to can address at all.

¶. My album looks for the tag (in Photos, a ‘keyword’) called ‘blog’, but that side of the set-up is left to the user. The point is to make it easy for your script to fine (only) the right items in your Photos app. If you are able to re-use tags, or pre-existing album/Projects in your workflow that’s even better!

3 Likes

So, we write a script:

(I’ll put the code in text further below). Running the above with both Photos and Tinderbox v9.x open, the result in Tinderbox is this:

Tada! For actual work you’ll want to expand on this but you have the basic data bridge from Photos → Tinderbox. Here I’m renaming a test note. In reality you’d likely add a new child note per source photo. Or you might actually be writing data directly into a blog article not—but that’s getting down into fine detail.

An further obvious consideration is that your dat set will likely be more than one photo so you might want to make a record, i.e. a list of key:value pairs, and a repeat with to unpack on the Tinderbox side, but I’ve leave that to someone with less rusty AppleScript skills to flesh out.

Let\s say you’ve 4 photos in this this turn of the blogging workflow (article, article(s), whatever). for each of the 4 you need 2 bits of info. I’d be tempted to make the photo filename the key (a camera-generated name is generally unique in this context). Then I’d concatenate the title and description with a non-ambigUous divider, e.g “####”. Now you get a single record like:

IMG_2803.HEIC:Tomatillos####Tomatillos freshly harvested from the allotment

Having made the list of records, you’d iterate through, unpack title and description in the record’s value and using those two strings where needed.

Here’s my little text script, to past into the script window in Script Editor:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

tell application "Photos"
	tell its album "Blog"
		tell its media item "Tomatillos"
			set theDescription to description
			set theTitle to name
		end tell
	end tell
end tell

tell application "Tinderbox 9"
	tell note "blog" of front document
		set value of (attribute named "Text") to theDescription
		set value of (attribute named "Name") to theTitle
	end tell
end tell

Security
Note, of late, macOS has—for very good reason—tightened up security as regards which apps/processes can do what. But, as you’ll see below, this comes with some challenges for the occasional scripter.

I’d suggest testing initially from your AppleScript scripting app (Script Editor, Script Debugger, etc.). For that you’ll need to give express permission for that app to be allowed to do automation.

Once the script works from a Scripting environment, you can experiment using with Automator, or Shortcuts, or the menubar’s scripts folder, or as an osascript command line. Whichever you use will need new permissions (these are deliberately granular). However, you make find code that runs in a script editor without problem will fail with unhelpful messages 9or with no message at all) when
run indirectly. Why? I’ve no idea. Worst case you may have to run your script from a script editor, but hopefully not. :slight_smile:

Tip: Before running the scripts, open you System Preferences ▸ Security & Privacy ▸ Privacy tab ▸ select Accessibility in the left-side list. This is where you’ll be asked to add things. Click the padlock (bottom left of the SysPrefs dialog so you’re ready to edit.

Tip: The OS generally gives you a pop-up telling you which needs what. Until you are used to this, don’t rush to click allow as this can fail is your system prefs are locked. Plus some apps/processes require giving permissions to ‘apps’ nested deep in other apps or the OS Library. I find it a good habit to have Sys Prefs open and unlocked (above) tip and to check is the item is already there (but maybe unticked). Also after allowing permission, re-check what got added/enabled. Not least, you might at some point want to turn off permission for and/or delete that app from the list at a later date. Taking a moment to do this the first few times helps build the muscle memory of where to look for the settings.

Tip: If you rush through accepting permissions requests and the process fails, the OS often appears to assume your failure to set access (e.g. you pressed ‘allow’ but had’t unlocked Sys Prefs first) means “don’t allow and never ask me again”. The behaviours is not consistent, so there are obviously further factors in this. But, trying to figure out which obscurely labelled sub-app needs permission and where you even find it can take longer than writing a script.

Sorry for another long post abut I hope that helps with getting data safely from Photos to Tinderbox!

1 Like

Turns out there are a lot of defined EXIF tags- more then I remember of old. Regardless, they describe the technical aspects of the digital photo. This enables/informal some of the modern digital post-processing methods but is also an archive form for this data. It’s actually quite organised - it used to be much less coherent.

What EXIF lacks, arguably by design, is the human-descriptive data about an image. Why? The TL;DR of that is that the two aspects of the image’s data come from different homes and image descriptive image metadata pre-dates the invention of EXIF. Also, not all picture arise for digital cameras so EXIF is often not even pertinent. With that background in mind the lack of title/description fields in EXIF makes sense (as in avoidance of potentially unsynched duplication of key data).

1 Like

Mind successfully blown, @mwra :exploding_head: :100: :rocket:

1 Like

Apologies if too much detail. Much of my late 90s/early '00s was spent doing image metadata. Not so many metadata (literal) nightmares these days: you had to be there…. As someone said at the meet-up, we have a broad and arcane skillset. Amen!

2 Likes

Wow! Thanks for this! As it happens, I do have Script Debugger, and I was sitting on the couch last night looking at the Photos Dictionary in that app, and looking at “media item” and asking myself, "Okay, so it should be there, how do I get it?

That was this morning’s to-do item, but my daughters decided they wanted to do an early Father’s Day breakfast (the younger one is headed back to LA this week).

I just got home and checked my email, and Mark Anderson, you’re a gent and a scholar! My work is done! Mostly.

I shall study this and see if I can get a workflow ironed out.

It may be somewhat different than what I’d envisioned, in terms of the sequence of events. In order to get an image out of Photos, you inevitably must Export, and I thought that might be the trigger to grab the relevant metadata when it landed in the export folder.

What it looks like, to me, now is to invoke the script to create the note in Tinderbox, but add an export command at the end as the last action to get the photo into the relevant folder in Finder, which is then sync’ed to the server via Forklift.

I’m sure there’ll be bumps along the way, but I think this is the biggest hurdle. Many, many thanks and so much appreciation. You’re very generous with your time, and I’m very grateful.

Sorry, I didn’t get that far. I’ve a suspicion those aspects of automation work better, as that is their starting intent [sic]. By comparison, much inter-app scripting feel like taking ‘the one’ home to meet the parents. It’s possible, but it’s going to take some effort and there will be tears along the way. In fairness, we’ve know for millennia that people from the next village over are different and therefore never to be trusted, so why should our apps behave differently. :roll_eyes: Eheu!

On a moure grounded note, I thikn my thought of a record was wrong. I’d make an array, as you may want lots of pieces of data from the AppleScript source now you have a conduit. With an array, the first item [0] will be you filename or UUID and the rest the source fields you want.

Otherwise you may end up with a baroque script where you iterate a list in Photos and in-loop write data to Tinderbox. Meh, the cool kidz may laugh at such an approach but actually—if it works it works, or so the Chief Bosun’s Mate taught me in my youth and he actually knew stuff.

Yes, I was going to ask about that, but later.

Right now, I’m just trying to do the “minimum viable product,” and get the script to create a new note in my existing TBX file, which will require some specificity.

Happily, there is one Images folder for each Archive year, so that path is well defined and fixed, i.e…:

Archive/2022/Images.

What I’ll need to do is also grab the filename, as that’s the default unless I select a custom filename at time of export. I don’t usually do that.The filename is then merged into the URL attribute of the note.

But I’m getting ahead of myself. I’ll bang away at this today and return here when I’m successful or stuck. Probably tomorrow (your time for sure, likely mine too).

Thanks again!

Progress! I think I’m almost there!

I’ve been using a dummy tbx file I’ve called “Sandbox” to test this script.
I’ve created a pPhoto prototype note with the relevant html to be created by the export code.
I’ve selected a photo in Photos and run the script in Script Debugger. (So this would ultimately appear up in the Scripts menu, or become a service or something like that.)
I select a photo in Photos, run the script and Sandbox creates a note of prototype pPhoto with all the attributes set appropriately.
If I export the photo to my site folder (as I would in the actual workflow), I must change the filename to match the original filename of the image, because Photos insists on changing it to “.jpeg”. (And “.jpg” is not the same as “.JPG”. Details matter, though they will likely send me to an early grave.)
The html looks good in the Export tab of the Text Pane (Had to look up how to make that appear again! Clue: It’s in the Window menu.)
Hit Preview, and voila! The image appears in the Preview pane

Here is the script, thanks to Ed Stockley at the Late Night Software forum:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

tell application "Photos"
	set currentSelection to the selection
	if currentSelection is {} then error number -28
	repeat with thisPhoto in currentSelection
		set PhotoCaption to description of thisPhoto
		set PhotoTitle to name of thisPhoto
		set PhotoFileName to filename of thisPhoto
		set PhotoID to id of thisPhoto
	end repeat
end tell

tell application "Tinderbox 9"
	tell document "Sandbox"
		set the_container to note "Inbox"
		set myNote to make new note at the_container
		set name of myNote to PhotoTitle
		tell myNote
			--set value of (user attribute named "PhotoDate") to PhotoDate
			set value of (user attribute named "PhotoTitle") to PhotoTitle
			set value of (user attribute named "PhotoCaption") to PhotoCaption
			set value of (user attribute named "PhotoFileName") to PhotoFileName
			set value of (user attribute named "PhotoID") to PhotoID
		end tell
	end tell
end tell

So, a big “to do” is to discuss, perhaps tomorrow, how to create that note a little deeper into the outline hierarchy. So far, I’ve only been able to create it in a root-level container (Inbox, in the case of Sandbox).
In the marmot, it would be created at Archives/2022/June/“myNote”.

I tried making a nested series of containers and using a /Inbox/2022/June/“myNote” path, which seemed to be what one does, but that never worked. Gave up and focused on just getting the attributes out of Photos, which was making me crazy. Turns out, a “selection” is always of type “list”, even if there’s only one. I guess that makes sense.

A possible detail to work out is how to capture the date of the photo. I tried that, and ran into some “type” (string vs. date) issues, so there’s probably a particular syntax I have to observe. It’s not essential to what I want to do for now, but it might be helpful going forward.

Anyway, thanks for the initial stimulus to look at the properties of “media item”, and the whole “selection” thing was to work around having to create a special album structure of some kind to specify exactly which photo I was interested in.

Probably the most rewarding thing I’ve done with AppleScript ever, though I won’t say it’s been “fun”!

Here’s Sandbox if anyone wants to play with it. It’s a nothingburger of a file, but I can beat it up and make mistakes and it won’t screw up the marmot.

Sandbox.tbx (115.8 KB)

1 Like

Glad the LNS forum folk came through for you - good place to ask. :slight_smile:

1 Like

This should get you a little further:

tell application "Photos"
	repeat with aPhoto in (get selection)
		tell aPhoto
			set photoFileName to its filename
			set photoDate to its date as «class isot» as string -- format Tinderbox can accept
			set photoCaption to description
			set photoTitle to name
			set photoID to id
		end tell
	end repeat
end tell

tell application "Tinderbox 9"
	tell document "Sandbox.tbx" -- suffix .tbx required on my machine
		-- this is how to specify a note down in the hierarchy:
		set theContainer to find note in it with path "Archives/2022/June"
		set newNote to make new note at theContainer
		tell newNote
			if photoTitle is not missing value then set value of attribute "Name" to photoTitle
			set value of attribute "PhotoDate" to photoDate
			set value of attribute "PhotoFileName" to photoFileName
			set value of attribute "PhotoID" to photoID
			set value of attribute "PhotoCaption" to photoCaption
			set value of attribute "Prototype" to "pPhoto"
		end tell
	end tell
end tell

Notes:

  • This is vanilla AppleScript. No need to specify scripting additions and version at the top.
  • The as «class isot» as string gets a AppleScript date into ISO format string that Tinderbox should be able to use.
  • Go down into the hierarchy using find note in it with path "path/to/note" . The “it” in this case refers to the document specified in the statement immediately above it.
  • set value of (user attribute named "PhotoTitle") to works, but it is awkward. More “AppleScripty” syntax is simply set value of attribute "PhotoTitle" to.
2 Likes

An unknown for me at this point is if you can create a note a some/path/to/note and have Tinderbox create the missing part(s) of the path. This is a very user-ish assumption fo ‘obviously needed’ but one many coding environments/app don’t support. I don’t get why, but that’s probably because no one has bothered to explain the (unknown to me) edge cases that makes this hard or impossible to do.

After all , it’s my Mac. If I want to make a note at /come/improbably/long/and/convoluted/path, why can I not do that without silent fails. If the path is valid (when completes), why punish he novice who doesn’t enjoy wasting time figuring out baroque mis-en-place. I do get issues around some parser-confusing characters like semi-colons, but that’s a separate issue, addressable via documentation. I do wonder how much of the technical caution is people dragging tech baggage from the late 20C when everything was far more fragile and expensive if you broke it.

I liked @dmrogers’s blog following up in this task - see here.

1 Like

Am trying to puzzle out the problem encountered here. Do you mean using AppleScript?

Very cool! Just copied and pasted it into Script Debugger, added the relevant container notes to Sandbox and ran it.

Choked on .tbx! It’s possible, I think, that because I’m running from Script Debugger, and not as a standalone script (yet), that hasn’t been an issue. But it’s interesting it failed with the extension in the script here.

Deleted that and it worked flawlessly. Thanks. Very useful to know exactly how to specify the path and the syntax to create a new note in that container.

Roger all on no need for Scripting Additions, but it comes up as the default on a “new” document in ScriptDebugger. Is there some performance penalty for its presence?

“AppleSripty” syntax is one of the things makes this harder. I mean, I’m happy to do it however it works, but the variety of different forms makes it harder to assimilate what’s going on. There’s little value in repetition, because so many examples are different in trivial ways that it obscures the important things.

Give me a clearly understandable, albeit rigid syntax with any day.

Question: Is the “if” statement essential for some reason? I selected a photo in Photos, gave it a caption without a title and the script fails with a missing value error. But not before creating a new note anyway. It’s not clear to me why I want to test that value?

Question: I will push the “I believe” button on the class isot thing, but what’s up with the double angle brackets? Is that part of AppleScript? I don’t know that I’ve seen them very often (ever) in the scripts I’ve looked at. But again, I seem to overlook details, so maybe it’s just me.

Looks like it’s OPT-\ for «, and SHIFT-OPT-| for ».

The date it returns does look like it’s something Tinderbox can work with as a date. Thanks for that.

I’m getting foolish and brave now, and I have more ideas. Typing the Name and Caption into the tiny “Info” box in Photos is fiddly, Caption often fails.

I think I need to make a little dialog script that’ll give me something bigger to work with.

Yes. I’m not saying it’s not possible. It may be, and if it is I’ll make sure it’s documented as it is (reasons above) something people often want to do (even if they can’t!)

A problem of AppleScript’ permissive syntax approach is it can be hard to guess what’s possible and the allowed syntax for that. :slight_smile:

Also, thanks for all the input re @dmrogers Photos problem. Always helpful to have some real AppleScript expertise around.

I’m running from Script Debugger too. Must be some other difference in the settings on our machines. In Finder > Preferences I have ‘Show all filename extensions’ checked. Maybe that makes a difference. Anyway, whatever works works! I usually use tell front document so I don’t have to worry about extensions. I have to have the tbx open, of course.

The use scripting additions stuff is included in the default for Script Debugger, but that is for AppleScriptObjC. Script Debugger also has an ‘Empty AppleScript’ option, which is for a vanilla AppleScript like this.

By “AppleScripty” I mean more natural and straightforward for the language. What you had works, but it is stilted, more complicated than it needs to be.

Try taking out the “if” statement and see what happens. If it works it works! On my machine the script would sometimes complain and halt without it, I think if I didn’t have a value for photoTitle.

Yes, you will see the « » at the more abstruse corners of the language. Don’t often have to use it. But it sometimes comes in handy. If it works it works! I stumbled on this solution for dates by studying examples online, possibly on the LNS forum or on another forum @mwra pointed us to years ago. The name escapes me right now.

With AppleScript you have to fiddle around and try different things. Frustrating. But the reward can be that a general user without a coding background can accomplish useful things. Which is one of the reasons after all these years of imminent demise AppleScript hasn’t been declared dead yet.

1 Like

When the script is run creating a new note at a container in within the hierarchy, .e.g. Archives/2002/June, then Tinderbox sets the Path attribute for that note automatically. But I think I am missing something.

I found a technical explanation of the double angle brackets here:

https://developer.apple.com/library/archive/documentation/AppleScript/Conceptual/AppleScriptLangGuide/conceptual/ASLR_raw_data.html

They seem to mean different things in different contexts, but what’s happening here seems to be explained near the bottom of the page. I’ve never worried too much about the technicalities of the formal grammar of the language. I keep it simple, and if I see a “phrase” that might work I try it out.

2 Likes