Ok, (photo) keywords to tags work - ... but how about tags to keywords?

hey community,

there are inbuilt commands & there are scripts to convert photo keywords (IPTC/XMP) to DT/OSX tags (and photo keywords are also indexed somehow by DT)
– but for a coherent workflow with images I´d need to also write tags from DT back to image keywords readable for photo-management software.

are there any scripts for this? or any pointers for a non-coder how to achieve this?

thx very much in advance!
oliver

Image scripting (see Image Events.app in Library of Script Editor.app) might be a possibility but I’m not aware of such scripts, at least not in this forum.

thx Christian!

yes, I did look for those as well, and was a little surprised to find none – as the community has some others which work for syncing (image-)metadata within DT; but normally these things are roundtrip issues…

… so, I was hoping someone can point me to a solution, or share some private script (as I assume peope working seriously with both images and DT will have need for syncing metadata changes back to image files…)

so, … still hoping and having my ears up in the wind… :slight_smile:

hey community,

I am a little surprised there seem to be not a lot of people who really roundtrip their images btw. DT and other image management, at least not on the level of metadata management.
– … I guess some day I will ask the community about their scenarios of working seriously with images + DT :smiley:

for now, and for anyone interested in this particular question of tags-to-keywords:
there is an app that can do this for whole folders (unfortunately not on automated basis per se): it´s MetaImage, and I can recommend that as semi-solution to this issue.
(– also, PhotoSupreme should be able to do it for existing catalogs and automated, but I add the cautionary note that it doesn´t work in my case)

best!

Not sure what exactly you need but maybe it can be done in an automated way with ExifTool. See IPTC Tags and XMP Tags. I don’t have images with metadata to test but if you upload some I could try it.

1 Like

This seems to work.

-- Add DEVONthink tags to image keywords

-- Test this with duplicates!

-- -overwrite_original_in_place ... (I think this is necessary if you have your images imported)

property overwriteKeywordsWithTags : false -- set to true if you want to mirror DEVONthink tags

tell application id "DNtp"
	try
		set theRecords to selection of think window 1
		
		repeat with thisRecord in theRecords
			set thePath to path of thisRecord
			
			-- get existing keywords
			set theOutput to do shell script "/usr/local/bin/exiftool -keywords -S -sep ', ' " & quoted form of thePath
			try
				set trimmedOutput to (characters 11 thru -1 in theOutput) as string
				set existingKeywords to my textItemDelimiters(trimmedOutput, ", ")
			on error
				set existingKeywords to {}
			end try
			
			-- get tags
			set theTags to tags of thisRecord
			set tagsToAdd_list to {}
			repeat with thisTag in theTags
				if existingKeywords does not contain thisTag then set end of tagsToAdd_list to thisTag as string
			end repeat
			set tagsToAdd_string to my string_From_List(tagsToAdd_list, ", ")
			
			if overwriteKeywordsWithTags = false then
				-- add tags to keywords
				set theShellScript to "/usr/local/bin/exiftool -sep ', ' -iptc:Keywords-=\"" & tagsToAdd_string & "\" -iptc:Keywords+=\"" & tagsToAdd_string & "\" -overwrite_original_in_place " & quoted form of thePath
				set addTagsToKeywords to do shell script theShellScript
			else
				-- replace keywords
				set theShellScript to "/usr/local/bin/exiftool -sep ', ' -iptc:Keywords=\"" & tagsToAdd_string & "\" -overwrite_original_in_place " & quoted form of thePath
				set replaceKeywordsWithTags to do shell script theShellScript
			end if
		end repeat
		
		
	on error error_message number error_number
		if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
		return
	end try
end tell

on textItemDelimiters(theText, theDelimiter)
	set d to AppleScript's text item delimiters
	set AppleScript's text item delimiters to theDelimiter
	set TextItems to text items of theText
	set AppleScript's text item delimiters to d
	return TextItems
end textItemDelimiters

