Practical scripts

This section will show examples on how scripts can enhance your album in really cool ways. The examples here makes use of the huge library of ready-made java classes that Sun has provided for free and are directly accessible. To better understand the examples and to assist in the writing of your own scripts I strongly recommend that you bookmark the basic Java API from Sun and use it to look up classes and their methods. If you are new to Java I recommend tutorials and that you then concentrate on the "java.lang", "java.util" and "java.io" packages. They are the ones that are most commonly used.

If you don't have any programming ambitions, fine. Just copy and paste the examples below to the slide.htt and index.htt files of the skin you wish to enhance. There is a tutorial that covers editing these files that you can check out first.

Adding voice annotations

Many digital cameras allow you to add voice annotations to images. The camera usually puts a wav file next to the image bearing the same base name as the image. This script will make JAlbum look for these wav files and insert a BGSOUND tag if there is an annotation. Put the script just after the body tag of a slide.htt file.


<!-- add and play voice annotations (.wav files) if they exist -->
<%
  import se.datadosen.util.IO;
  File sound = new File(imageDirectory, label+".WAV");
  if (sound.exists()) {
    // Make a copy if needed
    String soundPath;
    if (!outputDirectory.equals(imageDirectory) && engine.isCopyOriginals()) {
      IO.copyFile(sound.getAbsolutePath(), outputDirectory, true);
      soundPath = "../" + sound.getName();
    }
    else soundPath = IO.relativePath(sound, new File(outputDirectory, "slides"));
    out.println("<BGSOUND SRC=\"" + soundPath + "\">");
  }
%>
	

Converting focal length to 35mm equivalent

Some users prefer to list the focal length of the camera ($focalLength) as its 35mm equivalent value. Here are two scripts that does the conversion for you. In theory, converting is just a matter of muliplying the focal length with a factor that you can get from your camera manual (the factor varies between camera models), but in JAlbum the focalLength variable is a formatted string including "mm" at the end which can't take part in a multiplication. The follwing script takes care of that by stripping non-numeric parts of a value and converting the result to a floating point value. After that, converting is simply a matter of multiplication and rounding the result. Put these scripts into the slide.htt file of the skin you wish to use.

<%
// Strip non-numeric ending on strings and convert result to float
float numericPart(String s) {
  int i;
  for (i=0; i<s.length(); i++) {
    char c = s.charAt(i);
    if (!Character.isDigit(c) && c != '.') break;
  }
  return Float.parseFloat(s.substring(0,i));
}
%>
...And this is the code that displays the converted value (example for a Canon PowerShot G1 camera having 4.857 as conversion factor).
<ja:if exists="focalLength">
35 mm equivalent: <%= (int)(numericPart(focalLength) * 4.857 + 0.5) /* PowerShot G1 */ %>
</ja:if>

Google-like links to images

For quick navigation between images, a list of numbered links might be preferred to just having previous and next buttons. Put this script inside a slide.htt file for the Google effect. The "Bluepro" skin makes use of this effect.


<% // Produce links to all other pages, cooler than just next, previous links
  for (int i=0; i<files.length; i++) {
    Map vars = (Map)fileVariables.get(files[i]);
    if (i+1 != imageNum) out.print("<a href=\"" + vars.get("currentPage") + "\">");
    else out.print("<b>");
    out.print(" " + (i+1));
    if (i+1 != imageNum) out.print("</a>");
    else out.print("</b>");
  }
%>
	

Multilevel parent links

You might have an album with many nested folders (animals/mamals/cats...) In this case it helps a lot to have all folder names displayed like this: animals » mamals » cats (being in the "cats" folder), with links to each parent folder. Copy and paste the two scriptlets below for this effect. The first goes into index.htt and the second into slide.htt:


<!-- Multilevel index links for index page -->
<%
  void writePath(File dir, String prefix) {
    boolean topLevel = fileVariables.get(dir) == null;
    if (!topLevel)
      writePath(dir.getParentFile(), prefix + "../");
    if (!topLevel) out.print(" » ");
    if (prefix.length() > 0)
      out.print("<a href=\"" + prefix + engine.getIndexPageName()
       + engine.getPageExtension() + "\">");
    else out.print("<a href=\"" + indexPage + "\">");
    out.print((topLevel ? album.get("rootName") : dir.getName()) + "</a>");
  }
  if (album.get("rootName") == null) album.put("rootName", title);
  writePath(imageDirectory, "");
%>
	

<!-- Multilevel index links for slide pages -->
<%
  void writePath(File dir, String prefix) {
    boolean topLevel = fileVariables.get(dir) == null;
    if (!topLevel)
      writePath(dir.getParentFile(), prefix + "../");
    if (!topLevel) out.print(" » ");
    if (prefix.length() > 0)
      out.print("<a href=\"../" + prefix + engine.getIndexPageName()
       + engine.getPageExtension() + "\">");
    else out.print("<a href=\"../" + indexPage + "\">");
    out.print((topLevel ? album.get("rootName") : dir.getName()) + "</a>");
  }
  if (album.get("rootName") == null) album.put("rootName", title);
  writePath(imageDirectory, "");
%>
	

Reading captions/comments from separate text files

Some people wonder if it is possible to have JAlbum insert the contents of a text file having the same base name as an image but with ".txt" extension, for example "hiking.jpg" will get text from "hiking.txt". This is simple to do with the following scriptlet:


