Sample image I used, lots of EXIF info on this shot of a 1971 SL70 that I am rebuilding.
I started by looking at JAI-imageio, which is the current home of the old Sun JAI project (to be fair, that and more), but I couldn't readily figure out how to use it to write EXIF data. The capability might be there, but I couldn't find any substantive documentation or examples (apart from JavaDocs, which are useful yes, but I needed more hand holding to start out). When I ran into this basic getting started stumbling block I broadened my search.
package com.totsp.imageio;
// no warranty expressed, implied, blah-blah - back up any images before you try this
// imports omitted for brevity - see SVN below
public class SanselanDemo {
/**
* Read metadata from image file and display it.
* @param file
*/
public void readMetaData(File file) {
IImageMetadata metadata = null;
try {
metadata = Sanselan.getMetadata(file);
} catch (ImageReadException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
if (metadata instanceof JpegImageMetadata) {
JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata;
System.out.println("\nFile: " + file.getPath());
printTagValue(jpegMetadata,
TiffConstants.TIFF_TAG_XRESOLUTION);
printTagValue(jpegMetadata,
TiffConstants.TIFF_TAG_DATE_TIME);
printTagValue(jpegMetadata,
TiffConstants.EXIF_TAG_DATE_TIME_ORIGINAL);
printTagValue(jpegMetadata,
TiffConstants.EXIF_TAG_CREATE_DATE);
printTagValue(jpegMetadata,
TiffConstants.EXIF_TAG_ISO);
printTagValue(jpegMetadata,
TiffConstants.EXIF_TAG_SHUTTER_SPEED_VALUE);
printTagValue(jpegMetadata,
TiffConstants.EXIF_TAG_APERTURE_VALUE);
printTagValue(jpegMetadata,
TiffConstants.EXIF_TAG_BRIGHTNESS_VALUE);
// simple interface to GPS data
TiffImageMetadata exifMetadata = jpegMetadata.getExif();
if (exifMetadata != null) {
try {
TiffImageMetadata.GPSInfo gpsInfo = exifMetadata.getGPS();
if (null != gpsInfo) {
double longitude = gpsInfo.getLongitudeAsDegreesEast();
double latitude = gpsInfo.getLatitudeAsDegreesNorth();
System.out.println(" " +
"GPS Description: " + gpsInfo);
System.out.println(" " +
"GPS Longitude (Degrees East): " +
longitude);
System.out.println(" " +
"GPS Latitude (Degrees North): " +
latitude);
}
} catch (ImageReadException e) {
e.printStackTrace();
}
}
System.out.println("EXIF items -");
ArrayList items = jpegMetadata.getItems();
for (int i = 0; i < items.size(); i++) {
Object item = items.get(i);
System.out.println(" " + "item: " +
item);
}
System.out.println();
}
}
private static void printTagValue(
JpegImageMetadata jpegMetadata, TagInfo tagInfo) {
TiffField field = jpegMetadata.findEXIFValue(tagInfo);
if (field == null) {
System.out.println(tagInfo.name + ": " +
"Not Found.");
} else {
System.out.println(tagInfo.name + ": " +
field.getValueDescription());
}
}
/**
* Example of adding an EXIF item to metadata, in this case using ImageHistory field.
* (I have no idea if this is an appropriate use of ImageHistory, or not, just picked
* a field to update that looked like it wasn't commonly mucked with.)
* @param file
*/
public void addImageHistoryTag(File file) {
File dst = null;
IImageMetadata metadata = null;
JpegImageMetadata jpegMetadata = null;
TiffImageMetadata exif = null;
OutputStream os = null;
TiffOutputSet outputSet = new TiffOutputSet();
// establish metadata
try {
metadata = Sanselan.getMetadata(file);
} catch (ImageReadException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// establish jpegMedatadata
if (metadata != null) {
jpegMetadata = (JpegImageMetadata) metadata;
}
// establish exif
if (jpegMetadata != null) {
exif = jpegMetadata.getExif();
}
// establish outputSet
if (exif != null) {
try {
outputSet = exif.getOutputSet();
} catch (ImageWriteException e) {
e.printStackTrace();
}
}
if (outputSet != null) {
// check if field already EXISTS - if so remove
TiffOutputField imageHistoryPre = outputSet
.findField(TiffConstants.EXIF_TAG_IMAGE_HISTORY);
if (imageHistoryPre != null) {
outputSet.removeField(TiffConstants.EXIF_TAG_IMAGE_HISTORY);
}
// add field
try {
String fieldData = "ImageHistory-" + System.currentTimeMillis();
TiffOutputField imageHistory = new TiffOutputField(
ExifTagConstants.EXIF_TAG_IMAGE_HISTORY,
TiffFieldTypeConstants.FIELD_TYPE_ASCII,
fieldData.length(),
fieldData.getBytes());
TiffOutputDirectory exifDirectory = outputSet.getOrCreateExifDirectory();
exifDirectory.add(imageHistory);
} catch (ImageWriteException e) {
e.printStackTrace();
}
}
// create stream using temp file for dst
try {
dst = File.createTempFile("temp-" + System.currentTimeMillis(), ".jpeg");
os = new FileOutputStream(dst);
os = new BufferedOutputStream(os);
} catch (IOException e) {
e.printStackTrace();
}
// write/update EXIF metadata to output stream
try {
new ExifRewriter().updateExifMetadataLossless(file,
os, outputSet);
} catch (ImageReadException e) {
e.printStackTrace();
} catch (ImageWriteException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
}
}
}
// copy temp file over original file
try {
FileUtils.copyFile(dst, file);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
File bikeFile = new File("data/bike.jpg");
SanselanDemo demo = new SanselanDemo();
System.out.println("BEFORE update");
demo.readMetaData(bikeFile);
demo.addImageHistoryTag(bikeFile);
System.out.println("\nAFTER update");
demo.readMetaData(bikeFile);
}
}
BEFORE update
File: data\bike.jpg
XResolution: 180
Date Time: '2008:03:23 01:36:18'
Date Time Original: '2008:03:23 01:36:18'
Create Date: '2008:03:23 01:36:18'
ISO: 80
Shutter Speed Value: 255/32 (7.969)
Aperture Value: 4
Brightness Value: Not Found.
EXIF items -
item: Make: 'Canon'
item: Model: 'Canon PowerShot A590 IS'
item: Orientation: 1
item: XResolution: 180
item: YResolution: 180
item: Resolution Unit: 2
item: Modify Date: '2008:03:23 01:36:18'
item: YCbCr Positioning: 1
item: Exif Offset: 2384
item: Exposure Time: 1/250 (0.004)
item: FNumber: 4
item: ISO: 80
item: Exif Version: 48, 50, 50, 48
item: Date Time Original: '2008:03:23 01:36:18'
item: Create Date: '2008:03:23 01:36:18'
item: Components Configuration: 1, 2, 3, 0
item: Compressed Bits Per Pixel: 3
item: Shutter Speed Value: 255/32 (7.969)
item: Aperture Value: 4
item: Exposure Compensation: 0
item: Max Aperture Value: 88/32 (2.75)
item: Metering Mode: 5
item: Flash: 24
item: Focal Length: 5800/1000 (5.8)
item: Maker Note: 25,...)
item: UserComment: ''
item: Flashpix Version: 48, 49, 48, 48
item: Color Space: 1
item: Exif Image Width: 3264
item: Exif Image Length: 2448
item: Interop Offset: 3128
item: Focal Plane XResolution: 3264000/225 (14,506.667)
item: Focal Plane YResolution: 2448000/169 (14,485.207)
item: Focal Plane Resolution Unit: 2
item: Image History: 'ImageHistory-1207245408150'
item: Sensing Method: 2
item: File Source: 3
item: Custom Rendered: 0
item: Exposure Mode: 0
item: White Balance: 0
item: Digital Zoom Ratio: 1
item: Scene Capture Type: 0
item: Interop Index: 'R98'
item: Interop Version: 48, 49, 48, 48
item: Related Image Width: 3264
item: Related Image Length: 2448
item: Compression: 6
item: XResolution: 180
item: YResolution: 180
item: Resolution Unit: 2
item: Jpg From Raw Start: 5108
item: Jpg From Raw Length: 6322
AFTER update
File: data\bike.jpg
XResolution: 180
Date Time: '2008:03:23 01:36:18'
Date Time Original: '2008:03:23 01:36:18'
Create Date: '2008:03:23 01:36:18'
ISO: 80
Shutter Speed Value: 255/32 (7.969)
Aperture Value: 4
Brightness Value: Not Found.
EXIF items -
item: Make: 'Canon'
item: Model: 'Canon PowerShot A590 IS'
item: Orientation: 1
item: XResolution: 180
item: YResolution: 180
item: Resolution Unit: 2
item: Modify Date: '2008:03:23 01:36:18'
item: YCbCr Positioning: 1
item: Exif Offset: 2384
item: Exposure Time: 1/250 (0.004)
item: FNumber: 4
item: ISO: 80
item: Exif Version: 48, 50, 50, 48
item: Date Time Original: '2008:03:23 01:36:18'
item: Create Date: '2008:03:23 01:36:18'
item: Components Configuration: 1, 2, 3, 0
item: Compressed Bits Per Pixel: 3
item: Shutter Speed Value: 255/32 (7.969)
item: Aperture Value: 4
item: Exposure Compensation: 0
item: Max Aperture Value: 88/32 (2.75)
item: Metering Mode: 5
item: Flash: 24
item: Focal Length: 5800/1000 (5.8)
item: Maker Note: 25, ...)
item: UserComment: ''
item: Flashpix Version: 48, 49, 48, 48
item: Color Space: 1
item: Exif Image Width: 3264
item: Exif Image Length: 2448
item: Interop Offset: 3128
item: Focal Plane XResolution: 3264000/225 (14,506.667)
item: Focal Plane YResolution: 2448000/169 (14,485.207)
item: Focal Plane Resolution Unit: 2
item: Image History: 'ImageHistory-1207247905886'
item: Sensing Method: 2
item: File Source: 3
item: Custom Rendered: 0
item: Exposure Mode: 0
item: White Balance: 0
item: Digital Zoom Ratio: 1
item: Scene Capture Type: 0
item: Interop Index: 'R98'
item: Interop Version: 48, 49, 48, 48
item: Related Image Width: 3264
item: Related Image Length: 2448
item: Compression: 6
item: XResolution: 180
item: YResolution: 180
item: Resolution Unit: 2
item: Jpg From Raw Start: 5108
item: Jpg From Raw Length: 6322
Comments
Is that your new
Is that your new transportation to work? I just saw my first smart car in the US. Pretty efficient, though not super cheap considering how small it is. http://www.smartusa.com/smart-fortwo-pure.aspx
And I do realize I am off
And I do realize I am off subject here.
Yeah. The Smart in the US
Yeah. The Smart in the US seem really quite $$$ to me.
import
Where is the FileUtils class coming from?
FileUtils.copyFile(dst, file);
Thanks.
Craig
import - commons io
That's from commons-io.
You can see all the imports, and all the source for the entire project, in it's entirety, in SVN:
http://www.totsp.com/svn/repo/ImageIOTesting/trunk/ImageIOTesting/src/co....
Deps - in case it helps further
http://www.totsp.com/svn/repo/ImageIOTesting/trunk/ImageIOTesting/lib/
And the Sanselan version is not using the metadata extractor dep, I was using that in another file, playing with that too. It's great if you just need to read EXIF (and other meta data), but doesn't work to write it back.
many lose ends
1. some fields are given with encoding numbers that hide the real meaning. E.g. Color space 1 which is supposedly sRGB.
2. Did you notice you have two XRESOLUTIONS?
Didn't lose anything, but appear to have gained a few yes
I think you meant loose ends, but anyway . . .
1. The output display of the fields is primitive, yes, but that wasn't really the point of the article - and that is taken directly from the Sanselan examples.
2. I did not, good catch, I will look into that when I get a chance. Looks like Xres, Yres, and res unit, are all repeated in the output (not sure if that's a print out bug, or if they were actually doubled up in the meta data - either way valid issue).
Yes I did mean "loose ends"
Yes I did mean "loose ends" and I was referring to the package, not your code. No offense.
Human-readability is a problem with this package. I'd rather see sRGB, or resolution unit as inches (instead of "2"), and so on and so forth.
Another issue with the package is lack of document :(
And yes I do like the capability of writing the exif, just as you do.