on string_From_List(theList, theDelimiter)
	set theString to ""
	set theCount to 0
	repeat with thisItem in theList
		set theCount to theCount + 1
		set thisItem to thisItem as string
		if theCount ≠ (count of theList) then
			set theString to theString & thisItem & theDelimiter
		else
			set theString to theString & thisItem
		end if
	end repeat
	return theString
end string_From_List
1 Like

Pete,

first - and foremost: thanks. it´s really appreciated!

second - works like a charm!

third (and in case others are using it) - for me it works as script in the menu (manually triggered); same script called as smart rule + ‘external script’ doesn´t (for whatever reason).
– so it´s not automatable. but actually for me doing a one time script call for all images of a db is more than worth it + totally works.

so, sending my thankful greetings + some obligations!
oliver

Cf. the very good DT3 documentation (search for “embedded script”). You have to enclose the script in

on performSmartRule(theRecords)
end performSmartRule

if you want to use it in a smart rule. You can alternatively add it as an external script, that’s described in the documentation, too (cf. the chapter on “Automation”). In that case, you should have a look at the external scripts provided with DT3 to make sure that you use the correct conventions.

1 Like

thx @chrillek,

as to embedded script
I did try the modification, but not being a scripter at core, I only used

on performSmartRule(theRecords) - not the 'end...'
so your pointer helped!
BUT I include that very same script that works as menu-launched script – and here it still doesn´t, even if enclosed that way…

as to external scripts I was (simply / naively) assuming that that would work for scripts that also work within the DT menu (which it does). but I can dive in there too, trying to find my way…
the manual says

Add any of your own scripts to this directory to make them available for any smart rule you define.

– question is why this doesn´t seem to hold for a script that works flawlessly when triggered from the menu…

I did try the modification, but not being a scripter at core, I only used
on performSmartRule(theRecords) - not the 'end...'

Why? The end performSmartRule is not really more expensive than the first part.

I don’t understand what you mean here:

BUT I include that very same script that works as menu-launched script – and here it still doesn´t , even if enclosed that way…

Before you said my pointer “helped”, now it still doesn’t work? So in what way did it help?
Then you say it doesn’t work even if enclosed, but before you said you didn’t use the end part, so you didn’t really enclose it?

This is utterly confusing.

I’ll just quote a very short Smart Rule script here for you:

on performSmartRule(theRecords)
	tell application id "DNtp"
		repeat with theRecord in theRecords
			set theTags to get concordance of record theRecord
			set n to count of theTags
			if n > 0 then
				if n > pMaxTags then set n to pMaxTags
				set tags of theRecord to (parents of theRecord) & (items 1 thru n of theTags)
			end if
		end repeat
	end tell
end performSmartRule

As you can see, the code is contained in the same

on performSmartRule(...)
end performSmartRule

lines as I suggested before.
If you leave them out (or even one of them), you probably trigger a script error, so the script will not run. That’s not really surprising.
As to why you do need to modify a script that runs perfectly fine from the menu: context. Smart rules can execute the script only if it contains this on performSmartRule(...) line. In programmers terms: performSmartRule is a subroutine that DT3 is calling. If it isn’t there, there’s nothing to call. And it needs this subroutine to pass the file to work on.

OTH, scripts in the menu are simply executed, DT3 doesn’t need to call a subroutine with a particular name. For them, you’ll usually work on the currently selected files (as the script at the beginning of this thread), so there’s no parameter to pass.

Disclaimer: This tentative explanation is only a wild guess, Christian or Jim might know better.

Your explanation of the differences between a smart rule and the Script menu is good.

An external script must include the full on performSmartRule… end performSmartRule handler. Such scripts should be placed in ~/Library/Application Scripts/com.devon-technologies.think3/Smart Rules.

@lerone I should have mentioned that this was just for testing on your side and not ready for use as smart rule, sorry for the trouble.

Speaking of smart rules there’s something else to mention: If you use an external script and change the value of a property (the ones at the top) it’s necessary to restart DEVONthink. Without restarting the script uses the old value because DEVONthink caches scripts. Embedded scripts don’t need a restart.

