AUTOMATED SLIDESHOW USING IMAGE EVENTS

Our previous blog post introduced you to one of AppleScripts’ little helper applications that within a script can reduce a laborious task down to a keyboard shortcut. The application is Image Events.

Image Events

Should you have a process that requires getting some information from an image file or altering that file in a particular way, Image Events may be the answer. Image events has a limited dictionary, but its light weight can definitely be an advantage in a number of situations. Within its Image Suite are methods such as crop, flip, pad, scale, and rotate. Properties for images include bit depth, color space, dimensions, file type, name, and resolution.

Our example script will take advantage of Image Event’s ability to return the size (dimensions) and resolution (ppi) of a file. This will be used to determine the size of an InDesign document for building our Slideshow project from scratch using an AppleScript script.

To work with a file, Image Events needs to open the file. After working with the file, the script must make sure that the file is closed when done. The following demonstration script does just that. It uses the choose file method to have the user select the file for processing. The script assumes that the user has chosen an image file.

Image Events

set thePrompt to "Choose image file for processing"
set theFile to choose file with prompt thePrompt without multiple selections allowed
tell application "Image Events"
set fileRef to open theFile
set theProps to properties of fileRef
close fileRef
end tell
theProps

The properties returned from this simple snip of code should read similar to the following:
{color space:RGB, image file:file “Macintosh HD:Users:username:Desktop:Working:Images:10_ThunderheadClouds_0272.jpg” of application “Image Events”, bit depth:millions of colors, dimensions:{1024, 728}, location:folder “Macintosh HD:Users:shirleyhopkins:Desktop:Working:Images:” of application “Image Events”, embedded profile:profile “sRGB IEC61966-2.1” of image “10_ThunderheadClouds_0272.jpg” of application “Image Events”, file type:JPEG, class:image, name:”10_ThunderheadClouds_0272.jpg”, resolution:{96.0, 96.0}}

That’s a lot of information from just a few lines of code. You may be thinking of a number of ways this could be applied to an automation solution in InDesign. One task that I do quite often takes advantage of InDesign’s Publish to Web. It takes a folder of files and creates a slideshow that can be placed on a web page or linked from an email. The only requirement is that the images used for the slideshow are all the same size, and resolution. To designate the order in which the files will be shown, the names for the files can be prepended with a number (“01Image.jpg”, “02Image.jpg”, and so on).

SLIDESHOW

The script to perform the magic starts by having the user select a folder with the files that have been prepared for the slideshow. With the assumption that all files in the folder chosen have the same size, resolution, and file type, the script gets the dimensions and resolution for one of the files in the folder. This can be written up as a handler so it can be used in any number of other automation projects.

Get Image Files With Info

set thePrompt to "Choose folder of files for slideshow"
set theResult to getImageFilesWithInfo(thePrompt)
copy theResult to {folderPath, fileList, theDim, theRes}

--the handler
on getImageFilesWithInfo(thePrompt)
set theFolder to choose folder with prompt thePrompt
set fileList to list folder theFolder without invisibles
if length of fileList is not less than 3 then
set folderPath to theFolder as string
set filePath to (folderPath & item 1 of fileList)
tell application "Image Events"
set fileRef to open file filePath
set theDim to dimensions of fileRef
set theRes to resolution of fileRef
close fileRef
end tell
else
error ("Requires folder of files to be chosen")
end if
return {folderPath, fileList, theDim, theRes}
end getImageFilesWithInfo

The result of this handler should return all the image file information needed for the Slideshow project. The value of the variable theDim is a list of integers representing the width and height of the image sampled. Similarly, the value of the variable theRes lists two real number values with the horizontal resolution before the vertical as in {96.0, 96.0}.

The script can now use the value of theDim to determine the size of the document to be created in InDesign. If navigation buttons are to be placed outside of the slideshow, values will need to be added to the document’s page width and/or page height to accommodate the buttons. To keep the script simple, we will assume the buttons will be centered below the images and will require 60 points to be added to the page height.(measurements are in pixels). The script at this point should look similar to the following but with the getImageFilesWithInfo handler added. (Don’t forget to add the handler.)

 Create Document

set thePrompt to "Choose folder of files for slideshow"
set bottomMargin to 60
set marginList to {0, 0, bottomMargin, 0}
set theResult to getImageFiles(thePrompt)
copy theResult to {folderPath, fileList, theDim, theRes}
set pgHgt to (item 2 of theDim) + bottomMargin
set pgWid to (item 1 of theDim)
set marginList to {0, 0, bottomMargin, 0}
set docRef to createDocument(pgWid, pgHgt, marginList)


