Copy GPS Metadata Back into Photoshop TIFFs with Bridge Scripting

Photographers capturing GPS metadata and using Photoshop may run into problems saving  TIFF files—Photoshop may strip the GPS metadata when it saves a file as TIFF. This bug does not affect other file formats (PSD and JPEG for example), but if your customers require TIFF files instead of PSD, there is no obvious way to avoid the problem using Photoshop. The good news is Bridge can help. Bridge can write GPS metadata into TIFFs, so you can use Bridge scripting to automate copying missing GPS metadata back into TIFF files.

Below is an example script that shows how to convert PSDs to TIFFs with GPS metadata. It illustrates using several scripting APIs in the Creative Suite in concert: file system access, inter-application communication, advanced XMP metadata scripting, and user interface scripting. I wrote this example for script writers who already know the basics of JavaScript programming, but who may be new to scripting Bridge.

Download the complete script here.

My example assumes you can work on PSD files instead of TIFF in Photoshop. I imagine a workflow where you use this script to create final TIFFs from your final PSD files. If you’re already in the middle of a project using TIFF files, I’ll show briefly at the end how you can adapt the script to copy GPS metadata from original files back into your GPS-deprived TIFFs.

Step by step, here’s what the script does:

  1. Asks the user to find a folder to process.
  2. Gets all the PSD files in the folder.
  3. For each of the PSD files…
  4. Tells Photoshop to open the PSD and save it as a TIFF.
  5. Gets the metadata from the PSD file and the new TIFF file.
  6. Copies the GPS properties from the PSD to the TIFF’s metadata.
  7. Puts the updated metadata into the TIFF.

Here is how the script starts…

#target bridge
// Load XMPScript if it's not already
if( ! ExternalObject.AdobeXMPScript ) {
ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript");
}

The standard "#target bridge" directive at the beginning lets you run the script in Bridge by double-clicking the file. Next at the start of the script, we need to load AdobeXMPScript, an extra library of that gives scripts access to the XMPToolkit we will use to modify metadata and update metadata.

Two functions form the heart of our script: copyAllGPSProperties and copyGPSMetadata.

var copyAllGPSProperties = function( sourceXMPMeta, targetXMPMeta )
{
// Get an Iterator for all the properties in the EXIF namespace
var iter = sourceXMPMeta.iterator(XMPConst.ITERATOR_JUST_CHILDREN, XMPConst.NS_EXIF, "" );

var prop = iter.next();
while(prop) {

if( prop.path.indexOf("GPS") > 0 )
{
targetXMPMeta.setProperty( prop.namespace, prop.path, prop.value, prop.options );
}
prop = iter.next();
}
}
// copyAllGPSProperties

The two arguments passed to copyAllGPSProperties, sourceXMPMeta and targetXMPMeta, must be of type XMPMeta. XMPMeta is a class provided by AdobeXMPScript. These objects represent XMP that’s been parsed by the XMP toolkit, providing access to the XMP data model in memory. XMPMeta’s API is safer and easier to use than trying to manipulate XMP in XML format, and it is more powerful than Bridge scripting’s Metadata class.

All the GPS properties are in XMP’s Exif namespace and have names beginning with “GPS.” Function copyAllGPSProperties creates an iterator for all the Exif properties in the source metadata. As it iterates over the Exif properties, it copies each property with "GPS" in its name property to the target.

The copyGPSMetadata function handles reading and writing XMP from files.

