Batchfile: simple slideshow with custom image order

Last week a friend of mine wanted to have a image slideshow for a presentation TV screen for an exhibition. Besides of a large number of images that should be displayed in order he wanted to display his company logo image every second image, like:

  1. image 1
  2. company image
  3. image 2
  4. company image
  5. image 3
  6. company image

I wanted to do this without writing a program or installing Perl (his laptop is a Windows box) so I did this with a tiny Windows batch file:

@ECHO OFF
setLocal EnableDelayedExpansion

SET count=1
SET IMAGEDIR=%1
SET FILLIMAGE=%2

cd %IMAGEDIR%
FOR %%a IN (*.*) DO ( call :do "%%a" )
GOTO :EOF

:do
SET UQ1=%1
ren %UQ1% "%count%_!UQ1:"=!"
set /a count+=1
copy %FILLIMAGE% "%count%.jpg"
set /a count+=1
GOTO :eof

Given the batchfile slideshow.bat, the directory with a copy of all the source images in C:images and the company logo image in C:companyimage.jpg the call simply is:

slideshow.bat "C:images" "C:companyimage.jpg"

This will rename all the pictures found in C:images with a number in front of the original file name – with one number left out; like 1,3,5,7,etc. The company image is copied in between all these images as 2.jpg, 4.jpg, 6.jpg, etc. Now simply viewing the first image with Windows Picture Viewer and hitting the slideshow button will display the files by name which is exactly the desired display order.

Regarding the batchfile: it would be possible to execute multiple statements within the DO ( … ) but that way the counter variable did not work. I had to do a call to a method which then changes the counter. Another issue was the unquoting of the file name. The “%%a” supplies the image file name with double quotes to the %1 inside the :do function. As the final name should be counter + image-name without unquoting it would look like 1_”my image.jpg” which will do an error. The magick is the !VAR:”=! which unquotes the string in VAR.

Advertisements

FocalApp v0.2

I changed my FocalApp java application a little bit. First, there was sometimes a memory exception as the old version did a “new File()” in each loop iteration and apparently the garbage collector couldn’t catch up. I moved the file-open operation into an own class method and now the GC seems to get its job done. Second, I added some more command line parameters for specifying the camera model, the file extensions and the file name pattern – which all filter the resulting image file list used for the focal length count. Hence one can now say “Only count the images whose EXIF data’s camera model is a Canon 300D, but only the CRW files and only the files starting with CRW_50”. In addition I added a little help screen:

Build:
javac -Xlint:unchecked -classpath .;metadata-extractor-2.4.0-beta-1.jar FocalApp.java
jar cfm FocalApp.jar Manifest.txt *.class

Usage:
java -jar FocalApp.jar [Options]

Description:
For all images in the given directory and its subdirectories read the
lenses' focal length from the exif data and print a summary of how
often the particular focal lengths are used.

Currently this program works with the following image types:
* JPEG
* CRW - Canon Raw
* CR2 - Canon Raw v2
* NEF - Nikon Raw
* ARW - Sony Raw
* TIF/TIFF

Options:
-imagepath          path for the images to work on                          (required)
-camera             string with the name of the camera, the program will    (optional)
                    only count the images containing that camera name
-fileext            only count images with that file extension(s)           (optional)
-filename           only count images matching this regular expression      (optional)

Examples:
java -jar FocalApp.jar -imagedir "D:testimages"
java -jar FocalApp.jar -imagedir "D:testimages" -camera "Canon EOS 30D"
-fileext "jpg,crw" -filename "^CRW(.*)"

Feel free to get the new version here. You can find the original blog post to the FocalApp here.

Most widely used camera focus length

Recently a collegue of mine argued that he more often needs a small camera focus length (wide-angled) than a zoom focus length (tele). To proof – or confute – this I wrote a small java program which extracts the exif data from a given directory of images and counts the focal length parameters used when taking the pictures. I used the beta version of Drew Noakes’ metadata extractor. As I haven’t coded in java for some time, the code might be a little bit dirty.

import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.exif.ExifReader;
import com.drew.metadata.iptc.IptcReader;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGDecodeParam;
import com.sun.image.codec.jpeg.JPEGImageDecoder;
import com.drew.metadata.*;
import com.drew.metadata.exif.ExifDirectory;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.FilenameFilter;
import java.util.*;
import java.util.regex.*;