(*Creates document using preset*)
on createDocument(pgWid, pgHgt, marginList)
   tell application "Adobe InDesign CC 2018"
	set measurement unit of script preferences to points
	copy marginList to {tp, lt, bt, rt}
	set myPreset to make document preset with properties {page width:pgWid,¬
 page height:pgHgt, pages per document:1, page orientation:landscape, intent:web intent,¬
 facing pages:false, create primary text frame:false, top:tp, left:lt, bottom:bt, right:rt}
	set docRef to make document with properties {document preset:myPreset}
   end tell
   return docRef
end createDocument

If you test the script at this point you should have an Untitled document created with all margins except the bottom maargin set to 0.

MULTI-STATE OBJECT

To create the slideshow, our example script will use a multi state object. When this object is created it contains two states by default. The script will need to create additonal states to accommodate the remaining images. For each state the script will then create a container (a rectangle) into which each image of the file list will be placed. For this we will create another handler just for creating the multi state object. The handler requires a reference to the active document, the geometric bounds for the multi state object, the path to the image folder, and a list of the images.

 

We will add three statements to our script above: one to change the value of the folder chosen from an alias reference to a string, one to define the bounds of the multi state object, and one to call the createMSO handler.

set folderPath to folderRef as string
set gBounds to {0, 0, pgHgt - bottomMargin, pgWid}
set MSObject to createMSO(docRef, gBounds, folderPath, fileList)

The createMSO handler adds the multi state object to the page and adds states. Within a repeat with loop each state creates a container (rectangle) for the image, and places the file. Here is the script at this point without the createDocument and getImageFilesWithInfo handlers. (Make sure your script has these two handlers included.)

set thePrompt to "Choose folder of files for slideshow"
set bottomMargin to 60
set theResult to getImageFilesWithInfo(thePrompt)
copy theResult to {folderRef, fileList, theDim, theRes}
set pgHgt to (item 2 of theDim) + bottomMargin
set pgWid to item 1 of theDim
set marginList to {0, 0, bottomMargin, 0}
set docRef to createDocument(pgWid, pgHgt, marginList)
set folderPath to folderRef as string
set gBounds to {0, 0, (pgHgt - bottomMargin), pgWid} --bounds for multi state object
set MSObject to createMSO(docRef, gBounds, folderPath, fileList)

--the handler
on createMSO(docRef, gBounds, folderPath, imageList)
set statesToAdd to (length of imageList) - 2
tell application "Adobe InDesign CC 2018"
tell docRef
set layerRef to layer 1
set MSORef to make multi state object with properties {name:"Slideshow", geometric bounds:gBounds, item layer:layerRef}
repeat with i from 1 to statesToAdd
tell MSORef to make state
end repeat
end tell
repeat with i from 1 to length of imageList
set nameStr to item i of imageList
set fileAlias to (folderPath & nameStr) as alias
tell state i of MSORef
set rectRef to make rectangle with properties {geometric bounds:gBounds, name:("State" & i), fill color:"None", strokeweight:0}
end tell
tell rectRef to place fileAlias
end repeat
end tell
return MSORef
end createMSO
--add createDocument and getImageFilesWithInfo handlers here

A SMALL PROBLEM

When you run the script at this point you may be surprised to see that the image does not fill the bounds of the multi-state object.

…Settings for image in Photoshop

The problem is that an 800px x 600px image saved at 96 px/inch will actually measure 600px by 450px pixels when placed in an InDesign document  if its resolution is maintained at 96 ppi. The problem is that InDesign creates the document at 72 ppi but maintains the actual ppi for the image when it is placed.

…Image does not fill document

You can check this out by dragging the image in your multi state object to fill the document created by the script. View the image in the Links panel, and you will see, after resizing, the effective resolution is now 72 ppi.

The idea of the script, however, is to create a slideshow based on the images provided. With this in mind, you don’t want to fiddle with the image’s resolution. This is the reason that we had the script return the image’s resolution as part of the getImageFilesWithInfo handler. (ImageEvents is returning the file dimensions based on its actual resolution.)

Since scripts are marvelous at math, the easiest solution to the problem which is to create an InDesign document based on the image’s actual size and resolution, is to alter the size of the document to fit the image.

For this, our script will add tests to check the resolution returned for the image and perform a little mathematics to adjust the size for the document. This will happen immediately after the variables for pgWid and pgHgt are defined.

if item 1 of theRes is not 72 then
   set pgWid to round (72 / (item 1 of theRes) * (item 1 of theDim))
end if
if item 2 of theRes is not 72 then
   set pgHgt to round (72 / (item 2 of theRes) * (item 2 of theDim))
end if

The top of the script will now read as follows:

set thePrompt to "Choose folder of files for slideshow"
set bottomMargin to 60
set theResult to getImageFilesWithInfo(thePrompt)
copy theResult to {folderRef, fileList, theDim, theRes}
if item 1 of theRes is not 72 then
   set pgWid to round (72 / (item 1 of theRes) * (item 1 of theDim))