To test smart rule scripts in Script Editor you can add this line: tell application id "DNtp" to my PerformSmartRule(selection as list). A very nice tip found here (but @cgrunenberg might want to fix “on” from “my onPerformSmartRule(selection as list)”, it’s a bit confusing :slight_smile: )

The smart rule below has an option to sort keywords which makes it easier (for me) to compare tags and keywords.

-- Add DEVONthink tags to image keywords [Smart Rule]
-- Test this with duplicates!

property overwriteKeywordsWithTags : false -- set to true if you want to mirror DEVONthink tags
property sortKeywords : false

on PerformSmartRule(theRecords)
	tell application id "DNtp"
		try
			repeat with thisRecord in theRecords
				set thePath to path of thisRecord
				
				try
					set theMetaData to meta data of thisRecord
					set existingKeywords to my textItemDelimiters(KMDItemKeywords of theMetaData, "," & linefeed)
				on error
					set existingKeywords to {}
				end try
				
				set theTags to tags of thisRecord
				
				if my sort_list(theTags) ≠ my sort_list(existingKeywords) then
					if overwriteKeywordsWithTags = true then
						if sortKeywords = true then
							set overwriteAndSort to my overwriteKeywords((my string_From_List(my sort_list(theTags), ", ")), thePath)
						else
							set overwrite to my overwriteKeywords(my string_From_List(theTags, ", "), thePath)
						end if
						
					else if overwriteKeywordsWithTags = false then
						set tagsToAdd_list to {}
						repeat with thisTag in theTags
							if existingKeywords does not contain thisTag then set end of tagsToAdd_list to thisTag as string
						end repeat
						if sortKeywords = true then
							set addAndSort to my overwriteKeywords((my string_From_List(my sort_list(existingKeywords & tagsToAdd_list), ", ")), thePath)
						else
							set theTags_string to my string_From_List(theTags, ", ")
							set addTagsToKeywords to do shell script "/usr/local/bin/exiftool -sep ', ' -iptc:Keywords-=\"" & theTags_string & "\" -iptc:Keywords+=\"" & theTags_string & "\" -overwrite_original_in_place " & quoted form of thePath
						end if
					end if
				end if
			end repeat
			
		on error error_message number error_number
			if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
			return
		end try
	end tell
end PerformSmartRule

on overwriteKeywords(theString, thePath)
	do shell script "/usr/local/bin/exiftool -sep ', ' -iptc:Keywords=\"" & theString & "\" -overwrite_original_in_place " & quoted form of thePath
end overwriteKeywords

on textItemDelimiters(theText, theDelimiter)
	set d to AppleScript's text item delimiters
	set AppleScript's text item delimiters to theDelimiter
	set TextItems to text items of theText
	set AppleScript's text item delimiters to d
	return TextItems
end textItemDelimiters

on string_From_List(theList, theDelimiter)
	set theString to ""
	set theCount to 0
	repeat with thisItem in theList
		set theCount to theCount + 1
		set thisItem to thisItem as string
		if theCount ≠ (count of theList) then
			set theString to theString & thisItem & theDelimiter
		else
			set theString to theString & thisItem
		end if
	end repeat
	return theString
end string_From_List

on sort_list(theList)
	set old_delims to AppleScript's text item delimiters
	set AppleScript's text item delimiters to {ASCII character 10} -- always a linefeed
	set list_string to (theList as string)
	set new_string to do shell script "echo " & quoted form of list_string & " | sort -f"
	set new_list to (paragraphs of new_string)
	set AppleScript's text item delimiters to old_delims
	return new_list
end sort_list

@lerone regarding MetaImage.app, maybe you can get a refund?


Offtopic: Hi @BLUEFROG I’m working on a script that creates and sets custom thumbnails respecting the different record types (record, group etc.). It works fine in Script Debugger, but in DEVONthink it doesn’t work reliably. It sets a thumbail for record type “record” where it should set a thumbnail for e.g. type “group”. I’m going nuts on this, any ideas why it behaves different in Script Debugger and DEVONthink?

