/* * This is public domain software - that is, you can do whatever you want * with it, and include it software that is licensed under the GNU or the * BSD license, or whatever other licence you choose, including proprietary * closed source licenses. I do ask that you leave this header in tact. * * If you make modifications to this code that you think would benefit the * wider community, please send me a copy and I'll post it on my site. * * If you make use of this code, I'd appreciate hearing about it. * metadata_extractor [at] drewnoakes [dot] com * Latest version of this software kept at * http://drewnoakes.com/ * * Created by dnoakes on 12-Nov-2002 19:00:03 using IntelliJ IDEA. */ package com.drew.metadata.iptc; import com.drew.imaging.jpeg.JpegProcessingException; import com.drew.imaging.jpeg.JpegSegmentReader; import com.drew.metadata.Directory; import com.drew.metadata.Metadata; import com.drew.metadata.MetadataException; import com.drew.metadata.MetadataReader; import java.io.File; import java.io.InputStream; import java.util.Date; /** * Decodes IPTC binary data, populating a <code>Metadata</code> object with tag values in an <code>IptcDirectory</code>. * @author Drew Noakes http://drewnoakes.com */ public class IptcReader implements MetadataReader { /* public static final int DIRECTORY_IPTC = 2; public static final int ENVELOPE_RECORD = 1; public static final int APPLICATION_RECORD_2 = 2; public static final int APPLICATION_RECORD_3 = 3; public static final int APPLICATION_RECORD_4 = 4; public static final int APPLICATION_RECORD_5 = 5; public static final int APPLICATION_RECORD_6 = 6; public static final int PRE_DATA_RECORD = 7; public static final int DATA_RECORD = 8; public static final int POST_DATA_RECORD = 9; */ /** * The Iptc data segment. */ private final byte[] _data; /** * Creates a new IptcReader for the specified Jpeg jpegFile. * @deprecated Not all files will be Jpegs! Use a constructor that provides the IPTC segment in isolation. */ public IptcReader(File jpegFile) throws JpegProcessingException { // TODO consider removing this constructor and requiring callers to pass a byte[] or other means to read the IPTC segment in isolation... not all files will be Jpegs! this(new JpegSegmentReader(jpegFile).readSegment(JpegSegmentReader.SEGMENT_APPD)); } /** * Creates an IptcReader for a JPEG stream. * @param jpegInputStream JPEG stream. Stream will be closed. * @deprecated Not all files will be Jpegs! Use a constructor that provides the IPTC segment in isolation. */ public IptcReader(InputStream jpegInputStream) throws JpegProcessingException { // TODO consider removing this constructor and requiring callers to pass a byte[] or other means to read the IPTC segment in isolation... not all files will be Jpegs! this(new JpegSegmentReader(jpegInputStream).readSegment(JpegSegmentReader.SEGMENT_APPD)); } /** * Creates an IptcReader for the given IPTC data segment. */ public IptcReader(byte[] data) { _data = data; } /** * Performs the Exif data extraction, returning a new instance of <code>Metadata</code>. */ public Metadata extract() { return extract(new Metadata()); } /** * Performs the Exif data extraction, adding found values to the specified * instance of <code>Metadata</code>. */ public Metadata extract(Metadata metadata) { if (_data == null) { return metadata; } Directory directory = metadata.getDirectory(IptcDirectory.class); // find start of data int offset = 0; try { while (offset < _data.length - 1 && get32Bits(offset) != 0x1c02) { offset++; } } catch (MetadataException e) { directory.addError("Couldn't find start of Iptc data (invalid segment)"); return metadata; } // for each tag while (offset < _data.length) { // identifies start of a tag if (_data[offset] != 0x1c) { break; } // we need at least five bytes left to read a tag if ((offset + 5) >= _data.length) { break; } offset++; int directoryType; int tagType; int tagByteCount; try { directoryType = _data[offset++]; tagType = _data[offset++]; tagByteCount = get32Bits(offset); } catch (MetadataException e) { directory.addError("Iptc data segment ended mid-way through tag descriptor"); return metadata; } offset += 2; if ((offset + tagByteCount) > _data.length) { directory.addError("data for tag extends beyond end of iptc segment"); break; } processTag(directory, directoryType, tagType, offset, tagByteCount); offset += tagByteCount; } return metadata; } /** * Returns an int calculated from two bytes of data at the specified offset (MSB, LSB). * @param offset position within the data buffer to read first byte * @return the 32 bit int value, between 0x0000 and 0xFFFF */ private int get32Bits(int offset) throws MetadataException { if (offset >= _data.length) { throw new MetadataException("Attempt to read bytes from outside Iptc data buffer"); } return ((_data[offset] & 255) << 8) | (_data[offset + 1] & 255); } /** * This method serves as marsheller of objects for dataset. It converts from IPTC * octets to relevant java object. */ private void processTag(Directory directory, int directoryType, int tagType, int offset, int tagByteCount) { int tagIdentifier = tagType | (directoryType << 8); switch (tagIdentifier) { case IptcDirectory.TAG_RECORD_VERSION: // short short shortValue = (short)((_data[offset] << 8) | _data[offset + 1]); directory.setInt(tagIdentifier, shortValue); return; case IptcDirectory.TAG_URGENCY: // byte directory.setInt(tagIdentifier, _data[offset]); return; case IptcDirectory.TAG_RELEASE_DATE: case IptcDirectory.TAG_DATE_CREATED: // Date object if (tagByteCount >= 8) { String dateStr = new String(_data, offset, tagByteCount); try { int year = Integer.parseInt(dateStr.substring(0, 4)); int month = Integer.parseInt(dateStr.substring(4, 6)) - 1; int day = Integer.parseInt(dateStr.substring(6, 8)); Date date = (new java.util.GregorianCalendar(year, month, day)).getTime(); directory.setDate(tagIdentifier, date); return; } catch (NumberFormatException e) { // fall through and we'll store whatever was there as a String } } case IptcDirectory.TAG_RELEASE_TIME: case IptcDirectory.TAG_TIME_CREATED: // time... default: // fall through } // If we haven't returned yet, treat it as a string String str; if (tagByteCount < 1) { str = ""; } else { str = new String(_data, offset, tagByteCount); } if (directory.containsTag(tagIdentifier)) { // this fancy string[] business avoids using an ArrayList for performance reasons String[] oldStrings; String[] newStrings; try { oldStrings = directory.getStringArray(tagIdentifier); } catch (MetadataException e) { oldStrings = null; } if (oldStrings == null) { newStrings = new String[1]; } else { newStrings = new String[oldStrings.length + 1]; for (int i = 0; i < oldStrings.length; i++) { newStrings[i] = oldStrings[i]; } } newStrings[newStrings.length - 1] = str; directory.setStringArray(tagIdentifier, newStrings); } else { directory.setString(tagIdentifier, str); } } }