Friday, September 18, 2009

Extract image Exif information using Java and ExifTool

Exif is: Exchangeable Image File Format. It is a collection of meta information stored in image files taken by cameras. Sometimes we would like to read and present this information. The amount of Exif information stored today by cameras is quite impressive. It even may include the location where the picture was taken. Among all the Exif information stored in the picture, there is a group of parameters that really interests photographers. Parameters like the focal length in which the picture was taken, the Exposure mode, F-Number, Did the Flash fired when the picture was taken? and so on.

Extracting Exif information is a hard task, since there is very big amount of Camera manufactures and image types and there is no unified standard. Luckily for us, there is a great tool written Perl named: ExifTool (written by Harvey). This tool is available as Windows executable file (can be downloaded from the site). If you are using linux Debian (I develop in Windows, but my server runs under Linux) there is a ready package named: libimage-exiftool-perl

You can install it in Debian like this:

apt-get install libimage-exiftool-perl

and use the command like: exiftool in order to run it.

We will write a Java code that will use ExifTool in order to extract important Exif information from images.

The code is constructed from 2 classes:

  • ExifToolExtractor – This class extracts Exif information from an image using ExifTool and produces an ExifInfo class filled with Exif important information.
  • ExifInfo – This class stores image Exif information.

ExifToolExtractor class is very simple. it uses Process class in order to execute the ExifTool. Then it reads the ExifTool output and parses it. The output is a long list if Exif information, ordered in a key-value manner. For example, this is a partial output of ExifTool:

Make                            : NIKON CORPORATION
Camera Model Name               : NIKON D300
Orientation                     : Horizontal (normal)
X Resolution                    : 300
Y Resolution                    : 300
Resolution Unit                 : inches
Software                        : Ver.1.10
Modify Date                     : 2009:04:18 10:15:35
Artist                          :
Y Cb Cr Positioning             : Co-sited
Copyright                       :
Exposure Time                   : 1/1000
F Number                        : 7.1
Exposure Program                : Aperture-priority AE
ISO                             : 200
Exif Version                    : 0221
Date/Time Original              : 2009:04:18 10:15:35
Create Date                     : 2009:04:18 10:15:35
Components Configuration        : Y, Cb, Cr, -
Compressed Bits Per Pixel       : 2
Exposure Compensation           : -1/3
Max Aperture Value              : 4.8
Metering Mode                   : Multi-segment
Light Source                    : Unknown
Flash                           : No Flash
Focal Length                    : 65.0 mm
Maker Note Version              : 2.10
Quality                         : Normal
White Balance                   : Auto
Focus Mode                      : AF-S
Flash Setting                   : Normal
Flash Type                      :
White Balance Fine Tune         : 0 0
WB RB Levels                    : 1.4765625 1.3359375 1 1
Program Shift                   : 0
Exposure Difference             : 0
Compression                     : JPEG (old-style)
Preview Image Start             : 9128

Note, that this is a partial list. The complete list is much longer and changed from camera to camera. The class ExifToolExtractor doesn’t really care what data is outputted. it simply stores all the data to a HashMap. The HashMap doesn’t actually belongs to the class ExifToolExtractor but to the class ExifInfo which is responsible for storing all Exif information. This is how the class ExifToolExtractor looks like:

package com.bashan.blog.exif;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class ExifToolExtractor {
  private String exifToolApp;
  public ExifToolExtractor(String exifToolApp) {
    this.exifToolApp = exifToolApp;
  }
  public ExifInfo getExifInfo(String image) throws IOException, InterruptedException {
    ProcessBuilder processBuilder = new ProcessBuilder(exifToolApp, image);
    Process process = processBuilder.start();
    BufferedReader stdInput = new BufferedReader(new
        InputStreamReader(process.getInputStream()));
    BufferedReader stdError = new BufferedReader(new
        InputStreamReader(process.getErrorStream()));
    String line;
    ExifInfo exifInfo = new ExifInfo();
    while ((line = stdInput.readLine()) != null) {
      exifInfo.parseLine(line);
      System.out.println(line);
    }
    while ((line = stdError.readLine()) != null) {
    }
    process.waitFor();
    return exifInfo;
  }
}

As said before, The class ExifInfo stores a map of all Exif information. But, actually, from all the big amount of information, there are several parameters that are a real interest for Photographers. The class ExifInfo has ready methods for getting these specific information. Take a look at the method parseLine of this class. This is the method that is responsible for taking a line of information from ExifTool and convert it to key-value pair. Here is how the class ExifInfo looks like:

package com.bashan.blog.exif;
import java.util.HashMap;
import java.util.Map;
public class ExifInfo {
  private Map<String, String> exifMap = new HashMap<String, String>();
  public static final String COMPANY = "Make";
  public static final String MODEL = "Camera Model Name";
  public static final String EXPOSURE_TIME = "Exposure Time";
  public static final String F_NUMBER = "F Number";
  public static final String ISO = "ISO";
  public static final String EXPOSURE_COMPENSATION = "Exposure Compensation";
  public static final String FOCAL_LENGTH = "Focal Length";
  public static final String EXPOSURE_PROGRAM = "Exposure Program";
  public static final String DATE_TAKEN = "Create Date";
  public static final String METERING_MODE = "Metering Mode";
  public static final String FLASH = "Flash";
  public static final String FLASH_EXPOSURE_COMPENSATION = "Flash Exposure Compensation";
  public static final String LENS = "Lens ID";
  public void parseLine(String line) {
    int pos = line.indexOf(":");
    if (pos != -1) {
      exifMap.put(line.substring(0, pos).trim(), line.substring(pos + 1).trim());
    }
  }
  public String getCompany() {
    return exifMap.get(COMPANY);
  }
  public String getModel() {
    return exifMap.get(MODEL);
  }
  public String getExposureTime() {
    return exifMap.get(EXPOSURE_TIME);
  }
  public String getFNumber() {
    return exifMap.get(F_NUMBER);
  }
  public String getISO() {
    return exifMap.get(ISO);
  }
  public String getExposureCompensation() {
    return exifMap.get(EXPOSURE_COMPENSATION);
  }
  public String getFocalLength() {
    return exifMap.get(FOCAL_LENGTH);
  }
  public String getExposureProgram() {
    return exifMap.get(EXPOSURE_PROGRAM);
  }
  public String getDateTaken() {
    return exifMap.get(DATE_TAKEN);
  }
  public String getMeteringMode() {
    return exifMap.get(METERING_MODE);
  }
  public String getFlash() {
    return exifMap.get(FLASH);
  }
  public String getFlashExposureCompensation() {
    return exifMap.get(FLASH_EXPOSURE_COMPENSATION);
  }
  public String getLens()
  {
    return exifMap.get(LENS);
  }
  public String toString() {
    return "Company: " + getCompany() + "\n" +
        "Model: " + getModel() + "\n" +
        "Exposure Time: " + getExposureTime() + "\n" +
        "Date Taken: " + getDateTaken() + "\n" +
        "F-Number: " + getFNumber() + "\n" +
        "ISO: " + getISO() + "\n" +
        "Exposure Compensation: " + getExposureCompensation() + "\n" +
        "Metering Mode: " + getMeteringMode() + "\n" +
        "Focal Length: " + getFocalLength() + "\n" +
        "Exposure Program: " + getExposureProgram() + "\n" +
        "Flash: " + getExposureProgram() + "\n" +
        "Flash Exposure Compensation: " + getExposureProgram() + "\n" +
        "Lens: " + getLens();
  }
}

Notice, that ExifTool if quite a comprehensive Exif extraction tool and also supplies information of the Lens used to shoot the picture, which is a nice piece of information for photographers.

This is a small program showing how these 2 classes are working together:

  public static void main(String[] args) throws Exception {
    ExifToolExtractor exifToolExtractor = new ExifToolExtractor("C:\\exiftool(-k).exe");
    ExifInfo exifInfo = exifToolExtractor.getExifInfo("C:\\DSC_0001.JPG");
    System.out.println(exifInfo);
  }

An here is an example of the information extracted from a picture:

Company: NIKON CORPORATION
Model: NIKON D300
Exposure Time: 1/1000
Date Taken: 2009:04:18 10:15:35.68
F-Number: 7.1
ISO: 200
Exposure Compensation: -1/3
Metering Mode: Multi-segment
Focal Length: 65.0 mm (35 mm equivalent: 97.0 mm)
Exposure Program: Aperture-priority AE
Flash: Aperture-priority AE
Flash Exposure Compensation: Aperture-priority AE
Lens: Tamron AF 28-300mm f/3.5-6.3 XR Di VC LD Aspherical [IF] MACRO

2 comments:

  1. HI,
    very nice example it is possible to have the source code.

    I think there is an error somewhere in the code. i have tried it but , the values of the given tags, all comes to null, i think in the



    package com.bashan.blog.exif;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    public class ExifToolExtractor {
    private String exifToolApp;
    public ExifToolExtractor(String exifToolApp) {
    this.exifToolApp = exifToolApp;
    }
    public ExifInfo getExifInfo(String image) throws IOException, InterruptedException {
    ProcessBuilder processBuilder = new ProcessBuilder(exifToolApp, image);
    Process process = processBuilder.start();
    BufferedReader stdInput = new BufferedReader(new
    InputStreamReader(process.getInputStream()));
    BufferedReader stdError = new BufferedReader(new
    InputStreamReader(process.getErrorStream()));
    String line;
    ExifInfo exifInfo = new ExifInfo();
    while ((line = stdInput.readLine()) != null) {
    exifInfo.parseLine(line);
    System.out.println(line);
    }
    while ((line = stdError.readLine()) != null) {
    }
    process.waitFor();
    return exifInfo;
    }
    }


    the exit info is null,

    may be i am wrong. can you send me the source code at fotafranck@gmail.com
    Thks and best regard

    ReplyDelete
  2. Do you still need the code? I can dig it for you...

    ReplyDelete