Skip to content

Commit

Permalink
Version 2.1
Browse files Browse the repository at this point in the history
Extract methods no longer throw exceptions, with error information stored in Metadata instances, using hasErrors() and getErrors().
Metadata and dependant classes now serializable for network transmission, and persistance in files & databases.
Support for extracting metadata from InputStreams, such as network connections.
Replaced code that depended upon JDK 1.4.


git-svn-id: https://metadata-extractor.googlecode.com/svn/trunk@5 7bd79e3c-ab28-1fa5-d007-79fa9fde61af
  • Loading branch information
drewnoakes committed Jan 12, 2003
1 parent 86405a5 commit 179d934
Show file tree
Hide file tree
Showing 19 changed files with 525 additions and 229 deletions.
10 changes: 10 additions & 0 deletions ReleaseNotes.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
v2.1
------------

- Extract methods no longer throw exceptions, with error information stored
in Metadata instances, using hasErrors() and getErrors()
- Metadata and dependant classes now serializable for network transmission,
and persistance in files & databases
- Support for extracting metadata from InputStreams, such as network connections
- Replaced code that depended upon JDK 1.4

v2.0
------------

Expand Down
50 changes: 30 additions & 20 deletions src/com/drew/imaging/jpeg/JpegMetadataReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,41 @@

import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Iterator;

/**
*
*/
public class JpegMetadataReader
{
public static Metadata readMetadata(File file) throws FileNotFoundException
public static Metadata readMetadata(InputStream in) throws JpegProcessingException
{
JpegSegmentReader segmentReader = new JpegSegmentReader(in);
return extractJpegSegmentReaderMetadata(segmentReader);
}

public static Metadata readMetadata(File file) throws FileNotFoundException, JpegProcessingException
{
JpegSegmentReader segmentReader = new JpegSegmentReader(file);
return extractJpegSegmentReaderMetadata(segmentReader);
}

private static Metadata extractJpegSegmentReaderMetadata(JpegSegmentReader segmentReader)
{
Metadata metadata = new Metadata();
try {
byte[] exifSegment = segmentReader.readSegment(JpegSegmentReader.SEGMENT_APP1);
new ExifReader(exifSegment).extract(metadata);
} catch (Exception e) {
} catch (JpegProcessingException e) {
// in the interests of catching as much data as possible, continue
// TODO lodge error message within exif directory
// TODO lodge error message within exif directory?
}
try {
byte[] iptcSegment = segmentReader.readSegment(JpegSegmentReader.SEGMENT_APPD);
new IptcReader(iptcSegment).extract(metadata);
} catch (Exception e) {
// in the interests of catching as much data as possible, continue
// TODO lodge error message within iptc directory
} catch (JpegProcessingException e) {
// TODO lodge error message within iptc directory?
}
return metadata;
}
Expand All @@ -45,22 +56,15 @@ public static Metadata readMetadata(JPEGDecodeParam decodeParam)
/* We should only really be seeing Exif in _data[0]... the 2D array exists
* because markers can theoretically appear multiple times in the file.
*/
try {
byte[][] exifSegment = decodeParam.getMarkerData(JPEGDecodeParam.APP1_MARKER);
if (exifSegment != null) {
new ExifReader(exifSegment[0]).extract();
}
} catch (MetadataException e) {
// continue
byte[][] exifSegment = decodeParam.getMarkerData(JPEGDecodeParam.APP1_MARKER);
if (exifSegment != null && exifSegment[0].length>0) {
new ExifReader(exifSegment[0]).extract();
}

// similarly, use only the first IPTC segment
try {
byte[][] iptcSegment = decodeParam.getMarkerData(JPEGDecodeParam.APPD_MARKER);
if (iptcSegment != null) {
new IptcReader(iptcSegment[0]).extract();
}
} catch (MetadataException e) {
// continue
byte[][] iptcSegment = decodeParam.getMarkerData(JPEGDecodeParam.APPD_MARKER);
if (iptcSegment != null && iptcSegment[0].length>0) {
new IptcReader(iptcSegment[0]).extract();
}
return metadata;
}
Expand Down Expand Up @@ -94,6 +98,12 @@ public static void main(String[] args)
System.err.println(tag.getDirectoryName() + " " + tag.getTagName() + " <error>");
}
}
if (directory.hasErrors()) {
Iterator errors = directory.getErrors();
while (errors.hasNext()) {
System.out.println("ERROR: " + errors.next());
}
}
}
}
}
165 changes: 109 additions & 56 deletions src/com/drew/imaging/jpeg/JpegSegmentReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
package com.drew.imaging.jpeg;