<!-- Extract text from textfiles carrying the same base name as this image -->
<ja:include page="<%= new File(imageDirectory, label+".txt") %>" />
	

Reading captions/comments from single text file

Some people wonder if it is possible to have one single text file in each directory where all image captions/comments are stored next to the filename itself and then have JAlbum pick from this file instead of reading EXIF comments. The format should be like this:
filename1=caption one
filename2=caption two
etc

It's easy to do with some scripting. Here is how you do it: Put the scriptlet below inside the slide.htt skin template file or wherever you wish to have the caption text inserted. Put a "captions.txt" file in the image directory. Fill the file with captions next to the filename separating them with equals signs. Generate an album using the modified skin, that's it.


<!-- Extract the comment from a caption.txt file where the filename is the key -->
<%
  import se.datadosen.util.*;
  File cFile = new File(imageDirectory, "captions.txt");
  if (cFile.exists()) {
    Map captions = IO.readMapFile(cFile);
    String text = (String)captions.get(fileName);
    if (text != null) out.print(text);
  }
%>
	

Thumbnail to indicate sub album

Usually JAlbum will pick a folder icon to indicate the existence of a sub album (an album in a sub directory), but it might be better to have a thumbnail image from that directory to represent that sub album instead (randomly picked or choosen). The following script does just that. It goes in between the ja:coliterator elements of an index.htt file. The script will pick a random1 image from the sub directory unless there is a "meta.properties" file in that directory with a line indicating what file to pick instead. The format of the file is currently like this:

folderIcon=<filename of image to use as folder icon>

If there are only movie files in the specific directory/album, the ordinary folder icon will be used instead.

Excited? Here is the code to do this (requires at least JAlbum 2.8):


<%
  // Try to replace folder icons with prepresentative image from subdirectory
  import se.datadosen.util.*;

  if (files[imageNum-1].isDirectory()) {
    File subDir = files[imageNum-1];
    File iconFile = null;

    // If meta.properties exists and points to an icon file, use it
    File propsFile = new File(subDir, "meta.properties");
    if (propsFile.exists()) {
      Properties props = IO.readPropertyFile(propsFile);
      iconFile = new File(subDir, props.get("folderIcon"));
    }
    else {
      // Try to use the first image as folder icon
      File[] list = subDir.listFiles();
      for (int i=0; i<list.length; i++) {
        if (FileFilters.isFileSupported(list[i])) {
          iconFile = list[i];
          break;
        }
      }
    }
    if (iconFile != null) {
      // Process this image so we can extract width and height
      setAccessibility(true);  // Turn on access to private methods
      engine.registerVariables(iconFile, outputDirectory);  // Private = might change
      Map vars = fileVariables.get(iconFile);
      // Redefine variables
      iconPath = engine.encode(subDir.getName() + "/thumbs/" +
       engine.jpegName(iconFile.getName()));
      thumbWidth = vars.get("thumbWidth");
      thumbHeight = vars.get("thumbHeight");
    }
  }
%>

There is one more thing to this, usually an index.htt file contains something similar to this: <img src="$iconPath".... That has to be changed to the following:

<IMG SRC="<%=iconPath%>" WIDTH="<%=thumbWidth%>" HEIGHT="<%=thumbHeight%>" BORDER=0><BR>
The reason for this is that dollar-variables ($iconPath etc) are expanded before script execution occurs and this script redefines existing variables so we have to grab the variables using scriptlet syntax instead.

1 With random I mean that the script picks the first image file that the file system delivers when I ask for a list of files. It can be any file.

Reset file date to camera date

Sometimes modifications to a file will alter the file date. If the image still has EXIF data into it, the following script can be applied to reset the file date to the date the image was taken. Put it in slide.htt and generate an album


<!-- Reset file dates to EXIF dates -->
<ja:if exists="originalDate">
<%
  import java.text.*;
    Date parseExifDate(String exifDate) {
        String[] patterns = {
         "yyyy:MM:dd HH:mm:ss",
         "yyyy:MM:dd HH:mm",
         "yyyy-MM-dd HH:mm:ss",
         "yyyy-MM-dd HH:mm",
         "yyyyMMdd HHmmss",
         "yyyyMMdd HHmm"
        };
        // Try these patterns. Add new to make this method even smarter
        for (int i=0; i<patterns.length; i++) {
            try {
                DateFormat parser = new SimpleDateFormat(patterns[i]);
                return parser.parse(exifDate);
            }
            catch (ParseException ex) {}
        }
        return null;  // We cannot parse
    }
%>

<%
  Date d = parseExifDate(originalDate);
  if (d != null) {
    files[imageNum-1].setLastModified(d.getTime());
    out.println("File date modified for " + files[imageNum-1]);
  }
%>
</ja:if>
Note. In JAlbum 2.8.5 and up, the parseExifDate function is built into JAlbum, then the above can be written as:

<ja:if exists="originalDate">
<%
  Date d = se.datadosen.imaging.exif.ImageInfoFormatter.parseExifDate(originalDate);
  if (d != null) {
    files[imageNum-1].setLastModified(d.getTime());
    out.println("File date modified for " + files[imageNum-1]);
  }
%>
</ja:if>