Blog About Contact

How to: 10000 photos from Adobe Photoshop Album 2 to Picasa

Published Tue, 2 Nov 2010 • 7 comments

If you're (un)fortunate enough to have your entire photo collection stored in the now-ancient Adobe Photoshop Album 2, you're going to be rather stuck.

In theory, it should be possible to do "backup" operation from Album 2.0 and import into it's successor Photoshop Elements.

My research suggested that this may be hit-and-miss with several people experiencing bugs that rendered the backup useless on import. I tried - and experienced exactly the same issue.

Anyhow, after some consideration I decided to switch to Picasa, since it's -

So the next challenge was - how do I get all the photos from Photoshop Album to Picasa with all the meta-data intact?

Step 1 - Get the meta-data out of Photoshop Album

Fortunately someone has already solved this part of the problem. I found this post here which links to a (no longer available) tool for reading the meta data out of Album's (Access format?) ODBC data source.

I've cached a copy of the tool here - psatools.zip

I tried running the tool on the actual Photoshop Album database in the C:\Program Files folder, however it failed with a weird error. I found a workaround to that issue - export a full backup through Photoshop Album, and then run the PSATool on the backup file, e.g.

psatool /if-print /fp /xml "backup.psb" > output.xml

This will output the content of the Photoshop Album backup as XML with all the meta-data intact (captions, tags, albums). The "/fp" switch is required to output the full path to the original image file. We'll need this for the following step.

Step 2 - Clean up, reorganise and apply meta-data to image files

There are two components required for this step.

One is exiftool - a command line tool for reading and writing meta data from image files.

The second is a custom Java program which I wrote (listed below). The Java program iterates through the XML meta data exported from the Photoshop Album backup and simultaneously reorganises (renames and copies) the images based on their captions and collections and adds this meta data (captions and tags) into the image using exiftool.

You'll need a couple of libraries for the Java code to run -

You'll probably also need to revise the code below to suit your own needs. It's posted as a starting point only.

Be aware this is really rough and nasty code - it's not intended to be used more than once. :)

It assumes that the output XML from step 1 is in "I:\Temp\output.xml", the source picture files are accessible on the current PC and that you want to output to "I:\Temp\output" folder for the converted images.

import com.visural.common.Function;
import com.visural.common.Function;
import com.visural.common.StringUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.HashSet;
import java.util.Set;
import javolution.text.CharArray;
import javolution.xml.stream.XMLInputFactory;
import javolution.xml.stream.XMLStreamConstants;
import javolution.xml.stream.XMLStreamReader;

/**
 *
 * @author Richard Nichols
 */
