Permlink Replies: 0 - Pages: 1

Posts: 4,064
Registered: 18-Oct-2002
Pro tip: Managing image metadata
Posted: 6 Dec 18, 19:54
  Click to reply to this thread Reply
Every now and then you may want to perform certain operations on the metadata OF EVERY image of an album project, including sub folders. You may for instance want to clear all comments from images, convert the case, add a certain keyword to all images, copy the comments to the titles fields or vice versa, or swap them etc etc. We could in theory write up tools or make user interfaces for these needs but I want to show just how flexible it is to use jAlbum's system console with its interactive scripting language executor.

All examples here are assuming you're at least using jAlbum 17.1 and using the Groovy scripting engine.

Copy comments
Say you wish to copy the comments of each image to the title field, this can be done with the following scriptlet that relies on the very central AlbumObject API that jAlbum provides:
currentFolder.descendants.forEach(ao -> ao.title = ao.comment)
If you don't want existing titles overwritten, then you can modify the scriptlet like this:
for (ao : currentFolder.descendants) { if (ao.title.length() == 0) ao.title = ao.comment }

Swap comments and titles
currentFolder.descendants.forEach(ao -> { def tmp = ao.title; ao.title = ao.comment; ao.comment = tmp })

Exclude all images with rating lower than 3 -> ao.rating < 3).forEach(ao -> ao.included = false)

I hope you get the idea :-). I happened to show two ways of iterating all album objects: the classic for loop and the forEach method and lambda expressions. Use whatever you prefer. With basic stuff like this it doesn't matter which one you choose. The lambda based construct allows for more advanced stuff like this example that finds and prints the full path of all album objects that contains "David" in the keyword list: -> ao.keywords.contains("David")).forEach(ao -> println(ao.file.absolutePath))
Well, even in this case, a classic for loop is shorter: -> ao.rating < 3).forEach(ao -> ao.included = false)
, but consider this one, using parallelStream instead of stream:
currentFolder.descendants.parallelStream().filter(ao -> ao.keywords.contains("David")).forEach(ao -> println(ao.file.absolutePath))
This version scans and prints all album objects containing the keyword "David" in parallel, i.e. by using multiple threads. If the operation performed on each object is of a heavier kind, then such things matter. Here's for instance an example that prints the name of each image of an album along with its shutter speed:
  .filter(ao -> ao.category == Category.image)
  .forEach(ao -> println( + ": " + ao.vars.meta?.get("Shutter Speed Value")))
Sample output:
IMG_4398.JPG: 1/39 sec
IMG_4399.JPG: 1/79 sec
IMG_4400.JPG: 1/501 sec
IMG_4401.JPG: 1/501 sec
IMG_4402.JPG: 1/501 sec
IMG_4405.JPG: 1/501 sec
IMG_4406.JPG: 1/501 sec
IMG_4407.JPG: 1/403 sec
IMG_4408.JPG: 1/250 sec
IMG_4409.JPG: 1/158 sec
IMG_4410.JPG: 1/317 sec
This example is using Groovy's nifty null safe dereferencing (?.) as some images may not have camera metadata, and hence no "meta" object to look up EXIF data in. It also demonstrates Groovy's shorthand syntax for looking up values in maps. "meta" is actually a key in the vars map, so you'd ordinarily need to write ao.vars.get("meta") but Groovy allows the neat shorthand ao.vars.meta

If you want to manipulate objects in the current folder only, simply change "decendants" to "children".
Forum admins
Helpful Answer
Correct Answer

Point your RSS reader here for a feed of the latest messages in all forums