var copyGPSMetadata = function( sourceFile, targetFile )
{
if( ! sourceFile.exists )
return;
var sourceThumb = new  Thumbnail( sourceFile );
if( ! targetFile.exists )
return;
var targetThumb = new Thumbnail( targetFile );
var sourceXMPMeta = new XMPMeta( sourceThumb.synchronousMetadata.serialize() );
var targetXMPMeta = new XMPMeta( targetThumb.synchronousMetadata.serialize() );
copyAllGPSProperties( sourceXMPMeta, targetXMPMeta );
// Attempt to open the target file for writing metadata.
// Require the use of a "smart" handler -- designed to correctly handle the file format
var xmpFile = new XMPFile( targetFile.fsName, XMPConst.FILE_UNKNOWN, XMPConst.OPEN_FOR_UPDATE | XMPConst.OPEN_USE_SMART_HANDLER );
if( xmpFile.canPutXMP( targetXMPMeta ) )
{
xmpFile.putXMP( targetXMPMeta );
xmpFile.closeFile(XMPConst.CLOSE_UPDATE_SAFELY);
} else {
xmpFile.closeFile();
}
}
//copyGPSMetadata

The function takes two arguments: File objects for the source and target files. The File class is one of the core JavaScript classes available throughout the Creative Suite. The first part of the function checks to make sure the files exist and creates a Bridge Thumbnail object to access the metadata.

In Bridge scripting, Thumbnail objects provides access to Bridge’s internal data model for items you can browse. Think of there being a Thumbnail object virtually behind each visible thumbnail you see in the Content pane. Scripts, however, can create Thumbnails objects directly from a File object or file path, without ever actually browsing the files.

To make XMPMeta objects for copyGPSMetadata function, we have first extract metadata from the files.

var targetThumb = new Thumbnail( targetFile );
var targetXMPMeta = new XMPMeta( targetThumb.synchronousMetadata.serialize() );

There is a lot going on in the second line of code, above. I could have written it with three lines of code instead, which makes the code easier to follow…

var sourceMetadata = sourceThumb.synchronousMetadata;
var sourceXmpXml = sourceMetadata.serialize();
var sourceXMPMeta = new XMPMeta( sourceXmpXml );

The first line uses the Thumbnail’s "synchronousMetadata" property to get a Metadata object. It’s important to use "synchronousMetadata" and not the Thumbnail’s "metadata" property in this script. Bridge usually performs metadata extraction in the background—and on demand. Unless we’ve already browsed the PSDs, accessing "metadata" will only return a small Metadata object and enqueue a new background task to fetch and cache the complete metadata. Using the "synchronousMetadata" property tells Bridge to immediately extract and return all available metadata from the file.

The second and third lines create the XMPMeta object. XMPMeta’s constructor will not accept Bridge Metadata or Thumbnail objects. It accepts a string of XMP as XML, which we can get from the Metadata object’s "serialize" function.

To update the TIFF with the lost GPS properties, our script opens and updates the TIFF’s metadata using another class from AdobeXMPScript, XMPFile.

var xmpFile = new XMPFile( targetFile.fsName, XMPConst.FILE_UNKNOWN, XMPConst.OPEN_FOR_UPDATE | XMPConst.OPEN_USE_SMART_HANDLER );

XMPFile gives scripts direct access to the XMP Toolkit’s metadata reading and writing smarts for various file formats. But why not just ask Bridge to update the TIFF’s metadata? Because we want to make sure our script always uses the XMP Toolkit to update the TIFF files metadta.

Unless our Camera Raw preferences are set to disable TIFF support, Bridge may use the Camera Raw plugin to update metadata in TIFF files. Unfortunately, the current Camera Raw plugin won’t completely repair Photoshop’s TIFFs. GPS metadata can be stored in two parts of the file: the XMP packet and a block of Exif metadata. The Exif block is really the "right" place to store GPS metadata in TIFF and JPEG. Camera Raw’s limitation is not the same as Photoshop’s GPS bug, but it is another stumbling block to scripting GPS metadata. Camera Raw won’t strip GPS from files that have it, and its Save Image feature will export TIFFs with GPS—it just won’t add an Exif GPS block when updating files that don’t already have one.) Using XMPFile will, so our script will add an Exif GPS block to the TIFF files from Photoshop.