1 Like

thanks for further comments / help by everyone
– and sorry to everyone in here in case some things sound utterly confusing.

I guess this might be partly related to the fact that I am neither native scripter/coder nor native english speaker.

but to try helping with some clarifications/reactions:

I.@chrillek

Why? The end performSmartRule is not really more expensive than the first part.

… that simply because I wasn´t aware :slight_smile: I wasn´t trying to be code-economical here, or anything likewise. that is why I said ‘thank you’ for the pointer by a knowledgable person.

II.

I don’t understand what you mean here:

BUT I include that very same script that works as menu-launched script – and here it still doesn´t , even if enclosed that way…

Before you said my pointer “helped”, now it still doesn’t work? So in what way did it help?
Then you say it doesn’t work even if enclosed, but before you said you didn’t use the end part, so you didn’t really enclose it?

This is utterly confusing.

it´s like this:
a) the pointer helped because I wasn´t aware there is a closing line (see 1). so initially I only partially adapted the script to the the DT-environment (as I know by now)
b) then I enclosed the script that Pete graciously shared with those two lines at beginning and end (as you pointed out) - and it still didn´t work (– unlike triggering the original script from Pete in its original form via menu)

III. now with @pete31 new version it works!
– so, thanks again Pete! I guess you know that you are really helpful for someone like me!

and I might add: I wouldn´t have gotten there any other way given my (non-existent) script-skills!
especially I can see now the
end PerformSmartRule
is positioned in a way (not at the end) that I could never ever have figured out myself as a novice.

I really appreciate you putting up your time, and being helpful on all levels!

so, I am quite happy now. thanks, especially to Pete!
oliver


ps @pete31

@lerone regarding MetaImage.app, maybe you can get a refund?

… shows you are a really attentive person. thx for minding even these sideline details.
MetaImage is one of those accidental captures, meaning I will keep it anyways. it is very helpful in a lot of photo metadata contexts, and helpful bec it´s small, swift + agile. I use it for GPS-tagging for files in DT and other programs, as it is the most convenient way by far. and it has some more advantages in handling IPTC/EXIF and a host of other metadata. so, recommended to OSX/photo-people… :slight_smile:

Offtopic: Hi @BLUEFROG I’m working on a script that creates and sets custom thumbnails respecting the different record types (record, group etc.). It works fine in Script Debugger, but in DEVONthink it doesn’t work reliably. It sets a thumbail for record type “record” where it should set a thumbnail for e.g. type “group”. I’m going nuts on this, any ideas why it behaves different in Script Debugger and DEVONthink?

Hold the Option key and choose Help > Report bug to start a support ticket. Include the script and any pertinent info.

I apologize for my somewhat brisk remarks. I should have looked closer at the original script, then I might have noticed that it contains several subroutines/functions (“handler” in AppleScript parlance). That makes the positioning of the on … end pair a lot more complicated than I made it appear.

Some of this is due to my personal ignorance (and dislike) of AppleScript. I’d write the whole thing in JavaScript which would permit to nest the subroutines/functions inside the main function. Unfortunately, AppleScript doesn’t allow nested handlers.

thanks @chrillek for this really noble gesture; especially as one rarely sees such communicative ‘self-moderation’ / and -reflection in forums. its a sign of distinction.

then, I really meant I am grateful for the pointers you gave initially. as I was thankful that people minded my concern and spent time - after it initially seemed stalled. that includes you. and I think the remarks / scrutiny on enclosing moved the topic forward as well.

– … plus, being a parent and being confronted with learners/‘uninitiateds’ in still other fields, I understand some ‘professional’ impulses of impatience and rigor pulsate through all of us. :slight_smile:

so, kudos! + thx/appreciation again!

ps: and of course we are united in our hate of Apple Script, even if in it for different reasons ;-D