import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;

/**
* Performs read functions of Jpeg files, returning specific file segments.
Expand All @@ -41,6 +44,13 @@ public class JpegSegmentReader
*/
private byte[] _data;

/**
* Jpeg data as an InputStream.
*/
private InputStream _stream;

private HashMap _segmentDataMap;

/**
* Private, because this segment crashes my algorithm, and searching for
* it doesn't work (yet).
Expand Down Expand Up @@ -98,91 +108,84 @@ public class JpegSegmentReader
* Creates a JpegSegmentReader for a specific file.
* @param file the Jpeg file to read segments from
*/
public JpegSegmentReader(File file) throws FileNotFoundException
public JpegSegmentReader(File file) throws FileNotFoundException, JpegProcessingException
{
_file = file;
_data = null;
readSegments();
}

/**
* Creates a JpegSegmentReader for a byte array.
* @param fileContents the byte array containing Jpeg data
*/
public JpegSegmentReader(byte[] fileContents)
{
public JpegSegmentReader(byte[] fileContents) throws JpegProcessingException {
_file = null;
_data = fileContents;
readSegments();
}

public JpegSegmentReader(InputStream in) throws JpegProcessingException {
_stream = in;
_file = null;
_data = null;
readSegments();
}

/**
* Determines if the file passed to the constructor of this class is a Jpeg file. Note
* that this implementation simply checks the magic number, and doesn't detect corrupt
* files.
* @return true, if a legal jpeg file, otherwise false
* @throws JpegProcessingException for any problem in reading the file
* Reads the first instance of a given Jpeg segment, returning the contents as
* a byte array.
* @param segmentMarker the byte identifier for the desired segment
* @return the byte array if found, else null
* @throws JpegProcessingException for any problems processing the Jpeg data,
* including inner IOExceptions
*/
public boolean isJpeg() throws JpegProcessingException
public byte[] readSegment(byte segmentMarker) throws JpegProcessingException
{
InputStream inputStream = getJpegInputStream();
try {
return isValidJpegHeaderBytes(inputStream);
} catch (IOException e) {
throw new JpegProcessingException("Error reading Jpeg data", e);
}
return readSegment(segmentMarker, 0);
}

/**
* Private helper method to create a BufferedInputStream of Jpeg data from whichever
* data source was specified upon construction of this instance.
* @return a a BufferedInputStream of Jpeg data
* @throws JpegProcessingException for any problems obtaining the stream
* Reads the first instance of a given Jpeg segment, returning the contents as
* a byte array.
* @param segmentMarker the byte identifier for the desired segment
* @param occurrence the occurrence of the specified segment within the jpeg file
* @return the byte array if found, else null
* @throws JpegProcessingException for any problems processing the Jpeg data,
* including inner IOExceptions
*/
private BufferedInputStream getJpegInputStream() throws JpegProcessingException
public byte[] readSegment(byte segmentMarker, int occurrence) throws JpegProcessingException
{
InputStream inputStream;
if (_data == null) {
try {
inputStream = new FileInputStream(_file);
} catch (FileNotFoundException e) {
throw new JpegProcessingException("Jpeg file does not exist", e);
Byte key = new Byte(segmentMarker);
if (_segmentDataMap.containsKey(key)) {
List segmentList = (List)_segmentDataMap.get(key);
if (segmentList.size()<=occurrence) {
return null;
}
return (byte[]) segmentList.get(occurrence);
} else {
inputStream = new ByteArrayInputStream(_data);
return null;
}
return new BufferedInputStream(inputStream);
}

/**
* Helper method that validates the Jpeg file's magic number.
* @param fileStream the InputStream to read bytes from, which must be positioned
* at its start (i.e. no bytes read yet)
* @return true if the magic number is Jpeg (0xFFD8)
* @throws IOException for any problem in reading the file
*/
private boolean isValidJpegHeaderBytes(InputStream fileStream) throws IOException
public int getSegmentCount(byte segmentMarker)
{
byte[] header = new byte[2];
fileStream.read(header, 0, 2);
return ((header[0] & 0xFF) == 0xFF && (header[1] & 0xFF) == 0xD8);
List segmentList = (List)_segmentDataMap.get(new Byte(segmentMarker));
if (segmentList==null) {
return 0;
}
return segmentList.size();
}

/**
* Reads the first instance of a given Jpeg segment, returning the contents as
* a byte array.
* TODO allow user to specify the subsequent instances of a segment marker, or return a 2D array (overload of method)
* @param segmentMarker the byte identifier for the desired segment
* @return the byte array if found, else null
* @throws JpegProcessingException for any problems processing the Jpeg data,
* including inner IOExceptions
*/
public byte[] readSegment(int segmentMarker) throws JpegProcessingException
private void readSegments() throws JpegProcessingException
{
_segmentDataMap = new HashMap();
BufferedInputStream inStream = getJpegInputStream();
int offset = 0;
try {
int offset = 0;
// first two bytes should be jpeg magic number
if (!isValidJpegHeaderBytes(inStream)) {
throw new IOException("not a jpeg file");
throw new JpegProcessingException("not a jpeg file");
}
offset += 2;
do {
Expand All @@ -208,16 +211,24 @@ public byte[] readSegment(int segmentMarker) throws JpegProcessingException
byte[] segmentBytes = new byte[segmentLength];
inStream.read(segmentBytes, 0, segmentLength);
offset += segmentLength;
if ((thisSegmentMarker & 0xFF) == (segmentMarker & 0xFF)) {
return segmentBytes;
} else if ((thisSegmentMarker & 0xFF) == (SEGMENT_SOS & 0xFF)) {
if ((thisSegmentMarker & 0xFF) == (SEGMENT_SOS & 0xFF)) {
// The 'Start-Of-Scan' segment's length doesn't include the image data, instead would
// have to search for the two bytes: 0xFF 0xD9 (EOI).
// It comes last so simply return at this point
return null;
return;
} else if ((thisSegmentMarker & 0xFF) == (MARKER_EOI & 0xFF)) {
// the 'End-Of-Image' segment -- this should never be found in this fashion
return null;
return;
} else {
List segmentList;
Byte key = new Byte(thisSegmentMarker);
if (_segmentDataMap.containsKey(key)) {
segmentList = (List)_segmentDataMap.get(key);
} else{
segmentList = new ArrayList();
_segmentDataMap.put(key, segmentList);
}
segmentList.add(segmentBytes);
}
// didn't find the one we're looking for, loop through to the next segment
} while (true);
Expand All @@ -235,4 +246,46 @@ public byte[] readSegment(int segmentMarker) throws JpegProcessingException
}
}
}

/**
* Private helper method to create a BufferedInputStream of Jpeg data from whichever
* data source was specified upon construction of this instance.
* @return a a BufferedInputStream of Jpeg data
* @throws JpegProcessingException for any problems obtaining the stream
*/
private BufferedInputStream getJpegInputStream() throws JpegProcessingException
{
if (_stream!=null) {
if (_stream instanceof BufferedInputStream) {
return (BufferedInputStream) _stream;
} else {
return new BufferedInputStream(_stream);
}
}
InputStream inputStream;
if (_data == null) {
try {
inputStream = new FileInputStream(_file);
} catch (FileNotFoundException e) {
throw new JpegProcessingException("Jpeg file does not exist", e);
}
} else {
inputStream = new ByteArrayInputStream(_data);
}
return new BufferedInputStream(inputStream);
}

/**
* Helper method that validates the Jpeg file's magic number.
* @param fileStream the InputStream to read bytes from, which must be positioned
* at its start (i.e. no bytes read yet)
* @return true if the magic number is Jpeg (0xFFD8)
* @throws IOException for any problem in reading the file
*/
private boolean isValidJpegHeaderBytes(InputStream fileStream) throws IOException
{
byte[] header = new byte[2];
fileStream.read(header, 0, 2);
return ((header[0] & 0xFF) == 0xFF && (header[1] & 0xFF) == 0xD8);
}
}
13 changes: 13 additions & 0 deletions src/com/drew/imaging/jpeg/test/JpegMetadataReaderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import junit.framework.TestCase;

import java.io.File;
import java.io.InputStream;
import java.io.BufferedInputStream;
import java.io.FileInputStream;

/**
*
Expand All @@ -29,4 +32,14 @@ public void testExtractMetadata() throws Exception
Directory directory = metadata.getDirectory(ExifDirectory.class);
assertEquals("80", directory.getString(ExifDirectory.TAG_ISO_EQUIVALENT));
}

public void testExtractMetadataUsingInputStream() throws Exception
{
File withExif = new File("src/com/drew/metadata/exif/test/withExif.jpg");
InputStream in = new BufferedInputStream(new FileInputStream((withExif)));
Metadata metadata = JpegMetadataReader.readMetadata(in);
assertTrue(metadata.containsDirectory(ExifDirectory.class));
Directory directory = metadata.getDirectory(ExifDirectory.class);
assertEquals("80", directory.getString(ExifDirectory.TAG_ISO_EQUIVALENT));
}
}
Loading

0 comments on commit 179d934

Please sign in to comment.