public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws Exception {
        XMLInputFactory factory = XMLInputFactory.newInstance();
        XMLStreamReader reader = factory.createXMLStreamReader(new FileInputStream("i:/temp/output.xml"));

        Set<Photo> photos = new HashSet<Photo>();
        Photo photo = null;
        CharArray blank = new CharArray("");

        while (reader.getEventType() != XMLStreamConstants.END_DOCUMENT) {
            switch (reader.next()) {
                case XMLStreamConstants.START_ELEMENT:
                    if (reader.getLocalName().equals("file")) {
                        // Reads primitive types (int) attributes directly (no memory allocation).
                        String type = Function.nvl(reader.getAttributeValue(null, "type"), blank).toString();
                        String name = Function.nvl(reader.getAttributeValue(null, "name"), blank).toString();
                        String caption = Function.nvl(reader.getAttributeValue(null, "caption"), blank).toString();
                        if (type.equals("photo")) {
                            if (name.toLowerCase().endsWith(".jpg") || name.toLowerCase().endsWith(".jpeg")) {
                                photo = new Photo(name, caption);
                                photos.add(photo);
                            } else {
                                System.out.println("Skipped photo: "+name);
                            }
                        }
                    }
                    if (reader.getLocalName().equals("folder")) {
                        if (photo != null) {
                            String type = Function.nvl(reader.getAttributeValue(null, "type"), blank).toString();
                            String value = Function.nvl(reader.getElementText(), blank).toString();
                            if (StringUtil.isNotBlankStr(value)) {
                                if ("Tag".equalsIgnoreCase(type)) {
                                    photo.getTags().add(value);
                                }
                                if ("Collection".equalsIgnoreCase(type)) {
                                    if (value.startsWith("\"")) {
                                        value = value.substring(1);
                                    }
                                    if (value.endsWith("\"")) {
                                        value = value.substring(0, value.length()-1);
                                    }
                                    photo.getCollections().add(value);
                                }
                            }
                        }
                    }
                    break;
                case XMLStreamConstants.END_ELEMENT:
                    if (reader.getLocalName().equals("file")) {
                        photo = null;
                    }
                    break;
            }
        }
        reader.close(); 

        // fix filenames & collections
        Set<String> collections = new HashSet<String>();
        int numTwo = 0;
        for (Photo p : photos) {
            // remove any unwanted collections here
            p.getCollections().remove("unwanted collection");

            if (p.getCollections().isEmpty()) {
                p.getCollections().add("No Collection");
            } else if (p.getCollections().size() > 1) {
                numTwo++;
            }
            collections.addAll(p.getCollections());
        }

        for (String s : collections) {
            new File("i:/temp/output/" + safeFilename(s)).mkdirs();
        }

        int n = 0;
        for (Photo p : photos) {
            n++;
            if (n%50 == 0) {
                System.out.println(n+" of "+photos.size());
            }

            String collection = p.getCollections().iterator().next();
            File i = new File(p.getFilename());
            if (!i.exists()) {
                System.err.println("MISSING FILE: "+p.getFilename());
                continue;
            }
            String outname = p.getCaption();
            if (StringUtil.isBlankStr(outname)) {
                outname = p.getFilename().substring(p.getFilename().lastIndexOf('\\'));
            }
            File o = new File("i:/temp/output/" + safeFilename(collection)+"/"+safeFilename(outname)+".jpg");
            if (o.exists()) { // allows continue after partial conversion
                continue;
            }
            copyFile(i, o);
            tagAndCapFile(o, p.getCaption(), p.getTags());
        }
    }

    private static String safeFilename(String name) {
        for (char c : StringUtil.FILENAME_INVALID_CHARS) {
            name = name.replace(String.valueOf(c), " ");
        }
        return name.trim();
    }

    public static void copyFile(File in, File out) throws IOException {
        FileChannel inChannel = new FileInputStream(in).getChannel();
        FileChannel outChannel = new FileOutputStream(out).getChannel();
        try {
            inChannel.transferTo(0, inChannel.size(),
                    outChannel);
        } catch (IOException e) {
            throw e;
        } finally {
            if (inChannel != null) {
                inChannel.close();
            }
            if (outChannel != null) {
                outChannel.close();
            }
        }
    }

    private static void tagAndCapFile(File o, String caption, Set<String> tags) throws IOException, InterruptedException {
        //System.out.println("Tagging and capping: "+o.getCanonicalPath());
        {
            String run = "lib\\exiftool.exe \"-iptc:caption-abstract="+caption.replace('\"', ' ')+"\" \""+o.getCanonicalPath()+"\" -overwrite_original";
            Process p = Runtime.getRuntime().exec(run);
            int i = p.waitFor();
            if (i != 0) {
                System.err.println("Failed running: "+i+"\n"+run);
            }
        }

        for (String tag : tags) {
            String run = "lib\\exiftool.exe \"-iptc:keywords+="+tag.replace('\"', ' ').trim()+"\" \""+o.getCanonicalPath()+"\" -overwrite_original";
            Process p = Runtime.getRuntime().exec(run);
            int i = p.waitFor();
            if (i != 0) {
                System.err.println("Failed running: "+i+"\n"+run);
            }
        }

    }
}

/**
 *
 * @author Richard Nichols
 */
public class Photo {
    private String filename;
    private String caption;
    private final Set<String> collections = new HashSet<String>();
    private final Set<String> tags = new HashSet<String>();

    public Photo(String filename, String caption) {
        this.filename = filename;
        this.caption = caption;
    }

    public String getCaption() {
        return caption;
    }

    public void setFilename(String filename) {
        this.filename = filename;
    }

    public String getFilename() {
        return filename;
    }

    public Set<String> getCollections() {
        return collections;
    }

    public Set<String> getTags() {
        return tags;
    }

}

After an hour or so while this buzzed away on 10000 odd pics I then ended up with a nicely arranged image collection with all the meta-data attached!


About the Author

Richard Nichols is an Australian software engineer with a passion for making things.

Follow him on twitter or subscribe by RSS or email.

You might also enjoy reading -


Discuss / Comment

There are 7 comments.

Add a comment

  • {{e.error}}

Thanks for your comment!/

Required.
Valid email address required.
Required.
Posting message, please wait...