public class FocalApp {
    public FocalApp(String dirname) {
 // list of files
        File folder = new File(dirname);
        List filelist = new ArrayList();
        this.getFiles(folder, filelist);

 // key - value pairs: focallength - count
        SortedMap focalmap = new TreeMap();

 // for all found files
        for (Iterator it=filelist.iterator(); it.hasNext(); ) {
            String filename = it.next().toString();
 //System.out.println(" --- working on file: "+filename);

            File imageFile = new File(filename);
            try {
                Metadata metadata = ImageMetadataReader.readMetadata(imageFile);
                Directory exifDirectory = metadata.getDirectory(ExifDirectory.class);

                Double cameraFocus = 0.0;
                try {
                    String cameraFocusString = exifDirectory.getString(ExifDirectory.TAG_FOCAL_LENGTH);
                    if (cameraFocusString != null) {
 // value is present
                        cameraFocus = Double.parseDouble(cameraFocusString);
                    }
 //System.out.println("Focus lenght = " + cameraFocus);
                } catch(NumberFormatException nFE) {
 //System.out.println("Not an Integer");
                }

                Integer currentimagecount = 0;
                if(focalmap.containsKey(cameraFocus)) {
 //System.out.println("key already tgere");
                    currentimagecount = focalmap.get(cameraFocus);
                }
                currentimagecount++;
 //System.out.println("putting in map: "+currentvalue);
                focalmap.put(cameraFocus, currentimagecount);
            } catch (ImageProcessingException e) {
                System.err.println("skipping file "+filename+"due to error:"+e);
            }
        }

        Integer totalimagecount = 0;

        TreeSet set = new TreeSet(new Comparator() {
            public int compare(Object obj, Object obj1) {
                int vcomp = ((Comparable) ((Map.Entry) obj1).getValue()).compareTo(((Map.Entry)
                    obj).getValue());
                if (vcomp != 0) return vcomp;
                else return ((Comparable) ((Map.Entry) obj1).getKey()).compareTo(((Map.Entry)
                    obj).getKey());

            }
    });

        set.addAll(focalmap.entrySet());
        System.out.println("focal length in mm;number of images;");
        for (Iterator i = set.iterator(); i.hasNext();) {
            Map.Entry entry = (Map.Entry) i.next();
            Double key = (Double) entry.getKey();
            String keyname;
            if(key == 0) {
                keyname = "?";
            } else {
                keyname = key.toString();
            }
            Integer numimages = (Integer) entry.getValue();
            System.out.println(keyname + ";" + numimages+";");
            totalimagecount+=numimages;
        }

 //System.out.println("total: "+totalimagecount+" images.");
    }

    private void getFiles(File folder, List list) {
        folder.setReadOnly();

        File[] files = folder.listFiles(new ImageFileFilter());
        for(int j = 0; j < files.length; j++) {
            list.add(files[j]);
        }

        File[] subfolders = folder.listFiles();
        for(int j = 0; j  0 && args[0].length() > 0) {
            String imagepath = args[0];
            if(new File(imagepath).exists()) {
                new FocalApp(imagepath);
            } else {
                System.err.println("given image directory not found!");
                System.exit(2);
            }
        } else {
            System.err.println("please give a directory containing the images!");
            System.exit(1);
        }
    }
}

class ImageFileFilter implements FilenameFilter
{
    public boolean accept( File f, String s )
    {
        Pattern pattern = Pattern.compile("([^\s]+(?=\.(jpg|crw|cr2|nef|arw|tiff|tif))\.\2)", Pattern.CASE_INSENSITIVE);
        return pattern.matcher(s).matches();
    }
}

I used JDK 1.6. To build, you’ll need the “metadata-extractor-2.4.0-beta-1.jar” from the metadata extractor page. Then use:

javac -Xlint:unchecked -classpath .;metadata-extractor-2.4.0-beta-1.jar FocalApp.java
jar cfm FocalApp.jar Manifest.txt *.class

to build it. The Manifest.txt is just for entry method:

Manifest-Version: 1.0
Created-By: 1.6.0 (Sun Microsystems Inc.)
Class-Path: metadata-extractor-2.4.0-beta-1.jar
Main-Class: FocalApp

The run the jar, i.e. using:

java -jar FocalApp.jar "/path/to/your/images"

A tricky construct I had to google for after all the java-free months was how to sort a TreeMap by its values and not by the keys. In PERL this is simple (see Perlfaq4 for example):

my @keys = sort { $hash{$a} <=> $hash{$b} } keys %hash;

You get an array containig the hash’s keys, but sorted in the order of the hash’s values.
Now you can just iterate over the keys, displaying the values which could be something like that:

foreach my $Key (@keys) {
print "Key $Key has value $hash{$Key}";
}

In java I couldn’t find such an easy solution. First, I use a TreeMap to store the key-value pairs. This can already sort by keys using a SortedMap. But to turn the sorting around I needed put the entire content of that SortedMap into a TreeSet collection (code line 78), which uses a custom Comperator instance. I got this snippet from this forum page.

You can download the java code in a zip from here.