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.
Agreed on sanselan issues
No offense taken, sorry if things came across that way. I agree with you about the sanselan docs and readability stuff. It's not perfect by any sense, but I was able to use it and get some stuff done, which I failed to be able to do with the new JAI stuff.
I think sanselan is moving to an Apache project, so hopefully it will get more traction, more rigor. Having a sane Java library to do this stuff would be a huge plus. (And maybe JAI and Image-IO can do it, but back to the docs point, I couldn't tell how with those - I even posted a few questions on the boards there and got mostly "can't do it" back, so I moved on a found sanselan.)
not right
your usage of sanselan isn't correct.
You're writing the item's value to the document as a blunt string of ascii characters.
Reading it back with this application will parse the string and show it to be the value of the item, however when you try to read the exif data with any other application, the tag will not be displayed, or you will be warned that it is 'broken'
Sanselan the right way
I started from the demos and docs that Sanselan has - so my example is based on that.
If you have other suggestions, I would be happy to be made aware of them, and would be happy to correct the article. And, back when I wrote this, I am 90% sure I did test with several external EXIF reading capable applications and it worked fine.
I will go back and check that again to verify, and again, actual tangible suggestions are welcome.
Reading EXIF after Sanselan
I tried a few apps, EXIFViewer on the Mac can read the updated file (though it doesn't see any of the custom fields), but other things like PhotoInfo can't read it (PhotInfo can read the before, but not the after, what I am doing to the file is causing problems for it, but not for ExifViewer, ExifViewer can read the standard data both before and after, but can't read the added data).
To clarify, programmatically, this works, the program can read back the field and tell it's there, and that met the needs I had at the time. But yes, something must be amiss because some other apps seem to have some problems, sometimes (other apps being relative, I find varying mileage with EXIF reading apps in general).
When I get a chance I will try to learn more about the EXIF spec and what is going on here, and re-read the Sanselan docs, to figure out how to do it better.
ImageIO tools
Reading and writing of exif metadata can be accomplished by installing ImageIO tools, without using any library-specific code. Here's an article on how to read the metadata:
http://easyproblemsolutions.blogspot.com/2008/06/java-and-jpeg-metadata....
ImageIO writing not so easy
I tried ImageIO first too, as I think I stated in the original article. I even tried posting some questions on the ImageIO group when I felt like I wasn't getting anywhere on my own with the documentation.
In the end I did see how it could be used to READ back meta data, but I did not see a clear path to writing it. And, as I also noted in the article, I bet it can be done, I just didn't find a way - and Sanselan worked rather easily.