Because XMPFile supports several file formats, I’ve chosen to pass "FILE_UNKNOWN" when opening the file, even though this example script is processing TIFF files. I want copyGPSMetadata() to be reusable in scripts processing other file formats. By using the option "OPEN_USE_SMART_HANDLER," support is limited to formats with "smart" handler support. A smart handler is code written to read and write metadata correctly for a specific file format—for example the TIFF handler will update both the Exif block and the XMP packet.

(You can also use XMPFile to read XMP metadata from files. Using the Bridge Thumbnail’s synchronizedMetadata property instead will give our script the ability to read GPS metadata from Bridge-supported file types not supported by the XMPFile smart handlers, like camera raw file formats.)

Back to the example at hand. We need to automate feeding PSD and TIFF files to the CopyGPSMetadata function. The selectDialog function of the Folder class, pops up a file-chooser dialog and returns the folder the user selects. We’ll use that to get a folder of PSDs to process. The Folder’s “getFiles” function can return a filtered array of all the files in the folder with the extension “.psd."

var sourceFolder = Folder.selectDialog("Select a folder of PSDs with GPS Metadata");
var psdFiles = sourceFolder.getFiles("*.psd");

Next the script will process the PSDs one at a time. You may notice no “for loop” anywhere for the array of PSD files, and loops may be the way you may be used repeating a task in your scripts. If you have not done any sort of “event based” or inter-application programming before, you might find the rest of the script hard to understand.

To process the files the script uses another Creative Suite scripting feature, BridgeTalk, to send a message to Photoshop. The message contains a script Photoshop will run. After the Bridge script sends the message it continues to its next line; the script does not automatically wait for Photoshop to receive the message and run its script. To continue our workflow after Photoshop is done, we need to register called a "callback function" with the BridgeTalk messages. The callback receives a message returned by Photoshop when it’s done. Then we can fix the TIFF’s GPS metadata, and process the next PSD. Here is how things flow…

  1. The script uses a counter variable to keep track of which PSD is next.
  2. A function in the script sends a BridgeTalk message to Photoshop for the next PSD file.
  3. Photoshop opens the PSD and save it as a TIFF.
  4. BridgeTalk calls our callback function when Photoshop is done saving the file.
  5. Our callback calls copyGPSMetadata with with the PSD and new TIFF file.
  6. Our callback increments the counter. If there are more PSDs to process, we go back to step 2.
  7. When we get to the end of the array of PSDs, the script is done.
// Open all .PSD files in a folder, save them as TIFF, then fix the GPS metadata
var sourceFolder = Folder.selectDialog("Select a folder of PSDs with GPS Metadata");
var psdFiles = sourceFolder.getFiles("*.psd");
var psdCounter = 0;
// This callback fundtion is called after a PSD file has been saved as a TIFF by Photoshop
var onPhotoshopDone = function( message )
{
var lastPsd = psdFiles[psdCounter];
// Photoshop has sent us back the path to the new TIFF
var tif = new File( message.body );
copyGPSMetadata( lastPsd, tif );
++psdCounter;
if( psdCounter < psdFiles.length )
{
saveNextPsdAsTiff();
} else {
alert("All Done!");
}
}
// This should be called if an error occured in Photoshop
var onPhotoshopError = function( message )
{
alert("Ooops--unexptected error from Photoshop: " + message.body );
}
var saveNextPsdAsTiff = function()
{
var psd = psdFiles[psdCounter];
var scriptForPs = "var psd = new File('" + psd.fsName + "');\
origDoc = app.open( psd );\
var tifFile = new File( psd.fsName.slice(0, -3) + 'tif' );\
var saveOptions = new TiffSaveOptions();\
origDoc.saveAs( tifFile, saveOptions );\
origDoc.close( SaveOptions.DONOTSAVECHANGES );\
tifFile;"
var bt = new BridgeTalk();
bt.target = "photoshop";
bt.body = scriptForPs;
bt.onResult = onPhotoshopDone;
bt.onError = onPhotoshopError;
bt.send();
}
saveNextPsdAsTiff();