end if
if item 2 of theRes is not 72 then
   set pgHgt to round (72 / (item 2 of theRes) * (item 2 of theDim))
end if
set pgHgt to pgHgt + bottomMargin
set marginList to {0, 0, bottomMargin, 0}
set docRef to createDocument(pgWid, pgHgt, marginList)
set gBounds to {0, 0, pgHgt - bottomMargin, pgWid}
set MSObject to createMSO(docRef, gBounds, folderPath, fileList)

With this change the page display is a little smaller but the image resolution is as saved (actual ppi).

BUTTONS

All that is left for the script is to add the buttons to navigate the slideshow. Because there are so many options for creating buttons, we will leave this discussion for our next blog post. Just to test to make sure the multi state object will now work as intended, we can add a couple of rectangles to the page to serve as buttons connected to the multi state object. This will work just fine for testing.

We now need to add two variables to the top of the script to define values for the buttons.

set btnDim to {36, 36}--size of the buttons
set btnOset to 8 --distance on both sides of button in points

Then after the call to MSObject we will add a callto a handler named addButtons.

set btnList to addButtons(docRef, MSObject, btnDim, btnOset, gBounds, pgWid, pgHgt)

Our script at this point will look like the following (without the createDocumentgetImageFilesWithInfo, and createMSO handlers). Make sure your script includes these handlers.

set thePrompt to "Choose folder of files for slideshow"
set btnDim to {36, 36}
set btnOset to 8 --distance on both sides of button in points
set bottomMargin to 60
set marginList to {0, 0, bottomMargin, 0}
set theResult to getImageFiles(thePrompt)
copy theResult to {folderRef, fileList, theDim, theRes}
if item 1 of theRes is not 72 then
   set pgWid to round (72 / (item 1 of theRes) * (item 1 of theDim))
end if
if item 2 of theRes is not 72 then
   set pgHgt to round (72 / (item 2 of theRes) * (item 2 of theDim))
end if
set pgHgt to pgHgt + bottomMargin
set marginList to {0, 0, bottomMargin, 0}
set docRef to createDocument(pgWid, pgHgt, marginList)
set folderPath to folderRef as string
set gBounds to {0, 0, pgHgt - bottomMargin, pgWid}
set MSObject to createMSO(docRef, gBounds, folderPath, fileList)
set btnList to addButtons(docRef, MSObject, btnDim, btnOset, gBounds, pgWid, pgHgt)

--the addButtons handler
on addButtons(docRef, MSObject, btnDim, btnOset, gBounds, pgWid, pgHgt)
set btnWid to item 1 of btnDim
set btnHgt to item 2 of btnDim
set by0 to (item 3 of gBounds) + btnOset
set by1 to (item 3 of gBounds) + (btnHgt + btnOset)
set cPage to pgWid / 2
set bx0 to cPage - (btnWid + btnOset)
set bx1 to cPage + (btnWid + btnOset)
set btn1Bounds to {by0, (cPage - (btnWid + btnOset)), by1, (cPage - btnOset)}
set btn2Bounds to {by0, (cPage + btnOset), by1, (cPage + btnOset + btnWid)}
tell application "Adobe InDesign CC 2018"
set fColor to "RGB Cyan"
set sWid to 1
set fTint to 100
set sColor to "Black"
set btnProps to {fill color:fColor, fillTint:fTint, stroke weight:sWid, stroke color:sColor}
   tell page 1 of docRef
      set btn1 to make button with properties {geometric bounds:btn1Bounds} & btnProps
      tell btn1
         make goto previous state behavior with properties {associated multi state object:MSObject, behavior event:mouse down, enable behavior:true, loops to next or previous:true}
      end tell
      set btn2 to make button with properties {geometric bounds:btn2Bounds} & btnProps
      tell btn2
         make goto next state behavior with properties {associated multi state object:MSObject, behavior event:mouse down, enable behavior:true, loops to next or previous:true}
      end tell
   end tell
end tell
return {btn1, btn2}
end addButtons

…Slideshow with buttons for testing

ONWARD AND UPWARD

To make your script bulletproof add a try/on error trap around the top portion of of the script to catch the error that occurs if the user clicks Cancel instead of selecting the folder images. This will also catch any errors generated in the handlers. Also, you will want to add appropriate comments at the top of the script and at the top of each handler to remind yourself and others who may read the script what the script and its handlers are all about (what it does, what arguments are expected, and what if any is returned from the handler.

Disclaimer

Scripts provided are for demonstration only. No representation is made as to the completeness or auracy of the scripts Users are advised to use at their own discretion.