The script ends with a call to saveNextPsdAsTiff to kick off the automation. Note that I’ve also added another callback function “onPhotoshopError.” This will pop up an alert box incase something goes wrong in Photoshop.

In case you are new to some of the scripting concepts I used, here are some notes that might help you understand what these functions are doing and why they work—or at least I hope they help you avoid some easy-to-make mistakes in your own scripts.

  • When BridgeTalk passes our callback functions a "message," the message is an object, not a string. Message.body has the data or instructions that’s usually of interest.
  • The body of our BridgeTalk message to Photoshop, in saveNextPsdAsTiff, is JavaScript that Photoshop will evaluate, but in the context of our Bridge script, it’s just a string that is sent in a message. That script does not run until after we call bt.send() and Photoshop receives the message.
  • The backslash character in JavaScript is handy for splitting long, quoted, strings into more than one line, but be sure to use a single quote character (‘) inside your quoted string.
  • File.fsName is the best way to get a file path you can pass between applications.
  • The last line of the script for Photoshop just has the variable name "tifFile" alone. The last thing the Photoshop script evaluates will be sent back to Bridge in the "body" property of message sent to our "onPhotoshopDone" function. If you leave this out, the script won’t work.
  • Be careful to use just the name of your callback functions when registering them with the BridgeTalk Message, for example: bt.onResult = onPhotoshopDone;. Accidentally using a function call (by adding parenthesis to the end: bt.onResult = onPhotoshopDone();) would break the script.

How to Run the Finished Script

  1. Download and unzip the script from the link at the top of the page.
  2. Launch Bridge and browse to the folder containing the script, CopyGPSMetadata.jsx
  3. Double click the thumbnail for CopyGPSMetadata.jsx in Bridge.
  4. If prompted, click "Yes" to give Bridge permission to run the script.
  5. A folder-chooser dialog box appears. Find a folder of PSD files with GPS metadata.
  6. Sit back and let the script work, when it’s done an "All Done!" alert appears.

The script will create a TIFF file for each PSD file in the folder. The name of the TIFF will match the PSD’s name, but with a ".tif" extension. Make sure you select a folder for which you have permission to add files.

What if You Already Have TIFF Files with GPS Metadata Missing?

You can reuse the two core functions in the example script to add GPS metadata to existing TIFF files that have lost it if you still have original files with the GPS metadata intact. A script to do this is actually not as complex as this example—it does not require automating Photoshop. Here’s an example, assuming that you have the original raw files (CR2s in this example) in one folder, working TIFFs in another folder, and the file names match.

var origFolder = Folder.selectDialog("Select a folder of CR2s with GPS Metadata");
var tifFolder = Folder.selectDialog("Select a folder of TIFFs with matching names.");
var raws = origFolder.getFiles("*.CR2");
for( r in raws )
{
var raw = raws[r];
var tif = new File( tifFolder.fsName + "/" + raw.name.slice(0, -3) + "tif");
copyGPSMetadata( raw, tif );
}

Keep the copyAllGPSProperties and copyGPSMetadata functions from the example script, but replace everything below copyGpsMetadata with the code above. If you’ve renamed your files, you might need to add more complicated file-name matching code to find the right TIFF file.

Additional Documentation & Help

For a complete reference to Bridge’s Thumbnail, Metadata and other classes available to Bridge scripts, get the Adobe Bridge CS4 SDK. The SDK also contains several excellent sample scripts.

XMPScript and its classes are documented in the JavaScript Tools Guide available for download here. See the chapter "Scripting Access to XMP Metadata."

The File and Folder classes, BridgeTalk, and other APIs common to all scriptable Creative Suite applications are also documented in the JavaScript Tools Guide. See the chapters "File System Access" and "Interapplication Communication with Scripts".

The Guidelines for Handling Image Metadata, from the Metadata Working Group, available for download here, includes an easy to understand description of how the various "blocks" of data, like Exif and XMP are stored in JPEG and TIF files

You can lean more about XMP by downloading the three XMP Specification documents available here.