- Timestamp:
- 2015-03-10T01:17:39+01:00 (10 years ago)
- Location:
- trunk
- Files:
-
- 61 added
- 31 deleted
- 49 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/CONTRIBUTION
r7937 r8132 35 35 36 36 The jpeg metadata extraction code is from Drew Noakes 37 (http ://code.google.com/p/metadata-extractor/) and licensed37 (https://github.com/drewnoakes/metadata-extractor) and licensed 38 38 with Apache license version 2.0. 39 39 -
trunk/src/com/drew/imaging/ImageProcessingException.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.imaging; … … 26 26 /** 27 27 * An exception class thrown upon an unexpected condition that was fatal for the processing of an image. 28 * 29 * @author Drew Noakes http ://drewnoakes.com28 * 29 * @author Drew Noakes https://drewnoakes.com 30 30 */ 31 31 public class ImageProcessingException extends CompoundException -
trunk/src/com/drew/imaging/PhotographicConversions.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.imaging; … … 24 24 * Contains helper methods that perform photographic conversions. 25 25 * 26 * @author Drew Noakes http ://drewnoakes.com26 * @author Drew Noakes https://drewnoakes.com 27 27 */ 28 28 public final class PhotographicConversions -
trunk/src/com/drew/imaging/jpeg/JpegMetadataReader.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.imaging.jpeg; 22 22 23 import com.drew.lang.ByteArrayReader;24 import com.drew.lang.annotations.NotNull;25 import com.drew.metadata.Metadata;26 import com.drew.metadata.exif.ExifReader;27 import com.drew.metadata.iptc.IptcReader;28 import com.drew.metadata.jpeg.JpegCommentReader;29 import com.drew.metadata.jpeg.JpegDirectory;30 import com.drew.metadata.jpeg.JpegReader;31 32 23 import java.io.File; 24 import java.io.FileInputStream; 33 25 import java.io.IOException; 34 26 import java.io.InputStream; 27 import java.util.Arrays; 28 import java.util.HashSet; 29 import java.util.Set; 30 31 import com.drew.lang.StreamReader; 32 import com.drew.lang.annotations.NotNull; 33 import com.drew.lang.annotations.Nullable; 34 import com.drew.metadata.Metadata; 35 //import com.drew.metadata.adobe.AdobeJpegReader; 36 import com.drew.metadata.exif.ExifReader; 37 //import com.drew.metadata.icc.IccReader; 38 import com.drew.metadata.iptc.IptcReader; 39 //import com.drew.metadata.jfif.JfifReader; 40 import com.drew.metadata.jpeg.JpegCommentReader; 41 import com.drew.metadata.jpeg.JpegReader; 42 //import com.drew.metadata.photoshop.PhotoshopReader; 43 //import com.drew.metadata.xmp.XmpReader; 35 44 36 45 /** 37 * Obtains all available metadata from J pegformatted files.46 * Obtains all available metadata from JPEG formatted files. 38 47 * 39 * @author Drew Noakes http ://drewnoakes.com48 * @author Drew Noakes https://drewnoakes.com 40 49 */ 41 50 public class JpegMetadataReader 42 51 { 43 // TODO investigate supporting javax.imageio 44 // public static Metadata readMetadata(IIOMetadata metadata) throws JpegProcessingException {} 45 // public static Metadata readMetadata(ImageInputStream in) throws JpegProcessingException{} 46 // public static Metadata readMetadata(IIOImage image) throws JpegProcessingException{} 47 // public static Metadata readMetadata(ImageReader reader) throws JpegProcessingException{} 52 public static final Iterable<JpegSegmentMetadataReader> ALL_READERS = Arrays.asList( 53 new JpegReader(), 54 new JpegCommentReader(), 55 //new JfifReader(), 56 new ExifReader(), 57 //new XmpReader(), 58 //new IccReader(), 59 //new PhotoshopReader(), 60 new IptcReader()//, 61 //new AdobeJpegReader() 62 ); 48 63 49 64 @NotNull 50 public static Metadata readMetadata(@NotNull InputStream inputStream ) throws JpegProcessingException65 public static Metadata readMetadata(@NotNull InputStream inputStream, @Nullable Iterable<JpegSegmentMetadataReader> readers) throws JpegProcessingException, IOException 51 66 { 52 return readMetadata(inputStream, true); 67 Metadata metadata = new Metadata(); 68 process(metadata, inputStream, readers); 69 return metadata; 53 70 } 54 71 55 72 @NotNull 56 public static Metadata readMetadata(@NotNull InputStream inputStream , final boolean waitForBytes) throws JpegProcessingException73 public static Metadata readMetadata(@NotNull InputStream inputStream) throws JpegProcessingException, IOException 57 74 { 58 JpegSegmentReader segmentReader = new JpegSegmentReader(inputStream, waitForBytes); 59 return extractMetadataFromJpegSegmentReader(segmentReader.getSegmentData()); 75 return readMetadata(inputStream, null); 76 } 77 78 @NotNull 79 public static Metadata readMetadata(@NotNull File file, @Nullable Iterable<JpegSegmentMetadataReader> readers) throws JpegProcessingException, IOException 80 { 81 InputStream inputStream = null; 82 try 83 { 84 inputStream = new FileInputStream(file); 85 return readMetadata(inputStream, readers); 86 } finally { 87 if (inputStream != null) 88 inputStream.close(); 89 } 60 90 } 61 91 … … 63 93 public static Metadata readMetadata(@NotNull File file) throws JpegProcessingException, IOException 64 94 { 65 JpegSegmentReader segmentReader = new JpegSegmentReader(file); 66 return extractMetadataFromJpegSegmentReader(segmentReader.getSegmentData()); 95 return readMetadata(file, null); 67 96 } 68 97 69 @NotNull 70 public static Metadata extractMetadataFromJpegSegmentReader(@NotNull JpegSegmentData segmentReader) 98 public static void process(@NotNull Metadata metadata, @NotNull InputStream inputStream) throws JpegProcessingException, IOException 71 99 { 72 final Metadata metadata = new Metadata(); 100 process(metadata, inputStream, null); 101 } 73 102 74 // Loop through looking for all SOFn segments. When we find one, we know what type of compression 75 // was used for the JPEG, and we can process the JPEG metadata in the segment too. 76 for (byte i = 0; i < 16; i++) { 77 // There are no SOF4 or SOF12 segments, so don't bother 78 if (i == 4 || i == 12) 79 continue; 80 // Should never have more than one SOFn for a given 'n'. 81 byte[] jpegSegment = segmentReader.getSegment((byte)(JpegSegmentReader.SEGMENT_SOF0 + i)); 82 if (jpegSegment == null) 83 continue; 84 JpegDirectory directory = metadata.getOrCreateDirectory(JpegDirectory.class); 85 directory.setInt(JpegDirectory.TAG_JPEG_COMPRESSION_TYPE, i); 86 new JpegReader().extract(new ByteArrayReader(jpegSegment), metadata); 87 break; 88 } 103 public static void process(@NotNull Metadata metadata, @NotNull InputStream inputStream, @Nullable Iterable<JpegSegmentMetadataReader> readers) throws JpegProcessingException, IOException 104 { 105 if (readers == null) 106 readers = ALL_READERS; 89 107 90 // There should never be more than one COM segment. 91 byte[] comSegment = segmentReader.getSegment(JpegSegmentReader.SEGMENT_COM); 92 if (comSegment != null) 93 new JpegCommentReader().extract(new ByteArrayReader(comSegment), metadata); 94 95 // Loop through all APP1 segments, checking the leading bytes to identify the format of each. 96 for (byte[] app1Segment : segmentReader.getSegments(JpegSegmentReader.SEGMENT_APP1)) { 97 if (app1Segment.length > 3 && "EXIF".equalsIgnoreCase(new String(app1Segment, 0, 4))) 98 new ExifReader().extract(new ByteArrayReader(app1Segment), metadata); 99 100 //if (app1Segment.length > 27 && "http://ns.adobe.com/xap/1.0/".equalsIgnoreCase(new String(app1Segment, 0, 28))) 101 // new XmpReader().extract(new ByteArrayReader(app1Segment), metadata); 102 } 103 104 // Loop through all APPD segments, checking the leading bytes to identify the format of each. 105 for (byte[] appdSegment : segmentReader.getSegments(JpegSegmentReader.SEGMENT_APPD)) { 106 if (appdSegment.length > 12 && "Photoshop 3.0".compareTo(new String(appdSegment, 0, 13))==0) { 107 //new PhotoshopReader().extract(new ByteArrayReader(appdSegment), metadata); 108 } else { 109 // TODO might be able to check for a leading 0x1c02 for IPTC data... 110 new IptcReader().extract(new ByteArrayReader(appdSegment), metadata); 108 Set<JpegSegmentType> segmentTypes = new HashSet<JpegSegmentType>(); 109 for (JpegSegmentMetadataReader reader : readers) { 110 for (JpegSegmentType type : reader.getSegmentTypes()) { 111 segmentTypes.add(type); 111 112 } 112 113 } 113 114 114 return metadata; 115 JpegSegmentData segmentData = JpegSegmentReader.readSegments(new StreamReader(inputStream), segmentTypes); 116 117 processJpegSegmentData(metadata, readers, segmentData); 118 } 119 120 public static void processJpegSegmentData(Metadata metadata, Iterable<JpegSegmentMetadataReader> readers, JpegSegmentData segmentData) 121 { 122 // Pass the appropriate byte arrays to each reader. 123 for (JpegSegmentMetadataReader reader : readers) { 124 for (JpegSegmentType segmentType : reader.getSegmentTypes()) { 125 for (byte[] segmentBytes : segmentData.getSegments(segmentType)) { 126 if (reader.canProcess(segmentBytes, segmentType)) { 127 reader.extract(segmentBytes, metadata, segmentType); 128 } 129 } 130 } 131 } 115 132 } 116 133 … … 120 137 } 121 138 } 122 -
trunk/src/com/drew/imaging/jpeg/JpegProcessingException.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.imaging.jpeg; … … 25 25 26 26 /** 27 * An exception class thrown upon unexpected and fatal conditions while processing a J pegfile.27 * An exception class thrown upon unexpected and fatal conditions while processing a JPEG file. 28 28 * 29 * @author Drew Noakes http ://drewnoakes.com29 * @author Drew Noakes https://drewnoakes.com 30 30 */ 31 31 public class JpegProcessingException extends ImageProcessingException -
trunk/src/com/drew/imaging/jpeg/JpegSegmentData.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.imaging.jpeg; … … 24 24 import com.drew.lang.annotations.Nullable; 25 25 26 import java.io.*; 27 import java.util.ArrayList; 28 import java.util.HashMap; 29 import java.util.List; 26 import java.util.*; 30 27 31 28 /** 32 * Holds a collection of Jpeg data segments. This need not necessarily be all segments 33 * within the Jpeg. For example, it may be convenient to store only the non-image 34 * segments when analysing (or serializing) metadata. 35 * <p/> 36 * Segments are keyed via their segment marker (a byte). Where multiple segments use the 37 * same segment marker, they will all be stored and available. 38 * 39 * @author Drew Noakes http://drewnoakes.com 29 * Holds a collection of JPEG data segments. This need not necessarily be all segments 30 * within the JPEG. For example, it may be convenient to store only the non-image 31 * segments when analysing metadata. 32 * <p> 33 * Segments are keyed via their {@link JpegSegmentType}. Where multiple segments use the 34 * same segment type, they will all be stored and available. 35 * <p> 36 * Each segment type may contain multiple entries. Conceptually the model is: 37 * <code>Map<JpegSegmentType, Collection<byte[]>></code>. This class provides 38 * convenience methods around that structure. 39 * 40 * @author Drew Noakes https://drewnoakes.com 40 41 */ 41 public class JpegSegmentData implements Serializable42 public class JpegSegmentData 42 43 { 43 private static final long serialVersionUID = 7110175216435025451L; 44 45 /** A map of byte[], keyed by the segment marker */ 44 // TODO key this on JpegSegmentType rather than Byte, and hopefully lose much of the use of 'byte' with this class 46 45 @NotNull 47 46 private final HashMap<Byte, List<byte[]>> _segmentDataMap = new HashMap<Byte, List<byte[]>>(10); … … 49 48 /** 50 49 * Adds segment bytes to the collection. 51 * @param segmentMarker 52 * @param segmentBytes 53 */ 54 @SuppressWarnings({ "MismatchedQueryAndUpdateOfCollection" }) 55 public void addSegment(byte segmentMarker, @NotNull byte[] segmentBytes) 56 { 57 final List<byte[]> segmentList = getOrCreateSegmentList(segmentMarker); 58 segmentList.add(segmentBytes); 59 } 60 61 /** 62 * Gets the first Jpeg segment data for the specified marker. 63 * @param segmentMarker the byte identifier for the desired segment 50 * 51 * @param segmentType the type of the segment being added 52 * @param segmentBytes the byte array holding data for the segment being added 53 */ 54 @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"}) 55 public void addSegment(byte segmentType, @NotNull byte[] segmentBytes) 56 { 57 getOrCreateSegmentList(segmentType).add(segmentBytes); 58 } 59 60 /** 61 * Gets the set of JPEG segment type identifiers. 62 */ 63 public Iterable<JpegSegmentType> getSegmentTypes() 64 { 65 Set<JpegSegmentType> segmentTypes = new HashSet<JpegSegmentType>(); 66 67 for (Byte segmentTypeByte : _segmentDataMap.keySet()) 68 { 69 JpegSegmentType segmentType = JpegSegmentType.fromByte(segmentTypeByte); 70 if (segmentType == null) { 71 throw new IllegalStateException("Should not have a segmentTypeByte that is not in the enum: " + Integer.toHexString(segmentTypeByte)); 72 } 73 segmentTypes.add(segmentType); 74 } 75 76 return segmentTypes; 77 } 78 79 /** 80 * Gets the first JPEG segment data for the specified type. 81 * 82 * @param segmentType the JpegSegmentType for the desired segment 64 83 * @return a byte[] containing segment data or null if no data exists for that segment 65 84 */ 66 85 @Nullable 67 public byte[] getSegment(byte segmentMarker) 68 { 69 return getSegment(segmentMarker, 0); 70 } 71 72 /** 73 * Gets segment data for a specific occurrence and marker. Use this method when more than one occurrence 74 * of segment data for a given marker exists. 75 * @param segmentMarker identifies the required segment 76 * @param occurrence the zero-based index of the occurrence 77 * @return the segment data as a byte[], or null if no segment exists for the marker & occurrence 78 */ 79 @Nullable 80 public byte[] getSegment(byte segmentMarker, int occurrence) 81 { 82 final List<byte[]> segmentList = getSegmentList(segmentMarker); 83 84 if (segmentList==null || segmentList.size()<=occurrence) 85 return null; 86 else 87 return segmentList.get(occurrence); 88 } 89 90 /** 91 * Returns all instances of a given Jpeg segment. If no instances exist, an empty sequence is returned. 92 * 93 * @param segmentMarker a number which identifies the type of Jpeg segment being queried 94 * @return zero or more byte arrays, each holding the data of a Jpeg segment 95 */ 96 @NotNull 97 public Iterable<byte[]> getSegments(byte segmentMarker) 98 { 99 final List<byte[]> segmentList = getSegmentList(segmentMarker); 100 return segmentList==null ? new ArrayList<byte[]>() : segmentList; 101 } 102 103 @Nullable 104 public List<byte[]> getSegmentList(byte segmentMarker) 105 { 106 return _segmentDataMap.get(Byte.valueOf(segmentMarker)); 107 } 108 109 @NotNull 110 private List<byte[]> getOrCreateSegmentList(byte segmentMarker) 86 public byte[] getSegment(byte segmentType) 87 { 88 return getSegment(segmentType, 0); 89 } 90 91 /** 92 * Gets the first JPEG segment data for the specified type. 93 * 94 * @param segmentType the JpegSegmentType for the desired segment 95 * @return a byte[] containing segment data or null if no data exists for that segment 96 */ 97 @Nullable 98 public byte[] getSegment(@NotNull JpegSegmentType segmentType) 99 { 100 return getSegment(segmentType.byteValue, 0); 101 } 102 103 /** 104 * Gets segment data for a specific occurrence and type. Use this method when more than one occurrence 105 * of segment data for a given type exists. 106 * 107 * @param segmentType identifies the required segment 108 * @param occurrence the zero-based index of the occurrence 109 * @return the segment data as a byte[], or null if no segment exists for the type & occurrence 110 */ 111 @Nullable 112 public byte[] getSegment(@NotNull JpegSegmentType segmentType, int occurrence) 113 { 114 return getSegment(segmentType.byteValue, occurrence); 115 } 116 117 /** 118 * Gets segment data for a specific occurrence and type. Use this method when more than one occurrence 119 * of segment data for a given type exists. 120 * 121 * @param segmentType identifies the required segment 122 * @param occurrence the zero-based index of the occurrence 123 * @return the segment data as a byte[], or null if no segment exists for the type & occurrence 124 */ 125 @Nullable 126 public byte[] getSegment(byte segmentType, int occurrence) 127 { 128 final List<byte[]> segmentList = getSegmentList(segmentType); 129 130 return segmentList != null && segmentList.size() > occurrence 131 ? segmentList.get(occurrence) 132 : null; 133 } 134 135 /** 136 * Returns all instances of a given JPEG segment. If no instances exist, an empty sequence is returned. 137 * 138 * @param segmentType a number which identifies the type of JPEG segment being queried 139 * @return zero or more byte arrays, each holding the data of a JPEG segment 140 */ 141 @NotNull 142 public Iterable<byte[]> getSegments(@NotNull JpegSegmentType segmentType) 143 { 144 return getSegments(segmentType.byteValue); 145 } 146 147 /** 148 * Returns all instances of a given JPEG segment. If no instances exist, an empty sequence is returned. 149 * 150 * @param segmentType a number which identifies the type of JPEG segment being queried 151 * @return zero or more byte arrays, each holding the data of a JPEG segment 152 */ 153 @NotNull 154 public Iterable<byte[]> getSegments(byte segmentType) 155 { 156 final List<byte[]> segmentList = getSegmentList(segmentType); 157 return segmentList == null ? new ArrayList<byte[]>() : segmentList; 158 } 159 160 @Nullable 161 private List<byte[]> getSegmentList(byte segmentType) 162 { 163 return _segmentDataMap.get(segmentType); 164 } 165 166 @NotNull 167 private List<byte[]> getOrCreateSegmentList(byte segmentType) 111 168 { 112 169 List<byte[]> segmentList; 113 if (_segmentDataMap.containsKey(segment Marker)) {114 segmentList = _segmentDataMap.get(segment Marker);170 if (_segmentDataMap.containsKey(segmentType)) { 171 segmentList = _segmentDataMap.get(segmentType); 115 172 } else { 116 173 segmentList = new ArrayList<byte[]>(); 117 _segmentDataMap.put(segment Marker, segmentList);174 _segmentDataMap.put(segmentType, segmentList); 118 175 } 119 176 return segmentList; … … 121 178 122 179 /** 123 * Returns the count of segment data byte arrays stored for a given segment marker. 124 * @param segmentMarker identifies the required segment 180 * Returns the count of segment data byte arrays stored for a given segment type. 181 * 182 * @param segmentType identifies the required segment 125 183 * @return the segment count (zero if no segments exist). 126 184 */ 127 public int getSegmentCount(byte segmentMarker) 128 { 129 final List<byte[]> segmentList = getSegmentList(segmentMarker); 185 public int getSegmentCount(@NotNull JpegSegmentType segmentType) 186 { 187 return getSegmentCount(segmentType.byteValue); 188 } 189 190 /** 191 * Returns the count of segment data byte arrays stored for a given segment type. 192 * 193 * @param segmentType identifies the required segment 194 * @return the segment count (zero if no segments exist). 195 */ 196 public int getSegmentCount(byte segmentType) 197 { 198 final List<byte[]> segmentList = getSegmentList(segmentType); 130 199 return segmentList == null ? 0 : segmentList.size(); 131 200 } … … 133 202 /** 134 203 * Removes a specified instance of a segment's data from the collection. Use this method when more than one 135 * occurrence of segment data for a given marker exists. 136 * @param segmentMarker identifies the required segment 137 * @param occurrence the zero-based index of the segment occurrence to remove. 138 */ 139 @SuppressWarnings({ "MismatchedQueryAndUpdateOfCollection" }) 140 public void removeSegmentOccurrence(byte segmentMarker, int occurrence) 141 { 142 final List<byte[]> segmentList = _segmentDataMap.get(Byte.valueOf(segmentMarker)); 204 * occurrence of segment data exists for a given type exists. 205 * 206 * @param segmentType identifies the required segment 207 * @param occurrence the zero-based index of the segment occurrence to remove. 208 */ 209 @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"}) 210 public void removeSegmentOccurrence(@NotNull JpegSegmentType segmentType, int occurrence) 211 { 212 removeSegmentOccurrence(segmentType.byteValue, occurrence); 213 } 214 215 /** 216 * Removes a specified instance of a segment's data from the collection. Use this method when more than one 217 * occurrence of segment data exists for a given type exists. 218 * 219 * @param segmentType identifies the required segment 220 * @param occurrence the zero-based index of the segment occurrence to remove. 221 */ 222 @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"}) 223 public void removeSegmentOccurrence(byte segmentType, int occurrence) 224 { 225 final List<byte[]> segmentList = _segmentDataMap.get(segmentType); 143 226 segmentList.remove(occurrence); 144 227 } 145 228 146 229 /** 147 * Removes all segments from the collection having the specified marker. 148 * @param segmentMarker identifies the required segment 149 */ 150 public void removeSegment(byte segmentMarker) 151 { 152 _segmentDataMap.remove(Byte.valueOf(segmentMarker)); 153 } 154 155 /** 156 * Determines whether data is present for a given segment marker. 157 * @param segmentMarker identifies the required segment 230 * Removes all segments from the collection having the specified type. 231 * 232 * @param segmentType identifies the required segment 233 */ 234 public void removeSegment(@NotNull JpegSegmentType segmentType) 235 { 236 removeSegment(segmentType.byteValue); 237 } 238 239 /** 240 * Removes all segments from the collection having the specified type. 241 * 242 * @param segmentType identifies the required segment 243 */ 244 public void removeSegment(byte segmentType) 245 { 246 _segmentDataMap.remove(segmentType); 247 } 248 249 /** 250 * Determines whether data is present for a given segment type. 251 * 252 * @param segmentType identifies the required segment 158 253 * @return true if data exists, otherwise false 159 254 */ 160 public boolean containsSegment(byte segmentMarker) 161 { 162 return _segmentDataMap.containsKey(Byte.valueOf(segmentMarker)); 163 } 164 165 /** 166 * Serialises the contents of a JpegSegmentData to a file. 167 * @param file to file to write from 168 * @param segmentData the data to write 169 * @throws IOException if problems occur while writing 170 */ 171 public static void toFile(@NotNull File file, @NotNull JpegSegmentData segmentData) throws IOException 172 { 173 FileOutputStream fileOutputStream = null; 174 try 175 { 176 fileOutputStream = new FileOutputStream(file); 177 new ObjectOutputStream(fileOutputStream).writeObject(segmentData); 178 } 179 finally 180 { 181 if (fileOutputStream!=null) 182 fileOutputStream.close(); 183 } 184 } 185 186 /** 187 * Deserialises the contents of a JpegSegmentData from a file. 188 * @param file the file to read from 189 * @return the JpegSegmentData as read 190 * @throws IOException if problems occur while reading 191 * @throws ClassNotFoundException if problems occur while deserialising 192 */ 193 @NotNull 194 public static JpegSegmentData fromFile(@NotNull File file) throws IOException, ClassNotFoundException 195 { 196 ObjectInputStream inputStream = null; 197 try 198 { 199 inputStream = new ObjectInputStream(new FileInputStream(file)); 200 return (JpegSegmentData)inputStream.readObject(); 201 } 202 finally 203 { 204 if (inputStream!=null) 205 inputStream.close(); 206 } 255 public boolean containsSegment(@NotNull JpegSegmentType segmentType) 256 { 257 return containsSegment(segmentType.byteValue); 258 } 259 260 /** 261 * Determines whether data is present for a given segment type. 262 * 263 * @param segmentType identifies the required segment 264 * @return true if data exists, otherwise false 265 */ 266 public boolean containsSegment(byte segmentType) 267 { 268 return _segmentDataMap.containsKey(segmentType); 207 269 } 208 270 } -
trunk/src/com/drew/imaging/jpeg/JpegSegmentReader.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.imaging.jpeg; 22 22 23 import com.drew.lang.SequentialReader; 24 import com.drew.lang.StreamReader; 23 25 import com.drew.lang.annotations.NotNull; 24 26 import com.drew.lang.annotations.Nullable; 25 27 26 import java.io.*; 28 import java.io.File; 29 import java.io.FileInputStream; 30 import java.io.IOException; 31 import java.util.HashSet; 32 import java.util.Set; 27 33 28 34 /** 29 * Performs read functions of Jpeg files, returning specific file segments. 30 * @author Drew Noakes http://drewnoakes.com 35 * Performs read functions of JPEG files, returning specific file segments. 36 * <p> 37 * JPEG files are composed of a sequence of consecutive JPEG 'segments'. Each is identified by one of a set of byte 38 * values, modelled in the {@link JpegSegmentType} enumeration. Use <code>readSegments</code> to read out the some 39 * or all segments into a {@link JpegSegmentData} object, from which the raw JPEG segment byte arrays may be accessed. 40 * 41 * @author Drew Noakes https://drewnoakes.com 31 42 */ 32 43 public class JpegSegmentReader 33 44 { 34 // TODO add a findAvailableSegments() method35 // TODO add more segment identifiers36 // TODO add a getSegmentDescription() method, returning for example 'App1 application data segment, commonly containing Exif data'37 38 @NotNull39 private final JpegSegmentData _segmentData;40 41 45 /** 42 46 * Private, because this segment crashes my algorithm, and searching for it doesn't work (yet). 43 47 */ 44 private static final byte SEGMENT_SOS = (byte) 0xDA;48 private static final byte SEGMENT_SOS = (byte) 0xDA; 45 49 46 50 /** 47 51 * Private, because one wouldn't search for it. 48 52 */ 49 private static final byte MARKER_EOI = (byte)0xD9; 50 51 /** APP0 Jpeg segment identifier -- JFIF data (also JFXX apparently). */ 52 public static final byte SEGMENT_APP0 = (byte)0xE0; 53 /** APP1 Jpeg segment identifier -- where Exif data is kept. XMP data is also kept in here, though usually in a second instance. */ 54 public static final byte SEGMENT_APP1 = (byte)0xE1; 55 /** APP2 Jpeg segment identifier. */ 56 public static final byte SEGMENT_APP2 = (byte)0xE2; 57 /** APP3 Jpeg segment identifier. */ 58 public static final byte SEGMENT_APP3 = (byte)0xE3; 59 /** APP4 Jpeg segment identifier. */ 60 public static final byte SEGMENT_APP4 = (byte)0xE4; 61 /** APP5 Jpeg segment identifier. */ 62 public static final byte SEGMENT_APP5 = (byte)0xE5; 63 /** APP6 Jpeg segment identifier. */ 64 public static final byte SEGMENT_APP6 = (byte)0xE6; 65 /** APP7 Jpeg segment identifier. */ 66 public static final byte SEGMENT_APP7 = (byte)0xE7; 67 /** APP8 Jpeg segment identifier. */ 68 public static final byte SEGMENT_APP8 = (byte)0xE8; 69 /** APP9 Jpeg segment identifier. */ 70 public static final byte SEGMENT_APP9 = (byte)0xE9; 71 /** APPA (App10) Jpeg segment identifier -- can hold Unicode comments. */ 72 public static final byte SEGMENT_APPA = (byte)0xEA; 73 /** APPB (App11) Jpeg segment identifier. */ 74 public static final byte SEGMENT_APPB = (byte)0xEB; 75 /** APPC (App12) Jpeg segment identifier. */ 76 public static final byte SEGMENT_APPC = (byte)0xEC; 77 /** APPD (App13) Jpeg segment identifier -- IPTC data in here. */ 78 public static final byte SEGMENT_APPD = (byte)0xED; 79 /** APPE (App14) Jpeg segment identifier. */ 80 public static final byte SEGMENT_APPE = (byte)0xEE; 81 /** APPF (App15) Jpeg segment identifier. */ 82 public static final byte SEGMENT_APPF = (byte)0xEF; 83 /** Start Of Image segment identifier. */ 84 public static final byte SEGMENT_SOI = (byte)0xD8; 85 /** Define Quantization Table segment identifier. */ 86 public static final byte SEGMENT_DQT = (byte)0xDB; 87 /** Define Huffman Table segment identifier. */ 88 public static final byte SEGMENT_DHT = (byte)0xC4; 89 /** Start-of-Frame Zero segment identifier. */ 90 public static final byte SEGMENT_SOF0 = (byte)0xC0; 91 /** Jpeg comment segment identifier. */ 92 public static final byte SEGMENT_COM = (byte)0xFE; 53 private static final byte MARKER_EOI = (byte) 0xD9; 93 54 94 55 /** 95 * Creates a JpegSegmentReader for a specific file. 96 * @param file the Jpeg file to read segments from 56 * Processes the provided JPEG data, and extracts the specified JPEG segments into a {@link JpegSegmentData} object. 57 * <p> 58 * Will not return SOS (start of scan) or EOI (end of image) segments. 59 * 60 * @param file a {@link File} from which the JPEG data will be read. 61 * @param segmentTypes the set of JPEG segments types that are to be returned. If this argument is <code>null</code> 62 * then all found segment types are returned. 97 63 */ 98 @ SuppressWarnings({ "ConstantConditions" })99 public JpegSegmentReader(@NotNull File file) throws JpegProcessingException, IOException64 @NotNull 65 public static JpegSegmentData readSegments(@NotNull File file, @Nullable Iterable<JpegSegmentType> segmentTypes) throws JpegProcessingException, IOException 100 66 { 101 if (file==null) 102 throw new NullPointerException(); 103 104 InputStream inputStream = null; 67 FileInputStream stream = null; 105 68 try { 106 inputStream = new FileInputStream(file);107 _segmentData = readSegments(new BufferedInputStream(inputStream), false);69 stream = new FileInputStream(file); 70 return readSegments(new StreamReader(stream), segmentTypes); 108 71 } finally { 109 if (inputStream != null) 110 inputStream.close(); 72 if (stream != null) { 73 stream.close(); 74 } 111 75 } 112 76 } 113 77 114 78 /** 115 * Creates a JpegSegmentReader for a byte array. 116 * @param fileContents the byte array containing Jpeg data 79 * Processes the provided JPEG data, and extracts the specified JPEG segments into a {@link JpegSegmentData} object. 80 * <p> 81 * Will not return SOS (start of scan) or EOI (end of image) segments. 82 * 83 * @param reader a {@link SequentialReader} from which the JPEG data will be read. It must be positioned at the 84 * beginning of the JPEG data stream. 85 * @param segmentTypes the set of JPEG segments types that are to be returned. If this argument is <code>null</code> 86 * then all found segment types are returned. 117 87 */ 118 @ SuppressWarnings({ "ConstantConditions" })119 public JpegSegmentReader(@NotNull byte[] fileContents) throws JpegProcessingException88 @NotNull 89 public static JpegSegmentData readSegments(@NotNull final SequentialReader reader, @Nullable Iterable<JpegSegmentType> segmentTypes) throws JpegProcessingException, IOException 120 90 { 121 if (fileContents==null)122 throw new NullPointerException();91 // Must be big-endian 92 assert (reader.isMotorolaByteOrder()); 123 93 124 BufferedInputStream stream = new BufferedInputStream(new ByteArrayInputStream(fileContents)); 125 _segmentData = readSegments(stream, false); 94 // first two bytes should be JPEG magic number 95 final int magicNumber = reader.getUInt16(); 96 if (magicNumber != 0xFFD8) { 97 throw new JpegProcessingException("JPEG data is expected to begin with 0xFFD8 (ÿØ) not 0x" + Integer.toHexString(magicNumber)); 98 } 99 100 Set<Byte> segmentTypeBytes = null; 101 if (segmentTypes != null) { 102 segmentTypeBytes = new HashSet<Byte>(); 103 for (JpegSegmentType segmentType : segmentTypes) { 104 segmentTypeBytes.add(segmentType.byteValue); 105 } 106 } 107 108 JpegSegmentData segmentData = new JpegSegmentData(); 109 110 do { 111 // Find the segment marker. Markers are zero or more 0xFF bytes, followed 112 // by a 0xFF and then a byte not equal to 0x00 or 0xFF. 113 114 final short segmentIdentifier = reader.getUInt8(); 115 116 // We must have at least one 0xFF byte 117 if (segmentIdentifier != 0xFF) 118 throw new JpegProcessingException("Expected JPEG segment start identifier 0xFF, not 0x" + Integer.toHexString(segmentIdentifier).toUpperCase()); 119 120 // Read until we have a non-0xFF byte. This identifies the segment type. 121 byte segmentType = reader.getInt8(); 122 while (segmentType == (byte)0xFF) 123 segmentType = reader.getInt8(); 124 125 if (segmentType == 0) 126 throw new JpegProcessingException("Expected non-zero byte as part of JPEG marker identifier"); 127 128 if (segmentType == SEGMENT_SOS) { 129 // The 'Start-Of-Scan' segment's length doesn't include the image data, instead would 130 // have to search for the two bytes: 0xFF 0xD9 (EOI). 131 // It comes last so simply return at this point 132 return segmentData; 133 } 134 135 if (segmentType == MARKER_EOI) { 136 // the 'End-Of-Image' segment -- this should never be found in this fashion 137 return segmentData; 138 } 139 140 // next 2-bytes are <segment-size>: [high-byte] [low-byte] 141 int segmentLength = reader.getUInt16(); 142 143 // segment length includes size bytes, so subtract two 144 segmentLength -= 2; 145 146 if (segmentLength < 0) 147 throw new JpegProcessingException("JPEG segment size would be less than zero"); 148 149 // Check whether we are interested in this segment 150 if (segmentTypeBytes == null || segmentTypeBytes.contains(segmentType)) { 151 byte[] segmentBytes = reader.getBytes(segmentLength); 152 assert (segmentLength == segmentBytes.length); 153 segmentData.addSegment(segmentType, segmentBytes); 154 } else { 155 // Some if the JPEG is truncated, just return what data we've already gathered 156 if (!reader.trySkip(segmentLength)) { 157 return segmentData; 158 } 159 } 160 161 } while (true); 126 162 } 127 163 128 /** 129 * Creates a JpegSegmentReader for an InputStream. 130 * @param inputStream the InputStream containing Jpeg data 131 */ 132 @SuppressWarnings({ "ConstantConditions" }) 133 public JpegSegmentReader(@NotNull InputStream inputStream, boolean waitForBytes) throws JpegProcessingException 164 private JpegSegmentReader() throws Exception 134 165 { 135 if (inputStream==null) 136 throw new NullPointerException(); 137 138 BufferedInputStream bufferedInputStream = inputStream instanceof BufferedInputStream 139 ? (BufferedInputStream)inputStream 140 : new BufferedInputStream(inputStream); 141 142 _segmentData = readSegments(bufferedInputStream, waitForBytes); 143 } 144 145 /** 146 * Reads the first instance of a given Jpeg segment, returning the contents as 147 * a byte array. 148 * @param segmentMarker the byte identifier for the desired segment 149 * @return the byte array if found, else null 150 */ 151 @Nullable 152 public byte[] readSegment(byte segmentMarker) 153 { 154 return readSegment(segmentMarker, 0); 155 } 156 157 /** 158 * Reads the Nth instance of a given Jpeg segment, returning the contents as a byte array. 159 * 160 * @param segmentMarker the byte identifier for the desired segment 161 * @param occurrence the occurrence of the specified segment within the jpeg file 162 * @return the byte array if found, else null 163 */ 164 @Nullable 165 public byte[] readSegment(byte segmentMarker, int occurrence) 166 { 167 return _segmentData.getSegment(segmentMarker, occurrence); 168 } 169 170 /** 171 * Returns all instances of a given Jpeg segment. If no instances exist, an empty sequence is returned. 172 * 173 * @param segmentMarker a number which identifies the type of Jpeg segment being queried 174 * @return zero or more byte arrays, each holding the data of a Jpeg segment 175 */ 176 @NotNull 177 public Iterable<byte[]> readSegments(byte segmentMarker) 178 { 179 return _segmentData.getSegments(segmentMarker); 180 } 181 182 /** 183 * Returns the number of segments having the specified JPEG segment marker. 184 * @param segmentMarker the JPEG segment identifying marker. 185 * @return the count of matching segments. 186 */ 187 public final int getSegmentCount(byte segmentMarker) 188 { 189 return _segmentData.getSegmentCount(segmentMarker); 190 } 191 192 /** 193 * Returns the JpegSegmentData object used by this reader. 194 * @return the JpegSegmentData object. 195 */ 196 @NotNull 197 public final JpegSegmentData getSegmentData() 198 { 199 return _segmentData; 200 } 201 202 @NotNull 203 private JpegSegmentData readSegments(@NotNull final BufferedInputStream jpegInputStream, boolean waitForBytes) throws JpegProcessingException 204 { 205 JpegSegmentData segmentData = new JpegSegmentData(); 206 207 try { 208 int offset = 0; 209 // first two bytes should be jpeg magic number 210 byte[] headerBytes = new byte[2]; 211 if (jpegInputStream.read(headerBytes, 0, 2)!=2) 212 throw new JpegProcessingException("not a jpeg file"); 213 final boolean hasValidHeader = (headerBytes[0] & 0xFF) == 0xFF && (headerBytes[1] & 0xFF) == 0xD8; 214 if (!hasValidHeader) 215 throw new JpegProcessingException("not a jpeg file"); 216 217 offset += 2; 218 do { 219 // need four bytes from stream for segment header before continuing 220 if (!checkForBytesOnStream(jpegInputStream, 4, waitForBytes)) 221 throw new JpegProcessingException("stream ended before segment header could be read"); 222 223 // next byte is 0xFF 224 byte segmentIdentifier = (byte)(jpegInputStream.read() & 0xFF); 225 if ((segmentIdentifier & 0xFF) != 0xFF) { 226 throw new JpegProcessingException("expected jpeg segment start identifier 0xFF at offset " + offset + ", not 0x" + Integer.toHexString(segmentIdentifier & 0xFF)); 227 } 228 offset++; 229 // next byte is <segment-marker> 230 byte thisSegmentMarker = (byte)(jpegInputStream.read() & 0xFF); 231 offset++; 232 // next 2-bytes are <segment-size>: [high-byte] [low-byte] 233 byte[] segmentLengthBytes = new byte[2]; 234 if (jpegInputStream.read(segmentLengthBytes, 0, 2) != 2) 235 throw new JpegProcessingException("Jpeg data ended unexpectedly."); 236 offset += 2; 237 int segmentLength = ((segmentLengthBytes[0] << 8) & 0xFF00) | (segmentLengthBytes[1] & 0xFF); 238 // segment length includes size bytes, so subtract two 239 segmentLength -= 2; 240 if (!checkForBytesOnStream(jpegInputStream, segmentLength, waitForBytes)) 241 throw new JpegProcessingException("segment size would extend beyond file stream length"); 242 if (segmentLength < 0) 243 throw new JpegProcessingException("segment size would be less than zero"); 244 byte[] segmentBytes = new byte[segmentLength]; 245 if (jpegInputStream.read(segmentBytes, 0, segmentLength) != segmentLength) 246 throw new JpegProcessingException("Jpeg data ended unexpectedly."); 247 offset += segmentLength; 248 if ((thisSegmentMarker & 0xFF) == (SEGMENT_SOS & 0xFF)) { 249 // The 'Start-Of-Scan' segment's length doesn't include the image data, instead would 250 // have to search for the two bytes: 0xFF 0xD9 (EOI). 251 // It comes last so simply return at this point 252 return segmentData; 253 } else if ((thisSegmentMarker & 0xFF) == (MARKER_EOI & 0xFF)) { 254 // the 'End-Of-Image' segment -- this should never be found in this fashion 255 return segmentData; 256 } else { 257 segmentData.addSegment(thisSegmentMarker, segmentBytes); 258 } 259 } while (true); 260 } catch (IOException ioe) { 261 throw new JpegProcessingException("IOException processing Jpeg file: " + ioe.getMessage(), ioe); 262 } finally { 263 try { 264 if (jpegInputStream != null) { 265 jpegInputStream.close(); 266 } 267 } catch (IOException ioe) { 268 throw new JpegProcessingException("IOException processing Jpeg file: " + ioe.getMessage(), ioe); 269 } 270 } 271 } 272 273 private boolean checkForBytesOnStream(@NotNull BufferedInputStream stream, int bytesNeeded, boolean waitForBytes) throws IOException 274 { 275 // NOTE waiting is essential for network streams where data can be delayed, but it is not necessary for byte[] or filesystems 276 277 if (!waitForBytes) 278 return bytesNeeded <= stream.available(); 279 280 int count = 40; // * 100ms = approx 4 seconds 281 while (count > 0) { 282 if (bytesNeeded <= stream.available()) 283 return true; 284 try { 285 Thread.sleep(100); 286 } catch (InterruptedException e) { 287 // continue 288 } 289 count--; 290 } 291 return false; 166 throw new Exception("Not intended for instantiation."); 292 167 } 293 168 } -
trunk/src/com/drew/lang/BufferBoundsException.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 22 22 package com.drew.lang; 23 23 24 import com.drew.lang.annotations.NotNull;25 26 24 import java.io.IOException; 27 25 28 26 /** 29 * A checked replacement for IndexOutOfBoundsException. Used by BufferReader.30 * 31 * @author Drew Noakes http ://drewnoakes.com27 * A checked replacement for {@link IndexOutOfBoundsException}. Used by {@link RandomAccessReader}. 28 * 29 * @author Drew Noakes https://drewnoakes.com 32 30 */ 33 public final class BufferBoundsException extends Exception31 public final class BufferBoundsException extends IOException 34 32 { 35 33 private static final long serialVersionUID = 2911102837808946396L; 36 34 37 public BufferBoundsException( @NotNull byte[] buffer, int index, int bytesRequested)35 public BufferBoundsException(int index, int bytesRequested, long bufferLength) 38 36 { 39 super(getMessage( buffer, index, bytesRequested));37 super(getMessage(index, bytesRequested, bufferLength)); 40 38 } 41 39 … … 45 43 } 46 44 47 public BufferBoundsException(final String message, final IOException innerException) 48 { 49 super(message, innerException); 50 } 51 52 private static String getMessage(@NotNull byte[] buffer, int index, int bytesRequested) 45 private static String getMessage(int index, int bytesRequested, long bufferLength) 53 46 { 54 47 if (index < 0) 55 return String.format("Attempt to read from buffer using a negative index (% s)", index);48 return String.format("Attempt to read from buffer using a negative index (%d)", index); 56 49 57 return String.format("Attempt to read %d byte%s from beyond end of buffer (requested index: %d, max index: %d)", 58 bytesRequested, bytesRequested==1?"":"s", index, buffer.length - 1); 50 if (bytesRequested < 0) 51 return String.format("Number of requested bytes cannot be negative (%d)", bytesRequested); 52 53 if ((long)index + (long)bytesRequested - 1L > (long)Integer.MAX_VALUE) 54 return String.format("Number of requested bytes summed with starting index exceed maximum range of signed 32 bit integers (requested index: %d, requested count: %d)", index, bytesRequested); 55 56 return String.format("Attempt to read from beyond end of underlying data source (requested index: %d, requested count: %d, max index: %d)", 57 index, bytesRequested, bufferLength - 1); 59 58 } 60 59 } -
trunk/src/com/drew/lang/ByteArrayReader.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 … … 24 24 import com.drew.lang.annotations.NotNull; 25 25 26 import java.io. UnsupportedEncodingException;26 import java.io.IOException; 27 27 28 28 /** 29 29 * Provides methods to read specific values from a byte array, with a consistent, checked exception structure for 30 30 * issues. 31 * <p />31 * <p> 32 32 * By default, the reader operates with Motorola byte order (big endianness). This can be changed by calling 33 * {@see setMotorolaByteOrder(boolean)}.34 * 35 * @author Drew Noakes http ://drewnoakes.com33 * <code>setMotorolaByteOrder(boolean)</code>. 34 * 35 * @author Drew Noakes https://drewnoakes.com 36 36 * */ 37 public class ByteArrayReader implements BufferReader37 public class ByteArrayReader extends RandomAccessReader 38 38 { 39 39 @NotNull 40 40 private final byte[] _buffer; 41 private boolean _isMotorolaByteOrder = true;42 41 43 42 @SuppressWarnings({ "ConstantConditions" }) … … 47 46 if (buffer == null) 48 47 throw new NullPointerException(); 49 48 50 49 _buffer = buffer; 51 50 } … … 57 56 } 58 57 59 60 58 @Override 61 p ublic void setMotorolaByteOrder(boolean motorolaByteOrder)59 protected byte getByte(int index) throws IOException 62 60 { 63 _isMotorolaByteOrder = motorolaByteOrder;64 }65 66 @Override67 public boolean isMotorolaByteOrder()68 {69 return _isMotorolaByteOrder;70 }71 72 @Override73 public short getUInt8(int index) throws BufferBoundsException74 {75 checkBounds(index, 1);76 77 return (short) (_buffer[index] & 255);78 }79 80 @Override81 public byte getInt8(int index) throws BufferBoundsException82 {83 checkBounds(index, 1);84 85 61 return _buffer[index]; 86 62 } 87 63 88 64 @Override 89 p ublic int getUInt16(int index) throws BufferBoundsException65 protected void validateIndex(int index, int bytesRequested) throws IOException 90 66 { 91 checkBounds(index, 2); 92 93 if (_isMotorolaByteOrder) { 94 // Motorola - MSB first 95 return (_buffer[index ] << 8 & 0xFF00) | 96 (_buffer[index + 1] & 0xFF); 97 } else { 98 // Intel ordering - LSB first 99 return (_buffer[index + 1] << 8 & 0xFF00) | 100 (_buffer[index ] & 0xFF); 101 } 67 if (!isValidIndex(index, bytesRequested)) 68 throw new BufferBoundsException(index, bytesRequested, _buffer.length); 102 69 } 103 70 104 71 @Override 105 p ublic short getInt16(int index) throws BufferBoundsException72 protected boolean isValidIndex(int index, int bytesRequested) throws IOException 106 73 { 107 checkBounds(index, 2); 108 109 if (_isMotorolaByteOrder) { 110 // Motorola - MSB first 111 return (short) (((short)_buffer[index ] << 8 & (short)0xFF00) | 112 ((short)_buffer[index + 1] & (short)0xFF)); 113 } else { 114 // Intel ordering - LSB first 115 return (short) (((short)_buffer[index + 1] << 8 & (short)0xFF00) | 116 ((short)_buffer[index ] & (short)0xFF)); 117 } 74 return bytesRequested >= 0 75 && index >= 0 76 && (long)index + (long)bytesRequested - 1L < (long)_buffer.length; 118 77 } 119 78 120 79 @Override 121 public long getUInt32(int index) throws BufferBoundsException 80 @NotNull 81 public byte[] getBytes(int index, int count) throws IOException 122 82 { 123 checkBounds(index, 4); 124 125 if (_isMotorolaByteOrder) { 126 // Motorola - MSB first (big endian) 127 return (((long)_buffer[index ]) << 24 & 0xFF000000L) | 128 (((long)_buffer[index + 1]) << 16 & 0xFF0000L) | 129 (((long)_buffer[index + 2]) << 8 & 0xFF00L) | 130 (((long)_buffer[index + 3]) & 0xFFL); 131 } else { 132 // Intel ordering - LSB first (little endian) 133 return (((long)_buffer[index + 3]) << 24 & 0xFF000000L) | 134 (((long)_buffer[index + 2]) << 16 & 0xFF0000L) | 135 (((long)_buffer[index + 1]) << 8 & 0xFF00L) | 136 (((long)_buffer[index ]) & 0xFFL); 137 } 138 } 139 140 @Override 141 public int getInt32(int index) throws BufferBoundsException 142 { 143 checkBounds(index, 4); 144 145 if (_isMotorolaByteOrder) { 146 // Motorola - MSB first (big endian) 147 return (_buffer[index ] << 24 & 0xFF000000) | 148 (_buffer[index + 1] << 16 & 0xFF0000) | 149 (_buffer[index + 2] << 8 & 0xFF00) | 150 (_buffer[index + 3] & 0xFF); 151 } else { 152 // Intel ordering - LSB first (little endian) 153 return (_buffer[index + 3] << 24 & 0xFF000000) | 154 (_buffer[index + 2] << 16 & 0xFF0000) | 155 (_buffer[index + 1] << 8 & 0xFF00) | 156 (_buffer[index ] & 0xFF); 157 } 158 } 159 160 @Override 161 public long getInt64(int index) throws BufferBoundsException 162 { 163 checkBounds(index, 8); 164 165 if (_isMotorolaByteOrder) { 166 // Motorola - MSB first 167 return ((long)_buffer[index ] << 56 & 0xFF00000000000000L) | 168 ((long)_buffer[index + 1] << 48 & 0xFF000000000000L) | 169 ((long)_buffer[index + 2] << 40 & 0xFF0000000000L) | 170 ((long)_buffer[index + 3] << 32 & 0xFF00000000L) | 171 ((long)_buffer[index + 4] << 24 & 0xFF000000L) | 172 ((long)_buffer[index + 5] << 16 & 0xFF0000L) | 173 ((long)_buffer[index + 6] << 8 & 0xFF00L) | 174 ((long)_buffer[index + 7] & 0xFFL); 175 } else { 176 // Intel ordering - LSB first 177 return ((long)_buffer[index + 7] << 56 & 0xFF00000000000000L) | 178 ((long)_buffer[index + 6] << 48 & 0xFF000000000000L) | 179 ((long)_buffer[index + 5] << 40 & 0xFF0000000000L) | 180 ((long)_buffer[index + 4] << 32 & 0xFF00000000L) | 181 ((long)_buffer[index + 3] << 24 & 0xFF000000L) | 182 ((long)_buffer[index + 2] << 16 & 0xFF0000L) | 183 ((long)_buffer[index + 1] << 8 & 0xFF00L) | 184 ((long)_buffer[index ] & 0xFFL); 185 } 186 } 187 188 @Override 189 public float getS15Fixed16(int index) throws BufferBoundsException 190 { 191 checkBounds(index, 4); 192 193 if (_isMotorolaByteOrder) { 194 float res = (_buffer[index ] & 255) << 8 | 195 (_buffer[index + 1] & 255); 196 int d = (_buffer[index + 2] & 255) << 8 | 197 (_buffer[index + 3] & 255); 198 return (float)(res + d/65536.0); 199 } else { 200 // this particular branch is untested 201 float res = (_buffer[index + 3] & 255) << 8 | 202 (_buffer[index + 2] & 255); 203 int d = (_buffer[index + 1] & 255) << 8 | 204 (_buffer[index ] & 255); 205 return (float)(res + d/65536.0); 206 } 207 } 208 209 @Override 210 public float getFloat32(int index) throws BufferBoundsException 211 { 212 return Float.intBitsToFloat(getInt32(index)); 213 } 214 215 @Override 216 public double getDouble64(int index) throws BufferBoundsException 217 { 218 return Double.longBitsToDouble(getInt64(index)); 219 } 220 221 @Override 222 @NotNull 223 public byte[] getBytes(int index, int count) throws BufferBoundsException 224 { 225 checkBounds(index, count); 83 validateIndex(index, count); 226 84 227 85 byte[] bytes = new byte[count]; … … 229 87 return bytes; 230 88 } 231 232 @Override233 @NotNull234 public String getString(int index, int bytesRequested) throws BufferBoundsException235 {236 return new String(getBytes(index, bytesRequested));237 }238 239 @Override240 @NotNull241 public String getString(int index, int bytesRequested, String charset) throws BufferBoundsException242 {243 byte[] bytes = getBytes(index, bytesRequested);244 try {245 return new String(bytes, charset);246 } catch (UnsupportedEncodingException e) {247 return new String(bytes);248 }249 }250 251 @Override252 @NotNull253 public String getNullTerminatedString(int index, int maxLengthBytes) throws BufferBoundsException254 {255 // NOTE currently only really suited to single-byte character strings256 257 checkBounds(index, maxLengthBytes);258 259 // Check for null terminators260 int length = 0;261 while ((index + length) < _buffer.length && _buffer[index + length] != '\0' && length < maxLengthBytes)262 length++;263 264 byte[] bytes = getBytes(index, length);265 return new String(bytes);266 }267 268 private void checkBounds(final int index, final int bytesRequested) throws BufferBoundsException269 {270 if (bytesRequested < 0 || index < 0 || (long)index + (long)bytesRequested - 1L >= (long)_buffer.length)271 throw new BufferBoundsException(_buffer, index, bytesRequested);272 }273 89 } -
trunk/src/com/drew/lang/CompoundException.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.lang; … … 32 32 * of these previous JDK versions. 33 33 * 34 * @author Drew Noakes http ://drewnoakes.com34 * @author Drew Noakes https://drewnoakes.com 35 35 */ 36 36 public class CompoundException extends Exception … … 63 63 } 64 64 65 @Override 65 66 @NotNull 66 67 public String toString() … … 77 78 } 78 79 80 @Override 79 81 public void printStackTrace(@NotNull PrintStream s) 80 82 { … … 86 88 } 87 89 90 @Override 88 91 public void printStackTrace(@NotNull PrintWriter s) 89 92 { … … 95 98 } 96 99 100 @Override 97 101 public void printStackTrace() 98 102 { -
trunk/src/com/drew/lang/GeoLocation.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 … … 25 25 import com.drew.lang.annotations.Nullable; 26 26 27 import java.text.DecimalFormat; 28 27 29 /** 28 30 * Represents a latitude and longitude pair, giving a position on earth in spherical coordinates. 31 * <p> 29 32 * Values of latitude and longitude are given in degrees. 33 * <p> 30 34 * This type is immutable. 31 35 */ … … 79 83 { 80 84 double[] dms = decimalToDegreesMinutesSeconds(decimal); 81 return dms[0] + "° " + dms[1] + "' " + dms[2] + '"'; 85 DecimalFormat format = new DecimalFormat("0.##"); 86 return String.format("%s° %s' %s\"", format.format(dms[0]), format.format(dms[1]), format.format(dms[2])); 82 87 } 83 88 -
trunk/src/com/drew/lang/NullOutputStream.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.lang; … … 27 27 * An implementation of OutputSteam that ignores write requests by doing nothing. This class may be useful in tests. 28 28 * 29 * @author Drew Noakes http ://drewnoakes.com29 * @author Drew Noakes https://drewnoakes.com 30 30 */ 31 31 public class NullOutputStream extends OutputStream … … 36 36 } 37 37 38 @Override 38 39 public void write(int b) throws IOException 39 40 { -
trunk/src/com/drew/lang/Rational.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 … … 29 29 /** 30 30 * Immutable class for holding a rational number without loss of precision. Provides 31 * a familiar representation via toString() in form <code>numerator/denominator</code>. 32 * 33 * @author Drew Noakes http://drewnoakes.com 31 * a familiar representation via {@link Rational#toString} in form <code>numerator/denominator</code>. 32 * 33 * Note that any value with a numerator of zero will be treated as zero, even if the 34 * denominator is also zero. 35 * 36 * @author Drew Noakes https://drewnoakes.com 34 37 */ 35 38 public class Rational extends java.lang.Number implements Serializable … … 61 64 * to type <code>double</code>. 62 65 */ 66 @Override 63 67 public double doubleValue() 64 68 { 65 return (double) _numerator / (double) _denominator; 69 return _numerator == 0 70 ? 0.0 71 : (double) _numerator / (double) _denominator; 66 72 } 67 73 … … 73 79 * to type <code>float</code>. 74 80 */ 81 @Override 75 82 public float floatValue() 76 83 { 77 return (float) _numerator / (float) _denominator; 84 return _numerator == 0 85 ? 0.0f 86 : (float) _numerator / (float) _denominator; 78 87 } 79 88 … … 81 90 * Returns the value of the specified number as a <code>byte</code>. 82 91 * This may involve rounding or truncation. This implementation simply 83 * casts the result of <code>doubleValue()</code>to <code>byte</code>.92 * casts the result of {@link Rational#doubleValue} to <code>byte</code>. 84 93 * 85 94 * @return the numeric value represented by this object after conversion 86 95 * to type <code>byte</code>. 87 96 */ 97 @Override 88 98 public final byte byteValue() 89 99 { … … 94 104 * Returns the value of the specified number as an <code>int</code>. 95 105 * This may involve rounding or truncation. This implementation simply 96 * casts the result of <code>doubleValue()</code>to <code>int</code>.106 * casts the result of {@link Rational#doubleValue} to <code>int</code>. 97 107 * 98 108 * @return the numeric value represented by this object after conversion 99 109 * to type <code>int</code>. 100 110 */ 111 @Override 101 112 public final int intValue() 102 113 { … … 107 118 * Returns the value of the specified number as a <code>long</code>. 108 119 * This may involve rounding or truncation. This implementation simply 109 * casts the result of <code>doubleValue()</code>to <code>long</code>.120 * casts the result of {@link Rational#doubleValue} to <code>long</code>. 110 121 * 111 122 * @return the numeric value represented by this object after conversion 112 123 * to type <code>long</code>. 113 124 */ 125 @Override 114 126 public final long longValue() 115 127 { … … 120 132 * Returns the value of the specified number as a <code>short</code>. 121 133 * This may involve rounding or truncation. This implementation simply 122 * casts the result of <code>doubleValue()</code>to <code>short</code>.134 * casts the result of {@link Rational#doubleValue} to <code>short</code>. 123 135 * 124 136 * @return the numeric value represented by this object after conversion 125 137 * to type <code>short</code>. 126 138 */ 139 @Override 127 140 public final short shortValue() 128 141 { … … 154 167 } 155 168 156 /** Checks if this rationalnumber is an Integer, either positive or negative. */169 /** Checks if this {@link Rational} number is an Integer, either positive or negative. */ 157 170 public boolean isInteger() 158 171 { … … 167 180 * @return a string representation of the object. 168 181 */ 182 @Override 169 183 @NotNull 170 184 public String toString() … … 173 187 } 174 188 175 /** Returns the simplest representation of this Rational's value possible. */189 /** Returns the simplest representation of this {@link Rational}'s value possible. */ 176 190 @NotNull 177 191 public String toSimpleString(boolean allowDecimal) … … 211 225 212 226 /** 213 * Compares two <code>Rational</code>instances, returning true if they are mathematically227 * Compares two {@link Rational} instances, returning true if they are mathematically 214 228 * equivalent. 215 229 * 216 * @param obj the Rationalto compare this instance to.230 * @param obj the {@link Rational} to compare this instance to. 217 231 * @return true if instances are mathematically equivalent, otherwise false. Will also 218 * return false if <code>obj</code> is not an instance of <code>Rational</code>.232 * return false if <code>obj</code> is not an instance of {@link Rational}. 219 233 */ 220 234 @Override … … 235 249 /** 236 250 * <p> 237 * Simplifies the Rationalnumber.</p>251 * Simplifies the {@link Rational} number.</p> 238 252 * <p> 239 253 * Prime number series: 1, 2, 3, 5, 7, 9, 11, 13, 17</p> … … 244 258 * <p> 245 259 * However, generating the prime number series seems to be a hefty task. Perhaps 246 * it's simpler to check if both d & n are divisible by all numbers from 2 ->260 * it's simpler to check if both d & n are divisible by all numbers from 2 {@literal ->} 247 261 * (Math.min(denominator, numerator) / 2). In doing this, one can check for 2 248 262 * and 5 once, then ignore all even numbers, and all numbers ending in 0 or 5. … … 250 264 * <p> 251 265 * Therefore, the max number of pairs of modulus divisions required will be:</p> 252 * < code><pre>266 * <pre><code> 253 267 * 4 Math.min(denominator, numerator) - 1 254 268 * -- * ------------------------------------ + 2 255 269 * 10 2 256 * <p/>270 * 257 271 * Math.min(denominator, numerator) - 1 258 272 * = ------------------------------------ + 2 259 273 * 5 260 * </ pre></code>274 * </code></pre> 261 275 * 262 276 * @return a simplified instance, or if the Rational could not be simplified, -
trunk/src/com/drew/lang/StringUtil.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 … … 23 23 24 24 import com.drew.lang.annotations.NotNull; 25 import com.drew.lang.annotations.Nullable; 25 26 27 import java.io.BufferedReader; 28 import java.io.IOException; 29 import java.io.InputStream; 30 import java.io.InputStreamReader; 26 31 import java.util.Iterator; 27 32 28 /** @author Drew Noakes http://drewnoakes.com */ 33 /** 34 * @author Drew Noakes https://drewnoakes.com 35 */ 29 36 public class StringUtil 30 37 { 38 @NotNull 31 39 public static String join(@NotNull Iterable<? extends CharSequence> strings, @NotNull String delimiter) 32 40 { … … 50 58 } 51 59 60 @NotNull 52 61 public static <T extends CharSequence> String join(@NotNull T[] strings, @NotNull String delimiter) 53 62 { … … 69 78 return buffer.toString(); 70 79 } 80 81 @NotNull 82 public static String fromStream(@NotNull InputStream stream) throws IOException 83 { 84 BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); 85 StringBuilder sb = new StringBuilder(); 86 String line; 87 while ((line = reader.readLine()) != null) { 88 sb.append(line); 89 } 90 return sb.toString(); 91 } 92 93 public static int compare(@Nullable String s1, @Nullable String s2) 94 { 95 boolean null1 = s1 == null; 96 boolean null2 = s2 == null; 97 98 if (null1 && null2) { 99 return 0; 100 } else if (null1) { 101 return -1; 102 } else if (null2) { 103 return 1; 104 } else { 105 return s1.compareTo(s2); 106 } 107 } 108 109 @NotNull 110 public static String urlEncode(@NotNull String name) 111 { 112 // Sufficient for now, it seems 113 return name.replace(" ", "%20"); 114 } 71 115 } -
trunk/src/com/drew/lang/annotations/NotNull.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 … … 23 23 24 24 /** 25 * @author Drew Noakes http ://drewnoakes.com25 * @author Drew Noakes https://drewnoakes.com 26 26 */ 27 27 public @interface NotNull -
trunk/src/com/drew/lang/annotations/Nullable.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 … … 23 23 24 24 /** 25 * @author Drew Noakes http ://drewnoakes.com25 * @author Drew Noakes https://drewnoakes.com 26 26 */ 27 27 public @interface Nullable -
trunk/src/com/drew/lang/annotations/SuppressWarnings.java
r6127 r8132 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 -
trunk/src/com/drew/metadata/Age.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 … … 27 27 /** 28 28 * Represents an age in years, months, days, hours, minutes and seconds. 29 * <p />29 * <p> 30 30 * Used by certain Panasonic cameras which have face recognition features. 31 31 * 32 * @author Drew Noakes http ://drewnoakes.com32 * @author Drew Noakes https://drewnoakes.com 33 33 */ 34 34 public class Age 35 35 { 36 private int _years;37 private int _months;38 private int _days;39 private int _hours;40 private int _minutes;41 private int _seconds;36 private final int _years; 37 private final int _months; 38 private final int _days; 39 private final int _hours; 40 private final int _minutes; 41 private final int _seconds; 42 42 43 43 /** 44 44 * Parses an age object from the string format used by Panasonic cameras: 45 45 * <code>0031:07:15 00:00:00</code> 46 * 46 47 * @param s The String in format <code>0031:07:15 00:00:00</code>. 47 48 * @return The parsed Age object, or null if the value could not be parsed -
trunk/src/com/drew/metadata/DefaultTagDescriptor.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata; … … 28 28 * and gives descriptions using the default string representation of the value. 29 29 * 30 * @author Drew Noakes http ://drewnoakes.com30 * @author Drew Noakes https://drewnoakes.com 31 31 */ 32 32 public class DefaultTagDescriptor extends TagDescriptor<Directory> -
trunk/src/com/drew/metadata/Directory.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata; … … 37 37 * data types. 38 38 * 39 * @author Drew Noakes http ://drewnoakes.com39 * @author Drew Noakes https://drewnoakes.com 40 40 */ 41 41 public abstract class Directory 42 42 { 43 // TODO get Array methods need to return cloned data, to maintain this directory's integrity44 45 43 /** Map of values hashed by type identifiers. */ 46 44 @NotNull … … 104 102 public Collection<Tag> getTags() 105 103 { 106 return _definedTagList;104 return Collections.unmodifiableCollection(_definedTagList); 107 105 } 108 106 … … 158 156 public Iterable<String> getErrors() 159 157 { 160 return _errorList;158 return Collections.unmodifiableCollection(_errorList); 161 159 } 162 160 … … 414 412 return null; 415 413 416 if (o instanceof String) { 414 if (o instanceof Number) { 415 return ((Number)o).intValue(); 416 } else if (o instanceof String) { 417 417 try { 418 418 return Integer.parseInt((String)o); … … 428 428 return (int)val; 429 429 } 430 } else if (o instanceof Number) {431 return ((Number)o).intValue();432 430 } else if (o instanceof Rational[]) { 433 431 Rational[] rationals = (Rational[])o; … … 498 496 if (o == null) 499 497 return null; 498 if (o instanceof int[]) 499 return (int[])o; 500 500 if (o instanceof Rational[]) { 501 501 Rational[] rationals = (Rational[])o; … … 506 506 return ints; 507 507 } 508 if (o instanceof int[]) 509 return (int[])o; 508 if (o instanceof short[]) { 509 short[] shorts = (short[])o; 510 int[] ints = new int[shorts.length]; 511 for (int i = 0; i < shorts.length; i++) { 512 ints[i] = shorts[i]; 513 } 514 return ints; 515 } 510 516 if (o instanceof byte[]) { 511 517 byte[] bytes = (byte[])o; 512 518 int[] ints = new int[bytes.length]; 513 519 for (int i = 0; i < bytes.length; i++) { 514 byte b = bytes[i]; 515 ints[i] = b; 520 ints[i] = bytes[i]; 516 521 } 517 522 return ints; … … 527 532 if (o instanceof Integer) 528 533 return new int[] { (Integer)o }; 529 534 530 535 return null; 531 536 } … … 560 565 } 561 566 return bytes; 567 } else if (o instanceof short[]) { 568 short[] shorts = (short[])o; 569 byte[] bytes = new byte[shorts.length]; 570 for (int i = 0; i < shorts.length; i++) { 571 bytes[i] = (byte)shorts[i]; 572 } 573 return bytes; 562 574 } else if (o instanceof CharSequence) { 563 575 CharSequence str = (CharSequence)o; … … 703 715 /** 704 716 * Returns the specified tag's value as a java.util.Date. If the value is unset or cannot be converted, <code>null</code> is returned. 705 * <p />717 * <p> 706 718 * If the underlying value is a {@link String}, then attempts will be made to parse the string as though it is in 707 719 * the current {@link TimeZone}. If the {@link TimeZone} is known, call the overload that accepts one as an argument. … … 712 724 return getDate(tagType, null); 713 725 } 714 726 715 727 /** 716 728 * Returns the specified tag's value as a java.util.Date. If the value is unset or cannot be converted, <code>null</code> is returned. 717 * <p />729 * <p> 718 730 * If the underlying value is a {@link String}, then attempts will be made to parse the string as though it is in 719 731 * the {@link TimeZone} represented by the {@code timeZone} parameter (if it is non-null). Note that this parameter … … 818 830 boolean isLongArray = componentType.getName().equals("long"); 819 831 boolean isByteArray = componentType.getName().equals("byte"); 832 boolean isShortArray = componentType.getName().equals("short"); 820 833 StringBuilder string = new StringBuilder(); 821 834 for (int i = 0; i < arrayLength; i++) { … … 826 839 else if (isIntArray) 827 840 string.append(Array.getInt(o, i)); 841 else if (isShortArray) 842 string.append(Array.getShort(o, i)); 828 843 else if (isLongArray) 829 844 string.append(Array.getLong(o, i)); … … 896 911 897 912 /** 913 * Gets whether the specified tag is known by the directory and has a name. 914 * 915 * @param tagType the tag type identifier 916 * @return whether this directory has a name for the specified tag 917 */ 918 public boolean hasTagName(int tagType) 919 { 920 return getTagNameMap().containsKey(tagType); 921 } 922 923 /** 898 924 * Provides a description of a tag's value using the descriptor set by 899 925 * <code>setDescriptor(Descriptor)</code>. … … 908 934 return _descriptor.getDescription(tagType); 909 935 } 936 937 @Override 938 public String toString() 939 { 940 return String.format("%s Directory (%d %s)", 941 getName(), 942 _tagMap.size(), 943 _tagMap.size() == 1 944 ? "tag" 945 : "tags"); 946 } 910 947 } -
trunk/src/com/drew/metadata/Face.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata; … … 26 26 /** 27 27 * Class to hold information about a detected or recognized face in a photo. 28 * <p />28 * <p> 29 29 * When a face is <em>detected</em>, the camera believes that a face is present at a given location in 30 30 * the image, but is not sure whose face it is. When a face is <em>recognised</em>, then the face is … … 116 116 } 117 117 118 @Override 118 119 @NotNull 119 120 public String toString() -
trunk/src/com/drew/metadata/Metadata.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata; … … 24 24 import com.drew.lang.annotations.Nullable; 25 25 26 import java.util.ArrayList; 27 import java.util.Collection; 28 import java.util.HashMap; 29 import java.util.Map; 26 import java.util.*; 30 27 31 28 /** 32 * A top-level object to hold the various types of metadata (Exif/IPTC/etc) related to one entity (such as a file 33 * or stream). 34 * <p/> 35 * Metadata objects may contain zero or more directories. Each directory may contain zero or more tags with 36 * corresponding values. 29 * A top-level object that holds the metadata values extracted from an image. 30 * <p> 31 * Metadata objects may contain zero or more {@link Directory} objects. Each directory may contain zero or more tags 32 * with corresponding values. 37 33 * 38 * @author Drew Noakes http ://drewnoakes.com34 * @author Drew Noakes https://drewnoakes.com 39 35 */ 40 36 public final class Metadata … … 42 38 @NotNull 43 39 private final Map<Class<? extends Directory>,Directory> _directoryByClass = new HashMap<Class<? extends Directory>, Directory>(); 44 40 45 41 /** 46 42 * List of Directory objects set against this object. Keeping a list handy makes … … 58 54 public Iterable<Directory> getDirectories() 59 55 { 60 return _directoryList;56 return Collections.unmodifiableCollection(_directoryList); 61 57 } 62 58 … … 72 68 73 69 /** 74 * Returns a <code>Directory</code> of specified type. If this <code>Metadata</code>object already contains70 * Returns a {@link Directory} of specified type. If this {@link Metadata} object already contains 75 71 * such a directory, it is returned. Otherwise a new instance of this directory will be created and stored within 76 * this Metadataobject.72 * this {@link Metadata} object. 77 73 * 78 74 * @param type the type of the Directory implementation required. … … 104 100 105 101 /** 106 * If this <code>Metadata</code> object contains a <code>Directory</code>of the specified type, it is returned.102 * If this {@link Metadata} object contains a {@link Directory} of the specified type, it is returned. 107 103 * Otherwise <code>null</code> is returned. 108 104 * 109 105 * @param type the Directory type 110 106 * @param <T> the Directory type 111 * @return a Directory of type T if it exists in this Metadataobject, otherwise <code>null</code>.107 * @return a Directory of type T if it exists in this {@link Metadata} object, otherwise <code>null</code>. 112 108 */ 113 109 @Nullable … … 123 119 /** 124 120 * Indicates whether a given directory type has been created in this metadata 125 * repository. Directories are created by calling <code>getOrCreateDirectory(Class)</code>.121 * repository. Directories are created by calling {@link Metadata#getOrCreateDirectory(Class)}. 126 122 * 127 * @param type the Directorytype128 * @return true if the metadata directoryhas been created123 * @param type the {@link Directory} type 124 * @return true if the {@link Directory} has been created 129 125 */ 130 126 public boolean containsDirectory(Class<? extends Directory> type) … … 135 131 /** 136 132 * Indicates whether any errors were reported during the reading of metadata values. 137 * This value will be true if Directory.hasErrors() is true for one of the contained Directoryobjects.133 * This value will be true if Directory.hasErrors() is true for one of the contained {@link Directory} objects. 138 134 * 139 135 * @return whether one of the contained directories has an error … … 147 143 return false; 148 144 } 145 146 @Override 147 public String toString() 148 { 149 return String.format("Metadata (%d %s)", 150 _directoryList.size(), 151 _directoryList.size() == 1 152 ? "directory" 153 : "directories"); 154 } 149 155 } -
trunk/src/com/drew/metadata/MetadataException.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata; … … 27 27 * Base class for all metadata specific exceptions. 28 28 * 29 * @author Drew Noakes http ://drewnoakes.com29 * @author Drew Noakes https://drewnoakes.com 30 30 */ 31 31 public class MetadataException extends CompoundException -
trunk/src/com/drew/metadata/MetadataReader.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata; 22 22 23 import com.drew.lang. BufferReader;23 import com.drew.lang.RandomAccessReader; 24 24 import com.drew.lang.annotations.NotNull; 25 25 26 26 /** 27 * Interface through which all classes responsible for decoding a particular type of metadata may be called. 28 * Note that the data source is not specified on this interface. Instead it is suggested that implementations 29 * take their data within a constructor. Constructors might be overloaded to allow for different sources, such as 30 * files, streams and byte arrays. As such, instances of implementations of this interface would be single-use and 31 * not thread-safe. 27 * Defines an object capable of processing a particular type of metadata from a {@link RandomAccessReader}. 28 * <p> 29 * Instances of this interface must be thread-safe and reusable. 32 30 * 33 * @author Drew Noakes http ://drewnoakes.com31 * @author Drew Noakes https://drewnoakes.com 34 32 */ 35 33 public interface MetadataReader 36 34 { 37 35 /** 38 * Extract metadata from the source and merge it into an existing Metadataobject.36 * Extracts metadata from <code>reader</code> and merges it into the specified {@link Metadata} object. 39 37 * 40 * @param reader The readerfrom which the metadata should be extracted.41 * @param metadata The Metadataobject into which extracted values should be merged.38 * @param reader The {@link RandomAccessReader} from which the metadata should be extracted. 39 * @param metadata The {@link Metadata} object into which extracted values should be merged. 42 40 */ 43 public void extract(@NotNull final BufferReader reader, @NotNull final Metadata metadata);41 public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata); 44 42 } -
trunk/src/com/drew/metadata/Tag.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata; … … 25 25 26 26 /** 27 * Models a particular tag within a directory and provides methods for obtaining its value. Note that a Tag instance is28 * specific to a particular metadata extraction and cannot be reused.27 * Models a particular tag within a {@link com.drew.metadata.Directory} and provides methods for obtaining its value. 28 * Immutable. 29 29 * 30 * @author Drew Noakes http ://drewnoakes.com30 * @author Drew Noakes https://drewnoakes.com 31 31 */ 32 32 public class Tag … … 79 79 80 80 /** 81 * Get whether this tag has a name. 82 * 83 * If <code>true</code>, it may be accessed via {@link #getTagName}. 84 * If <code>false</code>, {@link #getTagName} will return a string resembling <code>"Unknown tag (0x1234)"</code>. 85 * 86 * @return whether this tag has a name 87 */ 88 @NotNull 89 public boolean hasTagName() 90 { 91 return _directory.hasTagName(_tagType); 92 } 93 94 /** 81 95 * Get the name of the tag, such as <code>Aperture</code>, or 82 96 * <code>InteropVersion</code>. … … 91 105 92 106 /** 93 * Get the name of the directoryin which the tag exists, such as107 * Get the name of the {@link com.drew.metadata.Directory} in which the tag exists, such as 94 108 * <code>Exif</code>, <code>GPS</code> or <code>Interoperability</code>. 95 109 * 96 * @return name of the directoryin which this tag exists110 * @return name of the {@link com.drew.metadata.Directory} in which this tag exists 97 111 */ 98 112 @NotNull … … 107 121 * @return the tag's type and value 108 122 */ 123 @Override 109 124 @NotNull 110 125 public String toString() 111 126 { 112 127 String description = getDescription(); 113 if (description ==null)128 if (description == null) 114 129 description = _directory.getString(getTagType()) + " (unable to formulate description)"; 115 130 return "[" + _directory.getName() + "] " + getTagName() + " - " + description; -
trunk/src/com/drew/metadata/TagDescriptor.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata; 22 22 23 import com.drew.lang.Rational; 24 import com.drew.lang.StringUtil; 23 25 import com.drew.lang.annotations.NotNull; 24 26 import com.drew.lang.annotations.Nullable; 25 27 28 import java.io.UnsupportedEncodingException; 26 29 import java.lang.reflect.Array; 30 import java.util.ArrayList; 31 import java.util.Date; 32 import java.util.List; 27 33 28 34 /** 29 * Abstract base class for all tag descriptor classes. Implementations are responsible for35 * Base class for all tag descriptor classes. Implementations are responsible for 30 36 * providing the human-readable string representation of tag values stored in a directory. 31 37 * The directory is provided to the tag descriptor via its constructor. 32 38 * 33 * @author Drew Noakes http ://drewnoakes.com39 * @author Drew Noakes https://drewnoakes.com 34 40 */ 35 public abstractclass TagDescriptor<T extends Directory>41 public class TagDescriptor<T extends Directory> 36 42 { 37 43 @NotNull … … 44 50 45 51 /** 46 * Returns a descriptive value of the thespecified tag for this image.52 * Returns a descriptive value of the specified tag for this image. 47 53 * Where possible, known values will be substituted here in place of the raw 48 54 * tokens actually kept in the metadata segment. If no substitution is 49 55 * available, the value provided by <code>getString(tagType)</code> will be returned. 50 * 56 * 51 57 * @param tagType the tag to find a description for 52 58 * @return a description of the image's value for the specified tag, or … … 58 64 Object object = _directory.getObject(tagType); 59 65 60 if (object ==null)66 if (object == null) 61 67 return null; 62 68 … … 66 72 if (length > 16) { 67 73 final String componentTypeName = object.getClass().getComponentType().getName(); 68 return String.format("[%d %s%s]", length, componentTypeName, length ==1 ? "" : "s");74 return String.format("[%d %s%s]", length, componentTypeName, length == 1 ? "" : "s"); 69 75 } 70 76 } … … 77 83 * Takes a series of 4 bytes from the specified offset, and converts these to a 78 84 * well-known version number, where possible. 79 * <p />85 * <p> 80 86 * Two different formats are processed: 81 87 * <ul> 82 * 83 * 88 * <li>[30 32 31 30] -> 2.10</li> 89 * <li>[0 1 0 0] -> 1.00</li> 84 90 * </ul> 85 * @param components the four version values 86 * @param majorDigits the number of components to be 91 * 92 * @param components the four version values 93 * @param majorDigits the number of components to be 87 94 * @return the version as a string of form "2.10" or null if the argument cannot be converted 88 95 */ … … 90 97 public static String convertBytesToVersionString(@Nullable int[] components, final int majorDigits) 91 98 { 92 if (components ==null)99 if (components == null) 93 100 return null; 94 101 StringBuilder version = new StringBuilder(); … … 99 106 if (c < '0') 100 107 c += '0'; 101 if (i == 0 && c =='0')108 if (i == 0 && c == '0') 102 109 continue; 103 110 version.append(c); … … 105 112 return version.toString(); 106 113 } 114 115 @Nullable 116 protected String getVersionBytesDescription(final int tagType, int majorDigits) 117 { 118 int[] values = _directory.getIntArray(tagType); 119 return values == null ? null : convertBytesToVersionString(values, majorDigits); 120 } 121 122 @Nullable 123 protected String getIndexedDescription(final int tagType, @NotNull String... descriptions) 124 { 125 return getIndexedDescription(tagType, 0, descriptions); 126 } 127 128 @Nullable 129 protected String getIndexedDescription(final int tagType, final int baseIndex, @NotNull String... descriptions) 130 { 131 final Integer index = _directory.getInteger(tagType); 132 if (index == null) 133 return null; 134 final int arrayIndex = index - baseIndex; 135 if (arrayIndex >= 0 && arrayIndex < descriptions.length) { 136 String description = descriptions[arrayIndex]; 137 if (description != null) 138 return description; 139 } 140 return "Unknown (" + index + ")"; 141 } 142 143 @Nullable 144 protected String getByteLengthDescription(final int tagType) 145 { 146 byte[] bytes = _directory.getByteArray(tagType); 147 if (bytes == null) 148 return null; 149 return String.format("(%d byte%s)", bytes.length, bytes.length == 1 ? "" : "s"); 150 } 151 152 @Nullable 153 protected String getSimpleRational(final int tagType) 154 { 155 Rational value = _directory.getRational(tagType); 156 if (value == null) 157 return null; 158 return value.toSimpleString(true); 159 } 160 161 @Nullable 162 protected String getDecimalRational(final int tagType, final int decimalPlaces) 163 { 164 Rational value = _directory.getRational(tagType); 165 if (value == null) 166 return null; 167 return String.format("%." + decimalPlaces + "f", value.doubleValue()); 168 } 169 170 @Nullable 171 protected String getFormattedInt(final int tagType, @NotNull final String format) 172 { 173 Integer value = _directory.getInteger(tagType); 174 if (value == null) 175 return null; 176 return String.format(format, value); 177 } 178 179 @Nullable 180 protected String getFormattedFloat(final int tagType, @NotNull final String format) 181 { 182 Float value = _directory.getFloatObject(tagType); 183 if (value == null) 184 return null; 185 return String.format(format, value); 186 } 187 188 @Nullable 189 protected String getFormattedString(final int tagType, @NotNull final String format) 190 { 191 String value = _directory.getString(tagType); 192 if (value == null) 193 return null; 194 return String.format(format, value); 195 } 196 197 @Nullable 198 protected String getEpochTimeDescription(final int tagType) 199 { 200 // TODO have observed a byte[8] here which is likely some kind of date (ticks as long?) 201 Long value = _directory.getLongObject(tagType); 202 if (value==null) 203 return null; 204 return new Date(value).toString(); 205 } 206 207 /** 208 * LSB first. Labels may be null, a String, or a String[2] with (low label,high label) values. 209 */ 210 @Nullable 211 protected String getBitFlagDescription(final int tagType, @NotNull final Object... labels) 212 { 213 Integer value = _directory.getInteger(tagType); 214 215 if (value == null) 216 return null; 217 218 List<String> parts = new ArrayList<String>(); 219 220 int bitIndex = 0; 221 while (labels.length > bitIndex) { 222 Object labelObj = labels[bitIndex]; 223 if (labelObj != null) { 224 boolean isBitSet = (value & 1) == 1; 225 if (labelObj instanceof String[]) { 226 String[] labelPair = (String[])labelObj; 227 assert(labelPair.length == 2); 228 parts.add(labelPair[isBitSet ? 1 : 0]); 229 } else if (isBitSet && labelObj instanceof String) { 230 parts.add((String)labelObj); 231 } 232 } 233 value >>= 1; 234 bitIndex++; 235 } 236 237 return StringUtil.join(parts, ", "); 238 } 239 240 @Nullable 241 protected String get7BitStringFromBytes(final int tagType) 242 { 243 final byte[] bytes = _directory.getByteArray(tagType); 244 245 if (bytes == null) 246 return null; 247 248 int length = bytes.length; 249 for (int index = 0; index < bytes.length; index++) { 250 int i = bytes[index] & 0xFF; 251 if (i == 0 || i > 0x7F) { 252 length = index; 253 break; 254 } 255 } 256 257 return new String(bytes, 0, length); 258 } 259 260 @Nullable 261 protected String getAsciiStringFromBytes(int tag) 262 { 263 byte[] values = _directory.getByteArray(tag); 264 265 if (values == null) 266 return null; 267 268 try { 269 return new String(values, "ASCII").trim(); 270 } catch (UnsupportedEncodingException e) { 271 return null; 272 } 273 } 107 274 } -
trunk/src/com/drew/metadata/exif/ExifIFD0Descriptor.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 … … 29 29 import java.io.UnsupportedEncodingException; 30 30 31 import static com.drew.metadata.exif.ExifIFD0Directory.*; 32 31 33 /** 32 * Provides human-readable string representations of tag values stored in a <code>ExifIFD0Directory</code>.33 * 34 * @author Drew Noakes http ://drewnoakes.com34 * Provides human-readable string representations of tag values stored in a {@link ExifIFD0Directory}. 35 * 36 * @author Drew Noakes https://drewnoakes.com 35 37 */ 36 38 public class ExifIFD0Descriptor extends TagDescriptor<ExifIFD0Directory> … … 54 56 55 57 /** 56 * Returns a descriptive value of the thespecified tag for this image.58 * Returns a descriptive value of the specified tag for this image. 57 59 * Where possible, known values will be substituted here in place of the raw 58 60 * tokens actually kept in the Exif segment. If no substitution is … … 62 64 * <code>null</code> if the tag hasn't been defined. 63 65 */ 66 @Override 64 67 @Nullable 65 68 public String getDescription(int tagType) 66 69 { 67 70 switch (tagType) { 68 case ExifIFD0Directory.TAG_RESOLUTION_UNIT:71 case TAG_RESOLUTION_UNIT: 69 72 return getResolutionDescription(); 70 case ExifIFD0Directory.TAG_YCBCR_POSITIONING:73 case TAG_YCBCR_POSITIONING: 71 74 return getYCbCrPositioningDescription(); 72 case ExifIFD0Directory.TAG_X_RESOLUTION:75 case TAG_X_RESOLUTION: 73 76 return getXResolutionDescription(); 74 case ExifIFD0Directory.TAG_Y_RESOLUTION:77 case TAG_Y_RESOLUTION: 75 78 return getYResolutionDescription(); 76 case ExifIFD0Directory.TAG_REFERENCE_BLACK_WHITE:79 case TAG_REFERENCE_BLACK_WHITE: 77 80 return getReferenceBlackWhiteDescription(); 78 case ExifIFD0Directory.TAG_ORIENTATION:81 case TAG_ORIENTATION: 79 82 return getOrientationDescription(); 80 83 81 case ExifIFD0Directory.TAG_WIN_AUTHOR:84 case TAG_WIN_AUTHOR: 82 85 return getWindowsAuthorDescription(); 83 case ExifIFD0Directory.TAG_WIN_COMMENT:86 case TAG_WIN_COMMENT: 84 87 return getWindowsCommentDescription(); 85 case ExifIFD0Directory.TAG_WIN_KEYWORDS:88 case TAG_WIN_KEYWORDS: 86 89 return getWindowsKeywordsDescription(); 87 case ExifIFD0Directory.TAG_WIN_SUBJECT:90 case TAG_WIN_SUBJECT: 88 91 return getWindowsSubjectDescription(); 89 case ExifIFD0Directory.TAG_WIN_TITLE:92 case TAG_WIN_TITLE: 90 93 return getWindowsTitleDescription(); 91 94 … … 98 101 public String getReferenceBlackWhiteDescription() 99 102 { 100 int[] ints = _directory.getIntArray( ExifIFD0Directory.TAG_REFERENCE_BLACK_WHITE);101 if (ints==null )103 int[] ints = _directory.getIntArray(TAG_REFERENCE_BLACK_WHITE); 104 if (ints==null || ints.length < 6) 102 105 return null; 103 106 int blackR = ints[0]; … … 107 110 int blackB = ints[4]; 108 111 int whiteB = ints[5]; 109 return "[" + blackR + "," + blackG + "," + blackB + "] " + 110 "[" + whiteR + "," + whiteG + "," + whiteB + "]"; 112 return String.format("[%d,%d,%d] [%d,%d,%d]", blackR, blackG, blackB, whiteR, whiteG, whiteB); 111 113 } 112 114 … … 114 116 public String getYResolutionDescription() 115 117 { 116 Rational value = _directory.getRational( ExifIFD0Directory.TAG_Y_RESOLUTION);118 Rational value = _directory.getRational(TAG_Y_RESOLUTION); 117 119 if (value==null) 118 120 return null; 119 121 final String unit = getResolutionDescription(); 120 return value.toSimpleString(_allowDecimalRepresentationOfRationals) +121 " dots per " +122 (unit==null ? "unit" : unit.toLowerCase());122 return String.format("%s dots per %s", 123 value.toSimpleString(_allowDecimalRepresentationOfRationals), 124 unit == null ? "unit" : unit.toLowerCase()); 123 125 } 124 126 … … 126 128 public String getXResolutionDescription() 127 129 { 128 Rational value = _directory.getRational( ExifIFD0Directory.TAG_X_RESOLUTION);130 Rational value = _directory.getRational(TAG_X_RESOLUTION); 129 131 if (value==null) 130 132 return null; 131 133 final String unit = getResolutionDescription(); 132 return value.toSimpleString(_allowDecimalRepresentationOfRationals) +133 " dots per " +134 (unit==null ? "unit" : unit.toLowerCase());134 return String.format("%s dots per %s", 135 value.toSimpleString(_allowDecimalRepresentationOfRationals), 136 unit == null ? "unit" : unit.toLowerCase()); 135 137 } 136 138 … … 138 140 public String getYCbCrPositioningDescription() 139 141 { 140 Integer value = _directory.getInteger(ExifIFD0Directory.TAG_YCBCR_POSITIONING); 141 if (value==null) 142 return null; 143 switch (value) { 144 case 1: return "Center of pixel array"; 145 case 2: return "Datum point"; 146 default: 147 return String.valueOf(value); 142 return getIndexedDescription(TAG_YCBCR_POSITIONING, 1, "Center of pixel array", "Datum point"); 143 } 144 145 @Nullable 146 public String getOrientationDescription() 147 { 148 return getIndexedDescription(TAG_ORIENTATION, 1, 149 "Top, left side (Horizontal / normal)", 150 "Top, right side (Mirror horizontal)", 151 "Bottom, right side (Rotate 180)", 152 "Bottom, left side (Mirror vertical)", 153 "Left side, top (Mirror horizontal and rotate 270 CW)", 154 "Right side, top (Rotate 90 CW)", 155 "Right side, bottom (Mirror horizontal and rotate 90 CW)", 156 "Left side, bottom (Rotate 270 CW)"); 157 } 158 159 @Nullable 160 public String getResolutionDescription() 161 { 162 // '1' means no-unit, '2' means inch, '3' means centimeter. Default value is '2'(inch) 163 return getIndexedDescription(TAG_RESOLUTION_UNIT, 1, "(No unit)", "Inch", "cm"); 164 } 165 166 /** The Windows specific tags uses plain Unicode. */ 167 @Nullable 168 private String getUnicodeDescription(int tag) 169 { 170 byte[] bytes = _directory.getByteArray(tag); 171 if (bytes == null) 172 return null; 173 try { 174 // Decode the unicode string and trim the unicode zero "\0" from the end. 175 return new String(bytes, "UTF-16LE").trim(); 176 } catch (UnsupportedEncodingException ex) { 177 return null; 148 178 } 149 179 } 150 180 151 181 @Nullable 152 public String getOrientationDescription()153 {154 Integer value = _directory.getInteger(ExifIFD0Directory.TAG_ORIENTATION);155 if (value==null)156 return null;157 switch (value) {158 case 1: return "Top, left side (Horizontal / normal)";159 case 2: return "Top, right side (Mirror horizontal)";160 case 3: return "Bottom, right side (Rotate 180)";161 case 4: return "Bottom, left side (Mirror vertical)";162 case 5: return "Left side, top (Mirror horizontal and rotate 270 CW)";163 case 6: return "Right side, top (Rotate 90 CW)";164 case 7: return "Right side, bottom (Mirror horizontal and rotate 90 CW)";165 case 8: return "Left side, bottom (Rotate 270 CW)";166 default:167 return String.valueOf(value);168 }169 }170 171 @Nullable172 public String getResolutionDescription()173 {174 // '1' means no-unit, '2' means inch, '3' means centimeter. Default value is '2'(inch)175 Integer value = _directory.getInteger(ExifIFD0Directory.TAG_RESOLUTION_UNIT);176 if (value==null)177 return null;178 switch (value) {179 case 1: return "(No unit)";180 case 2: return "Inch";181 case 3: return "cm";182 default:183 return "";184 }185 }186 187 /** The Windows specific tags uses plain Unicode. */188 @Nullable189 private String getUnicodeDescription(int tag)190 {191 byte[] commentBytes = _directory.getByteArray(tag);192 if (commentBytes==null)193 return null;194 try {195 // Decode the unicode string and trim the unicode zero "\0" from the end.196 return new String(commentBytes, "UTF-16LE").trim();197 }198 catch (UnsupportedEncodingException ex) {199 return null;200 }201 }202 203 @Nullable204 182 public String getWindowsAuthorDescription() 205 183 { 206 return getUnicodeDescription( ExifIFD0Directory.TAG_WIN_AUTHOR);184 return getUnicodeDescription(TAG_WIN_AUTHOR); 207 185 } 208 186 … … 210 188 public String getWindowsCommentDescription() 211 189 { 212 return getUnicodeDescription( ExifIFD0Directory.TAG_WIN_COMMENT);190 return getUnicodeDescription(TAG_WIN_COMMENT); 213 191 } 214 192 … … 216 194 public String getWindowsKeywordsDescription() 217 195 { 218 return getUnicodeDescription( ExifIFD0Directory.TAG_WIN_KEYWORDS);196 return getUnicodeDescription(TAG_WIN_KEYWORDS); 219 197 } 220 198 … … 222 200 public String getWindowsTitleDescription() 223 201 { 224 return getUnicodeDescription( ExifIFD0Directory.TAG_WIN_TITLE);202 return getUnicodeDescription(TAG_WIN_TITLE); 225 203 } 226 204 … … 228 206 public String getWindowsSubjectDescription() 229 207 { 230 return getUnicodeDescription( ExifIFD0Directory.TAG_WIN_SUBJECT);208 return getUnicodeDescription(TAG_WIN_SUBJECT); 231 209 } 232 210 } -
trunk/src/com/drew/metadata/exif/ExifIFD0Directory.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 … … 30 30 * Describes Exif tags from the IFD0 directory. 31 31 * 32 * @author Drew Noakes http ://drewnoakes.com32 * @author Drew Noakes https://drewnoakes.com 33 33 */ 34 34 public class ExifIFD0Directory extends Directory … … 46 46 public static final int TAG_WHITE_POINT = 0x013E; 47 47 public static final int TAG_PRIMARY_CHROMATICITIES = 0x013F; 48 48 49 public static final int TAG_YCBCR_COEFFICIENTS = 0x0211; 49 50 public static final int TAG_YCBCR_POSITIONING = 0x0213; 50 51 public static final int TAG_REFERENCE_BLACK_WHITE = 0x0214; 52 53 54 /** This tag is a pointer to the Exif SubIFD. */ 55 public static final int TAG_EXIF_SUB_IFD_OFFSET = 0x8769; 56 57 /** This tag is a pointer to the Exif GPS IFD. */ 58 public static final int TAG_GPS_INFO_OFFSET = 0x8825; 59 51 60 public static final int TAG_COPYRIGHT = 0x8298; 61 62 /** Non-standard, but in use. */ 63 public static final int TAG_TIME_ZONE_OFFSET = 0x882a; 52 64 53 65 /** The image title, as used by Windows XP. */ … … 82 94 _tagNameMap.put(TAG_YCBCR_POSITIONING, "YCbCr Positioning"); 83 95 _tagNameMap.put(TAG_REFERENCE_BLACK_WHITE, "Reference Black/White"); 96 84 97 _tagNameMap.put(TAG_COPYRIGHT, "Copyright"); 98 99 _tagNameMap.put(TAG_TIME_ZONE_OFFSET, "Time Zone Offset"); 85 100 86 101 _tagNameMap.put(TAG_WIN_AUTHOR, "Windows XP Author"); … … 96 111 } 97 112 113 @Override 98 114 @NotNull 99 115 public String getName() … … 102 118 } 103 119 120 @Override 104 121 @NotNull 105 122 protected HashMap<Integer, String> getTagNameMap() -
trunk/src/com/drew/metadata/exif/ExifInteropDescriptor.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.exif; … … 25 25 import com.drew.metadata.TagDescriptor; 26 26 27 import static com.drew.metadata.exif.ExifInteropDirectory.*; 28 27 29 /** 28 * Provides human-readable string representations of tag values stored in a <code>ExifInteropDirectory</code>.30 * Provides human-readable string representations of tag values stored in a {@link ExifInteropDirectory}. 29 31 * 30 * @author Drew Noakes http ://drewnoakes.com32 * @author Drew Noakes https://drewnoakes.com 31 33 */ 32 34 public class ExifInteropDescriptor extends TagDescriptor<ExifInteropDirectory> … … 37 39 } 38 40 41 @Override 39 42 @Nullable 40 43 public String getDescription(int tagType) 41 44 { 42 45 switch (tagType) { 43 case ExifInteropDirectory.TAG_INTEROP_INDEX:46 case TAG_INTEROP_INDEX: 44 47 return getInteropIndexDescription(); 45 case ExifInteropDirectory.TAG_INTEROP_VERSION:48 case TAG_INTEROP_VERSION: 46 49 return getInteropVersionDescription(); 47 50 default: … … 53 56 public String getInteropVersionDescription() 54 57 { 55 int[] ints = _directory.getIntArray(ExifInteropDirectory.TAG_INTEROP_VERSION); 56 return convertBytesToVersionString(ints, 2); 58 return getVersionBytesDescription(TAG_INTEROP_VERSION, 2); 57 59 } 58 60 … … 60 62 public String getInteropIndexDescription() 61 63 { 62 String value = _directory.getString( ExifInteropDirectory.TAG_INTEROP_INDEX);64 String value = _directory.getString(TAG_INTEROP_INDEX); 63 65 64 if (value ==null)66 if (value == null) 65 67 return null; 66 68 -
trunk/src/com/drew/metadata/exif/ExifInteropDirectory.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.exif; … … 29 29 * Describes Exif interoperability tags. 30 30 * 31 * @author Drew Noakes http ://drewnoakes.com31 * @author Drew Noakes https://drewnoakes.com 32 32 */ 33 33 public class ExifInteropDirectory extends Directory … … 56 56 } 57 57 58 @Override 58 59 @NotNull 59 60 public String getName() … … 62 63 } 63 64 65 @Override 64 66 @NotNull 65 67 protected HashMap<Integer, String> getTagNameMap() -
trunk/src/com/drew/metadata/exif/ExifReader.java
r6209 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.exif; 22 22 23 import java.util.HashSet; 24 import java.util.Set; 23 import com.drew.imaging.jpeg.JpegSegmentMetadataReader; 24 import com.drew.imaging.jpeg.JpegSegmentType; 25 import com.drew.imaging.tiff.TiffProcessingException; 26 import com.drew.imaging.tiff.TiffReader; 27 import com.drew.lang.ByteArrayReader; 28 import com.drew.lang.annotations.NotNull; 29 import com.drew.metadata.Metadata; 25 30 26 import com.drew.lang.BufferBoundsException; 27 import com.drew.lang.BufferReader; 28 import com.drew.lang.Rational; 29 import com.drew.lang.annotations.NotNull; 30 import com.drew.metadata.Directory; 31 import com.drew.metadata.Metadata; 32 import com.drew.metadata.MetadataReader; 31 import java.io.IOException; 32 import java.util.Arrays; 33 33 34 34 /** 35 35 * Decodes Exif binary data, populating a {@link Metadata} object with tag values in {@link ExifSubIFDDirectory}, 36 * {@link ExifThumbnailDirectory}, {@link ExifInteropDirectory}, {@link GpsDirectory} and one of the many camera makernote directories. 36 * {@link ExifThumbnailDirectory}, {@link ExifInteropDirectory}, {@link GpsDirectory} and one of the many camera 37 * makernote directories. 37 38 * 38 * @author Drew Noakes http ://drewnoakes.com39 * @author Drew Noakes https://drewnoakes.com 39 40 */ 40 public class ExifReader implements MetadataReader41 public class ExifReader implements JpegSegmentMetadataReader 41 42 { 42 // TODO extract a reusable TiffReader from this class with hooks for special tag handling and subdir following 43 44 /** The number of bytes used per format descriptor. */ 43 /** 44 * The offset at which the TIFF data actually starts. This may be necessary when, for example, processing 45 * JPEG Exif data from APP0 which has a 6-byte preamble before starting the TIFF data. 46 */ 47 private static final String JPEG_EXIF_SEGMENT_PREAMBLE = "Exif\0\0"; 48 49 private boolean _storeThumbnailBytes = true; 50 51 public boolean isStoreThumbnailBytes() 52 { 53 return _storeThumbnailBytes; 54 } 55 56 public void setStoreThumbnailBytes(boolean storeThumbnailBytes) 57 { 58 _storeThumbnailBytes = storeThumbnailBytes; 59 } 60 45 61 @NotNull 46 private static final int[] BYTES_PER_FORMAT = { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 }; 62 public Iterable<JpegSegmentType> getSegmentTypes() 63 { 64 return Arrays.asList(JpegSegmentType.APP1); 65 } 47 66 48 /** The number of formats known. */ 49 private static final int MAX_FORMAT_CODE = 12; 67 public boolean canProcess(@NotNull final byte[] segmentBytes, @NotNull final JpegSegmentType segmentType) 68 { 69 return segmentBytes.length >= JPEG_EXIF_SEGMENT_PREAMBLE.length() && new String(segmentBytes, 0, JPEG_EXIF_SEGMENT_PREAMBLE.length()).equalsIgnoreCase(JPEG_EXIF_SEGMENT_PREAMBLE); 70 } 50 71 51 // Format types 52 // TODO use an enum for these? 53 /** An 8-bit unsigned integer. */ 54 private static final int FMT_BYTE = 1; 55 /** A fixed-length character string. */ 56 private static final int FMT_STRING = 2; 57 /** An unsigned 16-bit integer. */ 58 private static final int FMT_USHORT = 3; 59 /** An unsigned 32-bit integer. */ 60 private static final int FMT_ULONG = 4; 61 private static final int FMT_URATIONAL = 5; 62 /** An 8-bit signed integer. */ 63 private static final int FMT_SBYTE = 6; 64 private static final int FMT_UNDEFINED = 7; 65 /** A signed 16-bit integer. */ 66 private static final int FMT_SSHORT = 8; 67 /** A signed 32-bit integer. */ 68 private static final int FMT_SLONG = 9; 69 private static final int FMT_SRATIONAL = 10; 70 /** A 32-bit floating point number. */ 71 private static final int FMT_SINGLE = 11; 72 /** A 64-bit floating point number. */ 73 private static final int FMT_DOUBLE = 12; 72 public void extract(@NotNull final byte[] segmentBytes, @NotNull final Metadata metadata, @NotNull final JpegSegmentType segmentType) 73 { 74 if (segmentBytes == null) 75 throw new NullPointerException("segmentBytes cannot be null"); 76 if (metadata == null) 77 throw new NullPointerException("metadata cannot be null"); 78 if (segmentType == null) 79 throw new NullPointerException("segmentType cannot be null"); 74 80 75 /** This tag is a pointer to the Exif SubIFD. */ 76 public static final int TAG_EXIF_SUB_IFD_OFFSET = 0x8769; 77 /** This tag is a pointer to the Exif Interop IFD. */ 78 public static final int TAG_INTEROP_OFFSET = 0xA005; 79 /** This tag is a pointer to the Exif GPS IFD. */ 80 public static final int TAG_GPS_INFO_OFFSET = 0x8825; 81 /** This tag is a pointer to the Exif Makernote IFD. */ 82 public static final int TAG_MAKER_NOTE_OFFSET = 0x927C; 81 try { 82 ByteArrayReader reader = new ByteArrayReader(segmentBytes); 83 83 84 public static final int TIFF_HEADER_START_OFFSET = 6; 85 86 /** 87 * Performs the Exif data extraction, adding found values to the specified 88 * instance of <code>Metadata</code>. 89 * 90 * @param reader The buffer reader from which Exif data should be read. 91 * @param metadata The Metadata object into which extracted values should be merged. 92 */ 93 public void extract(@NotNull final BufferReader reader, @NotNull Metadata metadata) 94 { 95 final ExifSubIFDDirectory directory = metadata.getOrCreateDirectory(ExifSubIFDDirectory.class); 96 97 // check for the header length 98 if (reader.getLength() <= 14) { 99 directory.addError("Exif data segment must contain at least 14 bytes"); 100 return; 101 } 102 103 // check for the header preamble 104 try { 105 if (!reader.getString(0, 6).equals("Exif\0\0")) { 106 directory.addError("Exif data segment doesn't begin with 'Exif'"); 84 // 85 // Check for the header preamble 86 // 87 try { 88 if (!reader.getString(0, JPEG_EXIF_SEGMENT_PREAMBLE.length()).equals(JPEG_EXIF_SEGMENT_PREAMBLE)) { 89 // TODO what do to with this error state? 90 System.err.println("Invalid JPEG Exif segment preamble"); 91 return; 92 } 93 } catch (IOException e) { 94 // TODO what do to with this error state? 95 e.printStackTrace(System.err); 107 96 return; 108 97 } 109 98 110 extractIFD(metadata, metadata.getOrCreateDirectory(ExifIFD0Directory.class), TIFF_HEADER_START_OFFSET, reader); 111 } catch (BufferBoundsException e) { 112 directory.addError("Exif data segment ended prematurely"); 99 // 100 // Read the TIFF-formatted Exif data 101 // 102 new TiffReader().processTiff( 103 reader, 104 new ExifTiffHandler(metadata, _storeThumbnailBytes), 105 JPEG_EXIF_SEGMENT_PREAMBLE.length() 106 ); 107 108 } catch (TiffProcessingException e) { 109 // TODO what do to with this error state? 110 e.printStackTrace(System.err); 111 } catch (IOException e) { 112 // TODO what do to with this error state? 113 e.printStackTrace(System.err); 113 114 } 114 115 } 115 116 /**117 * Performs the Exif data extraction on a TIFF/RAW, adding found values to the specified118 * instance of <code>Metadata</code>.119 *120 * @param reader The BufferReader from which TIFF data should be read.121 * @param metadata The Metadata object into which extracted values should be merged.122 */123 public void extractTiff(@NotNull BufferReader reader, @NotNull Metadata metadata)124 {125 final ExifIFD0Directory directory = metadata.getOrCreateDirectory(ExifIFD0Directory.class);126 127 try {128 extractIFD(metadata, directory, 0, reader);129 } catch (BufferBoundsException e) {130 directory.addError("Exif data segment ended prematurely");131 }132 }133 134 private void extractIFD(@NotNull Metadata metadata, @NotNull final ExifIFD0Directory directory, int tiffHeaderOffset, @NotNull BufferReader reader) throws BufferBoundsException135 {136 // this should be either "MM" or "II"137 String byteOrderIdentifier = reader.getString(tiffHeaderOffset, 2);138 139 if ("MM".equals(byteOrderIdentifier)) {140 reader.setMotorolaByteOrder(true);141 } else if ("II".equals(byteOrderIdentifier)) {142 reader.setMotorolaByteOrder(false);143 } else {144 directory.addError("Unclear distinction between Motorola/Intel byte ordering: " + byteOrderIdentifier);145 return;146 }147 148 // Check the next two values for correctness.149 final int tiffMarker = reader.getUInt16(2 + tiffHeaderOffset);150 151 final int standardTiffMarker = 0x002A;152 final int olympusRawTiffMarker = 0x4F52; // for ORF files153 final int panasonicRawTiffMarker = 0x0055; // for RW2 files154 155 if (tiffMarker != standardTiffMarker && tiffMarker != olympusRawTiffMarker && tiffMarker != panasonicRawTiffMarker) {156 directory.addError("Unexpected TIFF marker after byte order identifier: 0x" + Integer.toHexString(tiffMarker));157 return;158 }159 160 int firstDirectoryOffset = reader.getInt32(4 + tiffHeaderOffset) + tiffHeaderOffset;161 162 // David Ekholm sent a digital camera image that has this problem163 if (firstDirectoryOffset >= reader.getLength() - 1) {164 directory.addError("First exif directory offset is beyond end of Exif data segment");165 // First directory normally starts 14 bytes in -- try it here and catch another error in the worst case166 firstDirectoryOffset = 14;167 }168 169 Set<Integer> processedDirectoryOffsets = new HashSet<Integer>();170 171 processDirectory(directory, processedDirectoryOffsets, firstDirectoryOffset, tiffHeaderOffset, metadata, reader);172 173 // after the extraction process, if we have the correct tags, we may be able to store thumbnail information174 ExifThumbnailDirectory thumbnailDirectory = metadata.getDirectory(ExifThumbnailDirectory.class);175 if (thumbnailDirectory!=null && thumbnailDirectory.containsTag(ExifThumbnailDirectory.TAG_THUMBNAIL_COMPRESSION)) {176 Integer offset = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET);177 Integer length = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH);178 if (offset != null && length != null) {179 try {180 byte[] thumbnailData = reader.getBytes(tiffHeaderOffset + offset, length);181 thumbnailDirectory.setThumbnailData(thumbnailData);182 } catch (BufferBoundsException ex) {183 directory.addError("Invalid thumbnail data specification: " + ex.getMessage());184 }185 }186 }187 }188 189 /**190 * Process one of the nested Tiff IFD directories.191 * <p/>192 * Header193 * 2 bytes: number of tags194 * <p/>195 * Then for each tag196 * 2 bytes: tag type197 * 2 bytes: format code198 * 4 bytes: component count199 */200 private void processDirectory(@NotNull Directory directory, @NotNull Set<Integer> processedDirectoryOffsets, int dirStartOffset, int tiffHeaderOffset, @NotNull final Metadata metadata, @NotNull final BufferReader reader) throws BufferBoundsException201 {202 // check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist203 if (processedDirectoryOffsets.contains(Integer.valueOf(dirStartOffset)))204 return;205 206 // remember that we've visited this directory so that we don't visit it again later207 processedDirectoryOffsets.add(dirStartOffset);208 209 if (dirStartOffset >= reader.getLength() || dirStartOffset < 0) {210 directory.addError("Ignored directory marked to start outside data segment");211 return;212 }213 214 // First two bytes in the IFD are the number of tags in this directory215 int dirTagCount = reader.getUInt16(dirStartOffset);216 217 int dirLength = (2 + (12 * dirTagCount) + 4);218 if (dirLength + dirStartOffset > reader.getLength()) {219 directory.addError("Illegally sized directory");220 return;221 }222 223 // Handle each tag in this directory224 for (int tagNumber = 0; tagNumber < dirTagCount; tagNumber++) {225 final int tagOffset = calculateTagOffset(dirStartOffset, tagNumber);226 227 // 2 bytes for the tag type228 final int tagType = reader.getUInt16(tagOffset);229 230 // 2 bytes for the format code231 final int formatCode = reader.getUInt16(tagOffset + 2);232 if (formatCode < 1 || formatCode > MAX_FORMAT_CODE) {233 // This error suggests that we are processing at an incorrect index and will generate234 // rubbish until we go out of bounds (which may be a while). Exit now.235 directory.addError("Invalid TIFF tag format code: " + formatCode);236 continue; // JOSM patch to fix #9030237 }238 239 // 4 bytes dictate the number of components in this tag's data240 final int componentCount = reader.getInt32(tagOffset + 4);241 if (componentCount < 0) {242 directory.addError("Negative TIFF tag component count");243 continue;244 }245 // each component may have more than one byte... calculate the total number of bytes246 final int byteCount = componentCount * BYTES_PER_FORMAT[formatCode];247 final int tagValueOffset;248 if (byteCount > 4) {249 // If it's bigger than 4 bytes, the dir entry contains an offset.250 // dirEntryOffset must be passed, as some makernote implementations (e.g. FujiFilm) incorrectly use an251 // offset relative to the start of the makernote itself, not the TIFF segment.252 final int offsetVal = reader.getInt32(tagOffset + 8);253 if (offsetVal + byteCount > reader.getLength()) {254 // Bogus pointer offset and / or byteCount value255 directory.addError("Illegal TIFF tag pointer offset");256 continue;257 }258 tagValueOffset = tiffHeaderOffset + offsetVal;259 } else {260 // 4 bytes or less and value is in the dir entry itself261 tagValueOffset = tagOffset + 8;262 }263 264 if (tagValueOffset < 0 || tagValueOffset > reader.getLength()) {265 directory.addError("Illegal TIFF tag pointer offset");266 continue;267 }268 269 // Check that this tag isn't going to allocate outside the bounds of the data array.270 // This addresses an uncommon OutOfMemoryError.271 if (byteCount < 0 || tagValueOffset + byteCount > reader.getLength()) {272 directory.addError("Illegal number of bytes: " + byteCount);273 continue;274 }275 276 switch (tagType) {277 case TAG_EXIF_SUB_IFD_OFFSET: {278 final int subdirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset);279 processDirectory(metadata.getOrCreateDirectory(ExifSubIFDDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);280 continue;281 }282 case TAG_INTEROP_OFFSET: {283 final int subdirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset);284 processDirectory(metadata.getOrCreateDirectory(ExifInteropDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);285 continue;286 }287 case TAG_GPS_INFO_OFFSET: {288 final int subdirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset);289 processDirectory(metadata.getOrCreateDirectory(GpsDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);290 continue;291 }292 case TAG_MAKER_NOTE_OFFSET: {293 processMakerNote(tagValueOffset, processedDirectoryOffsets, tiffHeaderOffset, metadata, reader);294 continue;295 }296 default: {297 processTag(directory, tagType, tagValueOffset, componentCount, formatCode, reader);298 break;299 }300 }301 }302 303 // at the end of each IFD is an optional link to the next IFD304 final int finalTagOffset = calculateTagOffset(dirStartOffset, dirTagCount);305 int nextDirectoryOffset = reader.getInt32(finalTagOffset);306 if (nextDirectoryOffset != 0) {307 nextDirectoryOffset += tiffHeaderOffset;308 if (nextDirectoryOffset >= reader.getLength()) {309 // Last 4 bytes of IFD reference another IFD with an address that is out of bounds310 // Note this could have been caused by jhead 1.3 cropping too much311 return;312 } else if (nextDirectoryOffset < dirStartOffset) {313 // Last 4 bytes of IFD reference another IFD with an address that is before the start of this directory314 return;315 }316 // TODO in Exif, the only known 'follower' IFD is the thumbnail one, however this may not be the case317 final ExifThumbnailDirectory nextDirectory = metadata.getOrCreateDirectory(ExifThumbnailDirectory.class);318 processDirectory(nextDirectory, processedDirectoryOffsets, nextDirectoryOffset, tiffHeaderOffset, metadata, reader);319 }320 }321 322 private void processMakerNote(int subdirOffset, @NotNull Set<Integer> processedDirectoryOffsets, int tiffHeaderOffset, @NotNull final Metadata metadata, @NotNull BufferReader reader) throws BufferBoundsException323 {324 // Determine the camera model and makernote format325 Directory ifd0Directory = metadata.getDirectory(ExifIFD0Directory.class);326 327 if (ifd0Directory==null)328 return;329 330 String cameraModel = ifd0Directory.getString(ExifIFD0Directory.TAG_MAKE);331 332 //final String firstTwoChars = reader.getString(subdirOffset, 2);333 final String firstThreeChars = reader.getString(subdirOffset, 3);334 final String firstFourChars = reader.getString(subdirOffset, 4);335 final String firstFiveChars = reader.getString(subdirOffset, 5);336 final String firstSixChars = reader.getString(subdirOffset, 6);337 final String firstSevenChars = reader.getString(subdirOffset, 7);338 final String firstEightChars = reader.getString(subdirOffset, 8);339 final String firstTwelveChars = reader.getString(subdirOffset, 12);340 341 if ("OLYMP".equals(firstFiveChars) || "EPSON".equals(firstFiveChars) || "AGFA".equals(firstFourChars)) {342 // Olympus Makernote343 // Epson and Agfa use Olympus maker note standard: http://www.ozhiker.com/electronics/pjmt/jpeg_info/344 processDirectory(metadata.getOrCreateDirectory(OlympusMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 8, tiffHeaderOffset, metadata, reader);345 } else if (cameraModel != null && cameraModel.trim().toUpperCase().startsWith("NIKON")) {346 if ("Nikon".equals(firstFiveChars)) {347 /* There are two scenarios here:348 * Type 1: **349 * :0000: 4E 69 6B 6F 6E 00 01 00-05 00 02 00 02 00 06 00 Nikon...........350 * :0010: 00 00 EC 02 00 00 03 00-03 00 01 00 00 00 06 00 ................351 * Type 3: **352 * :0000: 4E 69 6B 6F 6E 00 02 00-00 00 4D 4D 00 2A 00 00 Nikon....MM.*...353 * :0010: 00 08 00 1E 00 01 00 07-00 00 00 04 30 32 30 30 ............0200354 */355 switch (reader.getUInt8(subdirOffset + 6)) {356 case 1:357 processDirectory(metadata.getOrCreateDirectory(NikonType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 8, tiffHeaderOffset, metadata, reader);358 break;359 case 2:360 processDirectory(metadata.getOrCreateDirectory(NikonType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 18, subdirOffset + 10, metadata, reader);361 break;362 default:363 ifd0Directory.addError("Unsupported Nikon makernote data ignored.");364 break;365 }366 } else {367 // The IFD begins with the first MakerNote byte (no ASCII name). This occurs with CoolPix 775, E990 and D1 models.368 processDirectory(metadata.getOrCreateDirectory(NikonType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);369 }370 } else if ("SONY CAM".equals(firstEightChars) || "SONY DSC".equals(firstEightChars)) {371 processDirectory(metadata.getOrCreateDirectory(SonyType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 12, tiffHeaderOffset, metadata, reader);372 } else if ("SIGMA\u0000\u0000\u0000".equals(firstEightChars) || "FOVEON\u0000\u0000".equals(firstEightChars)) {373 processDirectory(metadata.getOrCreateDirectory(SigmaMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 10, tiffHeaderOffset, metadata, reader);374 } else if ("SEMC MS\u0000\u0000\u0000\u0000\u0000".equals(firstTwelveChars)) {375 // force MM for this directory376 boolean isMotorola = reader.isMotorolaByteOrder();377 reader.setMotorolaByteOrder(true);378 // skip 12 byte header + 2 for "MM" + 6379 processDirectory(metadata.getOrCreateDirectory(SonyType6MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 20, tiffHeaderOffset, metadata, reader);380 reader.setMotorolaByteOrder(isMotorola);381 } else if ("KDK".equals(firstThreeChars)) {382 processDirectory(metadata.getOrCreateDirectory(KodakMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 20, tiffHeaderOffset, metadata, reader);383 } else if ("Canon".equalsIgnoreCase(cameraModel)) {384 processDirectory(metadata.getOrCreateDirectory(CanonMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);385 } else if (cameraModel != null && cameraModel.toUpperCase().startsWith("CASIO")) {386 if ("QVC\u0000\u0000\u0000".equals(firstSixChars))387 processDirectory(metadata.getOrCreateDirectory(CasioType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 6, tiffHeaderOffset, metadata, reader);388 else389 processDirectory(metadata.getOrCreateDirectory(CasioType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);390 } else if ("FUJIFILM".equals(firstEightChars) || "Fujifilm".equalsIgnoreCase(cameraModel)) {391 boolean byteOrderBefore = reader.isMotorolaByteOrder();392 // bug in fujifilm makernote ifd means we temporarily use Intel byte ordering393 reader.setMotorolaByteOrder(false);394 // the 4 bytes after "FUJIFILM" in the makernote point to the start of the makernote395 // IFD, though the offset is relative to the start of the makernote, not the TIFF396 // header (like everywhere else)397 int ifdStart = subdirOffset + reader.getInt32(subdirOffset + 8);398 processDirectory(metadata.getOrCreateDirectory(FujifilmMakernoteDirectory.class), processedDirectoryOffsets, ifdStart, tiffHeaderOffset, metadata, reader);399 reader.setMotorolaByteOrder(byteOrderBefore);400 } else if (cameraModel != null && cameraModel.toUpperCase().startsWith("MINOLTA")) {401 // Cases seen with the model starting with MINOLTA in capitals seem to have a valid Olympus makernote402 // area that commences immediately.403 processDirectory(metadata.getOrCreateDirectory(OlympusMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);404 } else if ("KYOCERA".equals(firstSevenChars)) {405 // http://www.ozhiker.com/electronics/pjmt/jpeg_info/kyocera_mn.html406 processDirectory(metadata.getOrCreateDirectory(KyoceraMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 22, tiffHeaderOffset, metadata, reader);407 } else if ("Panasonic\u0000\u0000\u0000".equals(reader.getString(subdirOffset, 12))) {408 // NON-Standard TIFF IFD Data using Panasonic Tags. There is no Next-IFD pointer after the IFD409 // Offsets are relative to the start of the TIFF header at the beginning of the EXIF segment410 // more information here: http://www.ozhiker.com/electronics/pjmt/jpeg_info/panasonic_mn.html411 processDirectory(metadata.getOrCreateDirectory(PanasonicMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 12, tiffHeaderOffset, metadata, reader);412 } else if ("AOC\u0000".equals(firstFourChars)) {413 // NON-Standard TIFF IFD Data using Casio Type 2 Tags414 // IFD has no Next-IFD pointer at end of IFD, and415 // Offsets are relative to the start of the current IFD tag, not the TIFF header416 // Observed for:417 // - Pentax ist D418 processDirectory(metadata.getOrCreateDirectory(CasioType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 6, subdirOffset, metadata, reader);419 } else if (cameraModel != null && (cameraModel.toUpperCase().startsWith("PENTAX") || cameraModel.toUpperCase().startsWith("ASAHI"))) {420 // NON-Standard TIFF IFD Data using Pentax Tags421 // IFD has no Next-IFD pointer at end of IFD, and422 // Offsets are relative to the start of the current IFD tag, not the TIFF header423 // Observed for:424 // - PENTAX Optio 330425 // - PENTAX Optio 430426 processDirectory(metadata.getOrCreateDirectory(PentaxMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, subdirOffset, metadata, reader);427 // } else if ("KC".equals(firstTwoChars) || "MINOL".equals(firstFiveChars) || "MLY".equals(firstThreeChars) || "+M+M+M+M".equals(firstEightChars)) {428 // // This Konica data is not understood. Header identified in accordance with information at this site:429 // // http://www.ozhiker.com/electronics/pjmt/jpeg_info/minolta_mn.html430 // // TODO add support for minolta/konica cameras431 // exifDirectory.addError("Unsupported Konica/Minolta data ignored.");432 } else {433 // TODO how to store makernote data when it's not from a supported camera model?434 // this is difficult as the starting offset is not known. we could look for it...435 }436 }437 438 private void processTag(@NotNull Directory directory, int tagType, int tagValueOffset, int componentCount, int formatCode, @NotNull final BufferReader reader) throws BufferBoundsException439 {440 // Directory simply stores raw values441 // The display side uses a Descriptor class per directory to turn the raw values into 'pretty' descriptions442 switch (formatCode) {443 case FMT_UNDEFINED:444 // this includes exif user comments445 directory.setByteArray(tagType, reader.getBytes(tagValueOffset, componentCount));446 break;447 case FMT_STRING:448 String string = reader.getNullTerminatedString(tagValueOffset, componentCount);449 directory.setString(tagType, string);450 /*451 // special handling for certain known tags, proposed by Yuri Binev but left out for now,452 // as it gives the false impression that the image was captured in the same timezone453 // in which the string is parsed454 if (tagType==ExifSubIFDDirectory.TAG_DATETIME ||455 tagType==ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL ||456 tagType==ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED) {457 String[] datePatterns = {458 "yyyy:MM:dd HH:mm:ss",459 "yyyy:MM:dd HH:mm",460 "yyyy-MM-dd HH:mm:ss",461 "yyyy-MM-dd HH:mm"};462 for (String datePattern : datePatterns) {463 try {464 DateFormat parser = new SimpleDateFormat(datePattern);465 Date date = parser.parse(string);466 directory.setDate(tagType, date);467 break;468 } catch (ParseException ex) {469 // simply try the next pattern470 }471 }472 }473 */474 break;475 case FMT_SRATIONAL:476 if (componentCount == 1) {477 directory.setRational(tagType, new Rational(reader.getInt32(tagValueOffset), reader.getInt32(tagValueOffset + 4)));478 } else if (componentCount > 1) {479 Rational[] rationals = new Rational[componentCount];480 for (int i = 0; i < componentCount; i++)481 rationals[i] = new Rational(reader.getInt32(tagValueOffset + (8 * i)), reader.getInt32(tagValueOffset + 4 + (8 * i)));482 directory.setRationalArray(tagType, rationals);483 }484 break;485 case FMT_URATIONAL:486 if (componentCount == 1) {487 directory.setRational(tagType, new Rational(reader.getUInt32(tagValueOffset), reader.getUInt32(tagValueOffset + 4)));488 } else if (componentCount > 1) {489 Rational[] rationals = new Rational[componentCount];490 for (int i = 0; i < componentCount; i++)491 rationals[i] = new Rational(reader.getUInt32(tagValueOffset + (8 * i)), reader.getUInt32(tagValueOffset + 4 + (8 * i)));492 directory.setRationalArray(tagType, rationals);493 }494 break;495 case FMT_SINGLE:496 if (componentCount == 1) {497 directory.setFloat(tagType, reader.getFloat32(tagValueOffset));498 } else {499 float[] floats = new float[componentCount];500 for (int i = 0; i < componentCount; i++)501 floats[i] = reader.getFloat32(tagValueOffset + (i * 4));502 directory.setFloatArray(tagType, floats);503 }504 break;505 case FMT_DOUBLE:506 if (componentCount == 1) {507 directory.setDouble(tagType, reader.getDouble64(tagValueOffset));508 } else {509 double[] doubles = new double[componentCount];510 for (int i = 0; i < componentCount; i++)511 doubles[i] = reader.getDouble64(tagValueOffset + (i * 4));512 directory.setDoubleArray(tagType, doubles);513 }514 break;515 516 //517 // Note that all integral types are stored as int32 internally (the largest supported by TIFF)518 //519 520 case FMT_SBYTE:521 if (componentCount == 1) {522 directory.setInt(tagType, reader.getInt8(tagValueOffset));523 } else {524 int[] bytes = new int[componentCount];525 for (int i = 0; i < componentCount; i++)526 bytes[i] = reader.getInt8(tagValueOffset + i);527 directory.setIntArray(tagType, bytes);528 }529 break;530 case FMT_BYTE:531 if (componentCount == 1) {532 directory.setInt(tagType, reader.getUInt8(tagValueOffset));533 } else {534 int[] bytes = new int[componentCount];535 for (int i = 0; i < componentCount; i++)536 bytes[i] = reader.getUInt8(tagValueOffset + i);537 directory.setIntArray(tagType, bytes);538 }539 break;540 case FMT_USHORT:541 if (componentCount == 1) {542 int i = reader.getUInt16(tagValueOffset);543 directory.setInt(tagType, i);544 } else {545 int[] ints = new int[componentCount];546 for (int i = 0; i < componentCount; i++)547 ints[i] = reader.getUInt16(tagValueOffset + (i * 2));548 directory.setIntArray(tagType, ints);549 }550 break;551 case FMT_SSHORT:552 if (componentCount == 1) {553 int i = reader.getInt16(tagValueOffset);554 directory.setInt(tagType, i);555 } else {556 int[] ints = new int[componentCount];557 for (int i = 0; i < componentCount; i++)558 ints[i] = reader.getInt16(tagValueOffset + (i * 2));559 directory.setIntArray(tagType, ints);560 }561 break;562 case FMT_SLONG:563 case FMT_ULONG:564 // NOTE 'long' in this case means 32 bit, not 64565 if (componentCount == 1) {566 int i = reader.getInt32(tagValueOffset);567 directory.setInt(tagType, i);568 } else {569 int[] ints = new int[componentCount];570 for (int i = 0; i < componentCount; i++)571 ints[i] = reader.getInt32(tagValueOffset + (i * 4));572 directory.setIntArray(tagType, ints);573 }574 break;575 default:576 directory.addError("Unknown format code " + formatCode + " for tag " + tagType);577 }578 }579 580 /**581 * Determine the offset at which a given InteropArray entry begins within the specified IFD.582 *583 * @param dirStartOffset the offset at which the IFD starts584 * @param entryNumber the zero-based entry number585 */586 private int calculateTagOffset(int dirStartOffset, int entryNumber)587 {588 // add 2 bytes for the tag count589 // each entry is 12 bytes, so we skip 12 * the number seen so far590 return dirStartOffset + 2 + (12 * entryNumber);591 }592 116 } -
trunk/src/com/drew/metadata/exif/ExifSubIFDDescriptor.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.exif; … … 32 32 import java.util.Map; 33 33 34 import static com.drew.metadata.exif.ExifSubIFDDirectory.*; 35 34 36 /** 35 * Provides human-readable string representations of tag values stored in a <code>ExifSubIFDDirectory</code>.37 * Provides human-readable string representations of tag values stored in a {@link ExifSubIFDDirectory}. 36 38 * 37 * @author Drew Noakes http ://drewnoakes.com39 * @author Drew Noakes https://drewnoakes.com 38 40 */ 39 41 public class ExifSubIFDDescriptor extends TagDescriptor<ExifSubIFDDirectory> … … 60 62 61 63 /** 62 * Returns a descriptive value of the thespecified tag for this image.64 * Returns a descriptive value of the specified tag for this image. 63 65 * Where possible, known values will be substituted here in place of the raw 64 66 * tokens actually kept in the Exif segment. If no substitution is 65 67 * available, the value provided by getString(int) will be returned. 68 * 66 69 * @param tagType the tag to find a description for 67 70 * @return a description of the image's value for the specified tag, or 68 71 * <code>null</code> if the tag hasn't been defined. 69 72 */ 73 @Override 70 74 @Nullable 71 75 public String getDescription(int tagType) 72 76 { 73 77 switch (tagType) { 74 case ExifSubIFDDirectory.TAG_NEW_SUBFILE_TYPE:78 case TAG_NEW_SUBFILE_TYPE: 75 79 return getNewSubfileTypeDescription(); 76 case ExifSubIFDDirectory.TAG_SUBFILE_TYPE:80 case TAG_SUBFILE_TYPE: 77 81 return getSubfileTypeDescription(); 78 case ExifSubIFDDirectory.TAG_THRESHOLDING:82 case TAG_THRESHOLDING: 79 83 return getThresholdingDescription(); 80 case ExifSubIFDDirectory.TAG_FILL_ORDER:84 case TAG_FILL_ORDER: 81 85 return getFillOrderDescription(); 82 case ExifSubIFDDirectory.TAG_EXPOSURE_TIME:86 case TAG_EXPOSURE_TIME: 83 87 return getExposureTimeDescription(); 84 case ExifSubIFDDirectory.TAG_SHUTTER_SPEED:88 case TAG_SHUTTER_SPEED: 85 89 return getShutterSpeedDescription(); 86 case ExifSubIFDDirectory.TAG_FNUMBER:90 case TAG_FNUMBER: 87 91 return getFNumberDescription(); 88 case ExifSubIFDDirectory.TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL:92 case TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL: 89 93 return getCompressedAverageBitsPerPixelDescription(); 90 case ExifSubIFDDirectory.TAG_SUBJECT_DISTANCE:94 case TAG_SUBJECT_DISTANCE: 91 95 return getSubjectDistanceDescription(); 92 case ExifSubIFDDirectory.TAG_METERING_MODE:96 case TAG_METERING_MODE: 93 97 return getMeteringModeDescription(); 94 case ExifSubIFDDirectory.TAG_WHITE_BALANCE:98 case TAG_WHITE_BALANCE: 95 99 return getWhiteBalanceDescription(); 96 case ExifSubIFDDirectory.TAG_FLASH:100 case TAG_FLASH: 97 101 return getFlashDescription(); 98 case ExifSubIFDDirectory.TAG_FOCAL_LENGTH:102 case TAG_FOCAL_LENGTH: 99 103 return getFocalLengthDescription(); 100 case ExifSubIFDDirectory.TAG_COLOR_SPACE:104 case TAG_COLOR_SPACE: 101 105 return getColorSpaceDescription(); 102 case ExifSubIFDDirectory.TAG_EXIF_IMAGE_WIDTH:106 case TAG_EXIF_IMAGE_WIDTH: 103 107 return getExifImageWidthDescription(); 104 case ExifSubIFDDirectory.TAG_EXIF_IMAGE_HEIGHT:108 case TAG_EXIF_IMAGE_HEIGHT: 105 109 return getExifImageHeightDescription(); 106 case ExifSubIFDDirectory.TAG_FOCAL_PLANE_UNIT:110 case TAG_FOCAL_PLANE_RESOLUTION_UNIT: 107 111 return getFocalPlaneResolutionUnitDescription(); 108 case ExifSubIFDDirectory.TAG_FOCAL_PLANE_X_RES:112 case TAG_FOCAL_PLANE_X_RESOLUTION: 109 113 return getFocalPlaneXResolutionDescription(); 110 case ExifSubIFDDirectory.TAG_FOCAL_PLANE_Y_RES:114 case TAG_FOCAL_PLANE_Y_RESOLUTION: 111 115 return getFocalPlaneYResolutionDescription(); 112 case ExifSubIFDDirectory.TAG_BITS_PER_SAMPLE:116 case TAG_BITS_PER_SAMPLE: 113 117 return getBitsPerSampleDescription(); 114 case ExifSubIFDDirectory.TAG_PHOTOMETRIC_INTERPRETATION:118 case TAG_PHOTOMETRIC_INTERPRETATION: 115 119 return getPhotometricInterpretationDescription(); 116 case ExifSubIFDDirectory.TAG_ROWS_PER_STRIP:120 case TAG_ROWS_PER_STRIP: 117 121 return getRowsPerStripDescription(); 118 case ExifSubIFDDirectory.TAG_STRIP_BYTE_COUNTS:122 case TAG_STRIP_BYTE_COUNTS: 119 123 return getStripByteCountsDescription(); 120 case ExifSubIFDDirectory.TAG_SAMPLES_PER_PIXEL:124 case TAG_SAMPLES_PER_PIXEL: 121 125 return getSamplesPerPixelDescription(); 122 case ExifSubIFDDirectory.TAG_PLANAR_CONFIGURATION:126 case TAG_PLANAR_CONFIGURATION: 123 127 return getPlanarConfigurationDescription(); 124 case ExifSubIFDDirectory.TAG_YCBCR_SUBSAMPLING:128 case TAG_YCBCR_SUBSAMPLING: 125 129 return getYCbCrSubsamplingDescription(); 126 case ExifSubIFDDirectory.TAG_EXPOSURE_PROGRAM:130 case TAG_EXPOSURE_PROGRAM: 127 131 return getExposureProgramDescription(); 128 case ExifSubIFDDirectory.TAG_APERTURE:132 case TAG_APERTURE: 129 133 return getApertureValueDescription(); 130 case ExifSubIFDDirectory.TAG_MAX_APERTURE:134 case TAG_MAX_APERTURE: 131 135 return getMaxApertureValueDescription(); 132 case ExifSubIFDDirectory.TAG_SENSING_METHOD:136 case TAG_SENSING_METHOD: 133 137 return getSensingMethodDescription(); 134 case ExifSubIFDDirectory.TAG_EXPOSURE_BIAS:138 case TAG_EXPOSURE_BIAS: 135 139 return getExposureBiasDescription(); 136 case ExifSubIFDDirectory.TAG_FILE_SOURCE:140 case TAG_FILE_SOURCE: 137 141 return getFileSourceDescription(); 138 case ExifSubIFDDirectory.TAG_SCENE_TYPE:142 case TAG_SCENE_TYPE: 139 143 return getSceneTypeDescription(); 140 case ExifSubIFDDirectory.TAG_COMPONENTS_CONFIGURATION:144 case TAG_COMPONENTS_CONFIGURATION: 141 145 return getComponentConfigurationDescription(); 142 case ExifSubIFDDirectory.TAG_EXIF_VERSION:146 case TAG_EXIF_VERSION: 143 147 return getExifVersionDescription(); 144 case ExifSubIFDDirectory.TAG_FLASHPIX_VERSION:148 case TAG_FLASHPIX_VERSION: 145 149 return getFlashPixVersionDescription(); 146 case ExifSubIFDDirectory.TAG_ISO_EQUIVALENT:150 case TAG_ISO_EQUIVALENT: 147 151 return getIsoEquivalentDescription(); 148 case ExifSubIFDDirectory.TAG_USER_COMMENT:152 case TAG_USER_COMMENT: 149 153 return getUserCommentDescription(); 150 case ExifSubIFDDirectory.TAG_CUSTOM_RENDERED:154 case TAG_CUSTOM_RENDERED: 151 155 return getCustomRenderedDescription(); 152 case ExifSubIFDDirectory.TAG_EXPOSURE_MODE:156 case TAG_EXPOSURE_MODE: 153 157 return getExposureModeDescription(); 154 case ExifSubIFDDirectory.TAG_WHITE_BALANCE_MODE:158 case TAG_WHITE_BALANCE_MODE: 155 159 return getWhiteBalanceModeDescription(); 156 case ExifSubIFDDirectory.TAG_DIGITAL_ZOOM_RATIO:160 case TAG_DIGITAL_ZOOM_RATIO: 157 161 return getDigitalZoomRatioDescription(); 158 case ExifSubIFDDirectory.TAG_35MM_FILM_EQUIV_FOCAL_LENGTH:162 case TAG_35MM_FILM_EQUIV_FOCAL_LENGTH: 159 163 return get35mmFilmEquivFocalLengthDescription(); 160 case ExifSubIFDDirectory.TAG_SCENE_CAPTURE_TYPE:164 case TAG_SCENE_CAPTURE_TYPE: 161 165 return getSceneCaptureTypeDescription(); 162 case ExifSubIFDDirectory.TAG_GAIN_CONTROL:166 case TAG_GAIN_CONTROL: 163 167 return getGainControlDescription(); 164 case ExifSubIFDDirectory.TAG_CONTRAST:168 case TAG_CONTRAST: 165 169 return getContrastDescription(); 166 case ExifSubIFDDirectory.TAG_SATURATION:170 case TAG_SATURATION: 167 171 return getSaturationDescription(); 168 case ExifSubIFDDirectory.TAG_SHARPNESS:172 case TAG_SHARPNESS: 169 173 return getSharpnessDescription(); 170 case ExifSubIFDDirectory.TAG_SUBJECT_DISTANCE_RANGE:174 case TAG_SUBJECT_DISTANCE_RANGE: 171 175 return getSubjectDistanceRangeDescription(); 172 176 default: … … 178 182 public String getNewSubfileTypeDescription() 179 183 { 180 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_NEW_SUBFILE_TYPE); 181 if (value==null) 182 return null; 183 switch (value) { 184 case 1: return "Full-resolution image"; 185 case 2: return "Reduced-resolution image"; 186 case 3: return "Single page of multi-page reduced-resolution image"; 187 case 4: return "Transparency mask"; 188 case 5: return "Transparency mask of reduced-resolution image"; 189 case 6: return "Transparency mask of multi-page image"; 190 case 7: return "Transparency mask of reduced-resolution multi-page image"; 191 default: 192 return "Unknown (" + value + ")"; 193 } 184 return getIndexedDescription(TAG_NEW_SUBFILE_TYPE, 1, 185 "Full-resolution image", 186 "Reduced-resolution image", 187 "Single page of multi-page reduced-resolution image", 188 "Transparency mask", 189 "Transparency mask of reduced-resolution image", 190 "Transparency mask of multi-page image", 191 "Transparency mask of reduced-resolution multi-page image" 192 ); 194 193 } 195 194 … … 197 196 public String getSubfileTypeDescription() 198 197 { 199 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SUBFILE_TYPE); 200 if (value==null) 201 return null; 202 switch (value) { 203 case 1: return "Full-resolution image"; 204 case 2: return "Reduced-resolution image"; 205 case 3: return "Single page of multi-page image"; 206 default: 207 return "Unknown (" + value + ")"; 208 } 198 return getIndexedDescription(TAG_SUBFILE_TYPE, 1, 199 "Full-resolution image", 200 "Reduced-resolution image", 201 "Single page of multi-page image" 202 ); 209 203 } 210 204 … … 212 206 public String getThresholdingDescription() 213 207 { 214 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_THRESHOLDING); 215 if (value==null) 216 return null; 217 switch (value) { 218 case 1: return "No dithering or halftoning"; 219 case 2: return "Ordered dither or halftone"; 220 case 3: return "Randomized dither"; 221 default: 222 return "Unknown (" + value + ")"; 223 } 208 return getIndexedDescription(TAG_THRESHOLDING, 1, 209 "No dithering or halftoning", 210 "Ordered dither or halftone", 211 "Randomized dither" 212 ); 224 213 } 225 214 … … 227 216 public String getFillOrderDescription() 228 217 { 229 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_FILL_ORDER); 230 if (value==null) 231 return null; 232 switch (value) { 233 case 1: return "Normal"; 234 case 2: return "Reversed"; 235 default: 236 return "Unknown (" + value + ")"; 237 } 218 return getIndexedDescription(TAG_FILL_ORDER, 1, 219 "Normal", 220 "Reversed" 221 ); 238 222 } 239 223 … … 241 225 public String getSubjectDistanceRangeDescription() 242 226 { 243 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SUBJECT_DISTANCE_RANGE); 244 if (value==null) 245 return null; 246 switch (value) { 247 case 0: return "Unknown"; 248 case 1: return "Macro"; 249 case 2: return "Close view"; 250 case 3: return "Distant view"; 251 default: 252 return "Unknown (" + value + ")"; 253 } 227 return getIndexedDescription(TAG_SUBJECT_DISTANCE_RANGE, 228 "Unknown", 229 "Macro", 230 "Close view", 231 "Distant view" 232 ); 254 233 } 255 234 … … 257 236 public String getSharpnessDescription() 258 237 { 259 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SHARPNESS); 260 if (value==null) 261 return null; 262 switch (value) { 263 case 0: return "None"; 264 case 1: return "Low"; 265 case 2: return "Hard"; 266 default: 267 return "Unknown (" + value + ")"; 268 } 238 return getIndexedDescription(TAG_SHARPNESS, 239 "None", 240 "Low", 241 "Hard" 242 ); 269 243 } 270 244 … … 272 246 public String getSaturationDescription() 273 247 { 274 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SATURATION); 275 if (value==null) 276 return null; 277 switch (value) { 278 case 0: return "None"; 279 case 1: return "Low saturation"; 280 case 2: return "High saturation"; 281 default: 282 return "Unknown (" + value + ")"; 283 } 248 return getIndexedDescription(TAG_SATURATION, 249 "None", 250 "Low saturation", 251 "High saturation" 252 ); 284 253 } 285 254 … … 287 256 public String getContrastDescription() 288 257 { 289 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_CONTRAST); 290 if (value==null) 291 return null; 292 switch (value) { 293 case 0: return "None"; 294 case 1: return "Soft"; 295 case 2: return "Hard"; 296 default: 297 return "Unknown (" + value + ")"; 298 } 258 return getIndexedDescription(TAG_CONTRAST, 259 "None", 260 "Soft", 261 "Hard" 262 ); 299 263 } 300 264 … … 302 266 public String getGainControlDescription() 303 267 { 304 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_GAIN_CONTROL); 305 if (value==null) 306 return null; 307 switch (value) { 308 case 0: return "None"; 309 case 1: return "Low gain up"; 310 case 2: return "Low gain down"; 311 case 3: return "High gain up"; 312 case 4: return "High gain down"; 313 default: 314 return "Unknown (" + value + ")"; 315 } 268 return getIndexedDescription(TAG_GAIN_CONTROL, 269 "None", 270 "Low gain up", 271 "Low gain down", 272 "High gain up", 273 "High gain down" 274 ); 316 275 } 317 276 … … 319 278 public String getSceneCaptureTypeDescription() 320 279 { 321 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SCENE_CAPTURE_TYPE); 322 if (value==null) 323 return null; 324 switch (value) { 325 case 0: return "Standard"; 326 case 1: return "Landscape"; 327 case 2: return "Portrait"; 328 case 3: return "Night scene"; 329 default: 330 return "Unknown (" + value + ")"; 331 } 280 return getIndexedDescription(TAG_SCENE_CAPTURE_TYPE, 281 "Standard", 282 "Landscape", 283 "Portrait", 284 "Night scene" 285 ); 332 286 } 333 287 … … 335 289 public String get35mmFilmEquivFocalLengthDescription() 336 290 { 337 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_35MM_FILM_EQUIV_FOCAL_LENGTH); 338 if (value==null) 339 return null; 340 if (value==0) 341 return "Unknown"; 342 else 343 return SimpleDecimalFormatter.format(value) + "mm"; 291 Integer value = _directory.getInteger(TAG_35MM_FILM_EQUIV_FOCAL_LENGTH); 292 return value == null 293 ? null 294 : value == 0 295 ? "Unknown" 296 : SimpleDecimalFormatter.format(value) + "mm"; 344 297 } 345 298 … … 347 300 public String getDigitalZoomRatioDescription() 348 301 { 349 Rational value = _directory.getRational( ExifSubIFDDirectory.TAG_DIGITAL_ZOOM_RATIO);350 if (value==null)351 return null;352 if (value.getNumerator()==0)353 return "Digital zoom not used.";354 returnSimpleDecimalFormatter.format(value.doubleValue());302 Rational value = _directory.getRational(TAG_DIGITAL_ZOOM_RATIO); 303 return value == null 304 ? null 305 : value.getNumerator() == 0 306 ? "Digital zoom not used." 307 : SimpleDecimalFormatter.format(value.doubleValue()); 355 308 } 356 309 … … 358 311 public String getWhiteBalanceModeDescription() 359 312 { 360 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_WHITE_BALANCE_MODE); 361 if (value==null) 362 return null; 363 switch (value) { 364 case 0: return "Auto white balance"; 365 case 1: return "Manual white balance"; 366 default: 367 return "Unknown (" + value + ")"; 368 } 313 return getIndexedDescription(TAG_WHITE_BALANCE_MODE, 314 "Auto white balance", 315 "Manual white balance" 316 ); 369 317 } 370 318 … … 372 320 public String getExposureModeDescription() 373 321 { 374 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_EXPOSURE_MODE); 375 if (value==null) 376 return null; 377 switch (value) { 378 case 0: return "Auto exposure"; 379 case 1: return "Manual exposure"; 380 case 2: return "Auto bracket"; 381 default: 382 return "Unknown (" + value + ")"; 383 } 322 return getIndexedDescription(TAG_EXPOSURE_MODE, 323 "Auto exposure", 324 "Manual exposure", 325 "Auto bracket" 326 ); 384 327 } 385 328 … … 387 330 public String getCustomRenderedDescription() 388 331 { 389 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_CUSTOM_RENDERED); 390 if (value==null) 391 return null; 392 switch (value) { 393 case 0: return "Normal process"; 394 case 1: return "Custom process"; 395 default: 396 return "Unknown (" + value + ")"; 397 } 332 return getIndexedDescription(TAG_CUSTOM_RENDERED, 333 "Normal process", 334 "Custom process" 335 ); 398 336 } 399 337 … … 401 339 public String getUserCommentDescription() 402 340 { 403 byte[] commentBytes = _directory.getByteArray( ExifSubIFDDirectory.TAG_USER_COMMENT);404 if (commentBytes ==null)341 byte[] commentBytes = _directory.getByteArray(TAG_USER_COMMENT); 342 if (commentBytes == null) 405 343 return null; 406 344 if (commentBytes.length == 0) … … 408 346 409 347 final Map<String, String> encodingMap = new HashMap<String, String>(); 410 encodingMap.put("ASCII", 411 encodingMap.put("UNICODE", 412 encodingMap.put("JIS", 348 encodingMap.put("ASCII", System.getProperty("file.encoding")); // Someone suggested "ISO-8859-1". 349 encodingMap.put("UNICODE", "UTF-16LE"); 350 encodingMap.put("JIS", "Shift-JIS"); // We assume this charset for now. Another suggestion is "JIS". 413 351 414 352 try { … … 442 380 { 443 381 // Have seen an exception here from files produced by ACDSEE that stored an int[] here with two values 444 Integer isoEquiv = _directory.getInteger(ExifSubIFDDirectory.TAG_ISO_EQUIVALENT); 445 if (isoEquiv==null) 446 return null; 382 Integer isoEquiv = _directory.getInteger(TAG_ISO_EQUIVALENT); 447 383 // There used to be a check here that multiplied ISO values < 50 by 200. 448 384 // Issue 36 shows a smart-phone image from a Samsung Galaxy S2 with ISO-40. 449 return Integer.toString(isoEquiv); 385 return isoEquiv != null 386 ? Integer.toString(isoEquiv) 387 : null; 450 388 } 451 389 … … 453 391 public String getExifVersionDescription() 454 392 { 455 int[] ints = _directory.getIntArray(ExifSubIFDDirectory.TAG_EXIF_VERSION); 456 if (ints==null) 457 return null; 458 return ExifSubIFDDescriptor.convertBytesToVersionString(ints, 2); 393 return getVersionBytesDescription(TAG_EXIF_VERSION, 2); 459 394 } 460 395 … … 462 397 public String getFlashPixVersionDescription() 463 398 { 464 int[] ints = _directory.getIntArray(ExifSubIFDDirectory.TAG_FLASHPIX_VERSION); 465 if (ints==null) 466 return null; 467 return ExifSubIFDDescriptor.convertBytesToVersionString(ints, 2); 399 return getVersionBytesDescription(TAG_FLASHPIX_VERSION, 2); 468 400 } 469 401 … … 471 403 public String getSceneTypeDescription() 472 404 { 473 Integer sceneType = _directory.getInteger(ExifSubIFDDirectory.TAG_SCENE_TYPE); 474 if (sceneType==null) 475 return null; 476 return sceneType == 1 477 ? "Directly photographed image" 478 : "Unknown (" + sceneType + ")"; 405 return getIndexedDescription(TAG_SCENE_TYPE, 406 1, 407 "Directly photographed image" 408 ); 479 409 } 480 410 … … 482 412 public String getFileSourceDescription() 483 413 { 484 Integer fileSource = _directory.getInteger(ExifSubIFDDirectory.TAG_FILE_SOURCE);485 if (fileSource==null)486 return null;487 return fileSource == 3488 ?"Digital Still Camera (DSC)"489 : "Unknown (" + fileSource + ")";414 return getIndexedDescription(TAG_FILE_SOURCE, 415 1, 416 "Film Scanner", 417 "Reflection Print Scanner", 418 "Digital Still Camera (DSC)" 419 ); 490 420 } 491 421 … … 493 423 public String getExposureBiasDescription() 494 424 { 495 Rational value = _directory.getRational( ExifSubIFDDirectory.TAG_EXPOSURE_BIAS);496 if (value ==null)425 Rational value = _directory.getRational(TAG_EXPOSURE_BIAS); 426 if (value == null) 497 427 return null; 498 428 return value.toSimpleString(true) + " EV"; … … 502 432 public String getMaxApertureValueDescription() 503 433 { 504 Double aperture = _directory.getDoubleObject( ExifSubIFDDirectory.TAG_MAX_APERTURE);505 if (aperture ==null)434 Double aperture = _directory.getDoubleObject(TAG_MAX_APERTURE); 435 if (aperture == null) 506 436 return null; 507 437 double fStop = PhotographicConversions.apertureToFStop(aperture); … … 512 442 public String getApertureValueDescription() 513 443 { 514 Double aperture = _directory.getDoubleObject( ExifSubIFDDirectory.TAG_APERTURE);515 if (aperture ==null)444 Double aperture = _directory.getDoubleObject(TAG_APERTURE); 445 if (aperture == null) 516 446 return null; 517 447 double fStop = PhotographicConversions.apertureToFStop(aperture); … … 522 452 public String getExposureProgramDescription() 523 453 { 524 // '1' means manual control, '2' program normal, '3' aperture priority, 525 // '4' shutter priority, '5' program creative (slow program), 526 // '6' program action(high-speed program), '7' portrait mode, '8' landscape mode. 527 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_EXPOSURE_PROGRAM); 528 if (value==null) 529 return null; 530 switch (value) { 531 case 1: return "Manual control"; 532 case 2: return "Program normal"; 533 case 3: return "Aperture priority"; 534 case 4: return "Shutter priority"; 535 case 5: return "Program creative (slow program)"; 536 case 6: return "Program action (high-speed program)"; 537 case 7: return "Portrait mode"; 538 case 8: return "Landscape mode"; 539 default: 540 return "Unknown program (" + value + ")"; 541 } 454 return getIndexedDescription(TAG_EXPOSURE_PROGRAM, 455 1, 456 "Manual control", 457 "Program normal", 458 "Aperture priority", 459 "Shutter priority", 460 "Program creative (slow program)", 461 "Program action (high-speed program)", 462 "Portrait mode", 463 "Landscape mode" 464 ); 542 465 } 543 466 … … 545 468 public String getYCbCrSubsamplingDescription() 546 469 { 547 int[] positions = _directory.getIntArray( ExifSubIFDDirectory.TAG_YCBCR_SUBSAMPLING);548 if (positions ==null)470 int[] positions = _directory.getIntArray(TAG_YCBCR_SUBSAMPLING); 471 if (positions == null) 549 472 return null; 550 473 if (positions[0] == 2 && positions[1] == 1) { … … 564 487 // pixel. If value is '2', Y/Cb/Cr value is separated and stored to Y plane/Cb plane/Cr 565 488 // plane format. 566 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_PLANAR_CONFIGURATION); 567 if (value==null) 568 return null; 569 switch (value) { 570 case 1: return "Chunky (contiguous for each subsampling pixel)"; 571 case 2: return "Separate (Y-plane/Cb-plane/Cr-plane format)"; 572 default: 573 return "Unknown configuration"; 574 } 489 return getIndexedDescription(TAG_PLANAR_CONFIGURATION, 490 1, 491 "Chunky (contiguous for each subsampling pixel)", 492 "Separate (Y-plane/Cb-plane/Cr-plane format)" 493 ); 575 494 } 576 495 … … 578 497 public String getSamplesPerPixelDescription() 579 498 { 580 String value = _directory.getString( ExifSubIFDDirectory.TAG_SAMPLES_PER_PIXEL);581 return value ==null ? null : value + " samples/pixel";499 String value = _directory.getString(TAG_SAMPLES_PER_PIXEL); 500 return value == null ? null : value + " samples/pixel"; 582 501 } 583 502 … … 585 504 public String getRowsPerStripDescription() 586 505 { 587 final String value = _directory.getString( ExifSubIFDDirectory.TAG_ROWS_PER_STRIP);588 return value ==null ? null : value + " rows/strip";506 final String value = _directory.getString(TAG_ROWS_PER_STRIP); 507 return value == null ? null : value + " rows/strip"; 589 508 } 590 509 … … 592 511 public String getStripByteCountsDescription() 593 512 { 594 final String value = _directory.getString( ExifSubIFDDirectory.TAG_STRIP_BYTE_COUNTS);595 return value ==null ? null : value + " bytes";513 final String value = _directory.getString(TAG_STRIP_BYTE_COUNTS); 514 return value == null ? null : value + " bytes"; 596 515 } 597 516 … … 600 519 { 601 520 // Shows the color space of the image data components 602 Integer value = _directory.getInteger( ExifSubIFDDirectory.TAG_PHOTOMETRIC_INTERPRETATION);603 if (value ==null)521 Integer value = _directory.getInteger(TAG_PHOTOMETRIC_INTERPRETATION); 522 if (value == null) 604 523 return null; 605 524 switch (value) { … … 626 545 public String getBitsPerSampleDescription() 627 546 { 628 String value = _directory.getString( ExifSubIFDDirectory.TAG_BITS_PER_SAMPLE);629 return value ==null ? null : value + " bits/component/pixel";547 String value = _directory.getString(TAG_BITS_PER_SAMPLE); 548 return value == null ? null : value + " bits/component/pixel"; 630 549 } 631 550 … … 633 552 public String getFocalPlaneXResolutionDescription() 634 553 { 635 Rational rational = _directory.getRational( ExifSubIFDDirectory.TAG_FOCAL_PLANE_X_RES);636 if (rational ==null)554 Rational rational = _directory.getRational(TAG_FOCAL_PLANE_X_RESOLUTION); 555 if (rational == null) 637 556 return null; 638 557 final String unit = getFocalPlaneResolutionUnitDescription(); 639 558 return rational.getReciprocal().toSimpleString(_allowDecimalRepresentationOfRationals) 640 + (unit ==null ? "" : " " + unit.toLowerCase());559 + (unit == null ? "" : " " + unit.toLowerCase()); 641 560 } 642 561 … … 644 563 public String getFocalPlaneYResolutionDescription() 645 564 { 646 Rational rational = _directory.getRational( ExifSubIFDDirectory.TAG_FOCAL_PLANE_Y_RES);647 if (rational ==null)565 Rational rational = _directory.getRational(TAG_FOCAL_PLANE_Y_RESOLUTION); 566 if (rational == null) 648 567 return null; 649 568 final String unit = getFocalPlaneResolutionUnitDescription(); 650 569 return rational.getReciprocal().toSimpleString(_allowDecimalRepresentationOfRationals) 651 + (unit ==null ? "" : " " + unit.toLowerCase());570 + (unit == null ? "" : " " + unit.toLowerCase()); 652 571 } 653 572 … … 655 574 public String getFocalPlaneResolutionUnitDescription() 656 575 { 657 // Unit of FocalPlaneXResolution/FocalPlaneYResolution. '1' means no-unit, 658 // '2' inch, '3' centimeter. 659 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_FOCAL_PLANE_UNIT); 660 if (value==null) 661 return null; 662 switch (value) { 663 case 1: return "(No unit)"; 664 case 2: return "Inches"; 665 case 3: return "cm"; 666 default: 667 return ""; 668 } 576 // Unit of FocalPlaneXResolution/FocalPlaneYResolution. 577 // '1' means no-unit, '2' inch, '3' centimeter. 578 return getIndexedDescription(TAG_FOCAL_PLANE_RESOLUTION_UNIT, 579 1, 580 "(No unit)", 581 "Inches", 582 "cm" 583 ); 669 584 } 670 585 … … 672 587 public String getExifImageWidthDescription() 673 588 { 674 final Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_EXIF_IMAGE_WIDTH); 675 if (value==null) 676 return null; 677 return value + " pixels"; 589 final Integer value = _directory.getInteger(TAG_EXIF_IMAGE_WIDTH); 590 return value == null ? null : value + " pixels"; 678 591 } 679 592 … … 681 594 public String getExifImageHeightDescription() 682 595 { 683 final Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_EXIF_IMAGE_HEIGHT); 684 if (value==null) 685 return null; 686 return value + " pixels"; 596 final Integer value = _directory.getInteger(TAG_EXIF_IMAGE_HEIGHT); 597 return value == null ? null : value + " pixels"; 687 598 } 688 599 … … 690 601 public String getColorSpaceDescription() 691 602 { 692 final Integer value = _directory.getInteger( ExifSubIFDDirectory.TAG_COLOR_SPACE);693 if (value ==null)603 final Integer value = _directory.getInteger(TAG_COLOR_SPACE); 604 if (value == null) 694 605 return null; 695 606 if (value == 1) … … 697 608 if (value == 65535) 698 609 return "Undefined"; 699 return "Unknown ";610 return "Unknown (" + value + ")"; 700 611 } 701 612 … … 703 614 public String getFocalLengthDescription() 704 615 { 705 Rational value = _directory.getRational( ExifSubIFDDirectory.TAG_FOCAL_LENGTH);706 if (value ==null)616 Rational value = _directory.getRational(TAG_FOCAL_LENGTH); 617 if (value == null) 707 618 return null; 708 619 java.text.DecimalFormat formatter = new DecimalFormat("0.0##"); … … 714 625 { 715 626 /* 716 * This is a bit mask.627 * This is a bit mask. 717 628 * 0 = flash fired 718 629 * 1 = return detected … … 724 635 */ 725 636 726 final Integer value = _directory.getInteger( ExifSubIFDDirectory.TAG_FLASH);727 728 if (value ==null)637 final Integer value = _directory.getInteger(TAG_FLASH); 638 639 if (value == null) 729 640 return null; 730 641 731 642 StringBuilder sb = new StringBuilder(); 732 643 733 if ((value & 0x1) !=0)644 if ((value & 0x1) != 0) 734 645 sb.append("Flash fired"); 735 646 else … … 737 648 738 649 // check if we're able to detect a return, before we mention it 739 if ((value & 0x4)!=0) 740 { 741 if ((value & 0x2)!=0) 650 if ((value & 0x4) != 0) { 651 if ((value & 0x2) != 0) 742 652 sb.append(", return detected"); 743 653 else … … 745 655 } 746 656 747 if ((value & 0x10) !=0)657 if ((value & 0x10) != 0) 748 658 sb.append(", auto"); 749 659 750 if ((value & 0x40) !=0)660 if ((value & 0x40) != 0) 751 661 sb.append(", red-eye reduction"); 752 662 … … 760 670 // '17' standard light A, '18' standard light B, '19' standard light C, '20' D55, 761 671 // '21' D65, '22' D75, '255' other. 762 final Integer value = _directory.getInteger( ExifSubIFDDirectory.TAG_WHITE_BALANCE);763 if (value ==null)672 final Integer value = _directory.getInteger(TAG_WHITE_BALANCE); 673 if (value == null) 764 674 return null; 765 675 switch (value) { … … 786 696 // '0' means unknown, '1' average, '2' center weighted average, '3' spot 787 697 // '4' multi-spot, '5' multi-segment, '6' partial, '255' other 788 Integer value = _directory.getInteger( ExifSubIFDDirectory.TAG_METERING_MODE);789 if (value ==null)698 Integer value = _directory.getInteger(TAG_METERING_MODE); 699 if (value == null) 790 700 return null; 791 701 switch (value) { … … 806 716 public String getSubjectDistanceDescription() 807 717 { 808 Rational value = _directory.getRational( ExifSubIFDDirectory.TAG_SUBJECT_DISTANCE);809 if (value ==null)718 Rational value = _directory.getRational(TAG_SUBJECT_DISTANCE); 719 if (value == null) 810 720 return null; 811 721 java.text.DecimalFormat formatter = new DecimalFormat("0.0##"); … … 816 726 public String getCompressedAverageBitsPerPixelDescription() 817 727 { 818 Rational value = _directory.getRational( ExifSubIFDDirectory.TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL);819 if (value ==null)728 Rational value = _directory.getRational(TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL); 729 if (value == null) 820 730 return null; 821 731 String ratio = value.toSimpleString(_allowDecimalRepresentationOfRationals); 822 if (value.isInteger() && value.intValue() == 1) { 823 return ratio + " bit/pixel"; 824 } else { 825 return ratio + " bits/pixel"; 826 } 732 return value.isInteger() && value.intValue() == 1 733 ? ratio + " bit/pixel" 734 : ratio + " bits/pixel"; 827 735 } 828 736 … … 830 738 public String getExposureTimeDescription() 831 739 { 832 String value = _directory.getString( ExifSubIFDDirectory.TAG_EXPOSURE_TIME);833 return value ==null ? null : value + " sec";740 String value = _directory.getString(TAG_EXPOSURE_TIME); 741 return value == null ? null : value + " sec"; 834 742 } 835 743 … … 847 755 // description (spotted bug using a Canon EOS 300D) 848 756 // thanks also to Gli Blr for spotting this bug 849 Float apexValue = _directory.getFloatObject( ExifSubIFDDirectory.TAG_SHUTTER_SPEED);850 if (apexValue ==null)851 return null; 852 if (apexValue <=1) {853 float apexPower = (float)(1 /(Math.exp(apexValue*Math.log(2))));757 Float apexValue = _directory.getFloatObject(TAG_SHUTTER_SPEED); 758 if (apexValue == null) 759 return null; 760 if (apexValue <= 1) { 761 float apexPower = (float)(1 / (Math.exp(apexValue * Math.log(2)))); 854 762 long apexPower10 = Math.round((double)apexPower * 10.0); 855 float fApexPower = (float) 763 float fApexPower = (float)apexPower10 / 10.0f; 856 764 return fApexPower + " sec"; 857 765 } else { 858 int apexPower = (int)((Math.exp(apexValue *Math.log(2))));766 int apexPower = (int)((Math.exp(apexValue * Math.log(2)))); 859 767 return "1/" + apexPower + " sec"; 860 768 } … … 879 787 return sb.toString(); 880 788 */ 881 882 789 } 883 790 … … 885 792 public String getFNumberDescription() 886 793 { 887 Rational value = _directory.getRational( ExifSubIFDDirectory.TAG_FNUMBER);888 if (value ==null)794 Rational value = _directory.getRational(TAG_FNUMBER); 795 if (value == null) 889 796 return null; 890 797 return "F" + SimpleDecimalFormatter.format(value.doubleValue()); … … 897 804 // '4' Three-chip color area sensor, '5' Color sequential area sensor 898 805 // '7' Trilinear sensor '8' Color sequential linear sensor, 'Other' reserved 899 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SENSING_METHOD); 900 if (value==null) 901 return null; 902 switch (value) { 903 case 1: return "(Not defined)"; 904 case 2: return "One-chip color area sensor"; 905 case 3: return "Two-chip color area sensor"; 906 case 4: return "Three-chip color area sensor"; 907 case 5: return "Color sequential area sensor"; 908 case 7: return "Trilinear sensor"; 909 case 8: return "Color sequential linear sensor"; 910 default: 911 return ""; 912 } 806 return getIndexedDescription(TAG_SENSING_METHOD, 807 1, 808 "(Not defined)", 809 "One-chip color area sensor", 810 "Two-chip color area sensor", 811 "Three-chip color area sensor", 812 "Color sequential area sensor", 813 null, 814 "Trilinear sensor", 815 "Color sequential linear sensor" 816 ); 913 817 } 914 818 … … 916 820 public String getComponentConfigurationDescription() 917 821 { 918 int[] components = _directory.getIntArray( ExifSubIFDDirectory.TAG_COMPONENTS_CONFIGURATION);919 if (components ==null)822 int[] components = _directory.getIntArray(TAG_COMPONENTS_CONFIGURATION); 823 if (components == null) 920 824 return null; 921 825 String[] componentStrings = {"", "Y", "Cb", "Cr", "R", "G", "B"}; -
trunk/src/com/drew/metadata/exif/ExifSubIFDDirectory.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.exif; … … 29 29 * Describes Exif tags from the SubIFD directory. 30 30 * 31 * @author Drew Noakes http ://drewnoakes.com31 * @author Drew Noakes https://drewnoakes.com 32 32 */ 33 33 public class ExifSubIFDDirectory extends Directory … … 133 133 /** 134 134 * Indicates the Opto-Electric Conversion Function (OECF) specified in ISO 14524. 135 * <p />135 * <p> 136 136 * OECF is the relationship between the camera optical input and the image values. 137 * <p />137 * <p> 138 138 * The values are: 139 139 * <ul> … … 260 260 */ 261 261 public static final int TAG_FOCAL_LENGTH = 0x920A; 262 263 /** 264 * This tag holds the Exif Makernote. Makernotes are free to be in any format, though they are often IFDs. 265 * To determine the format, we consider the starting bytes of the makernote itself and sometimes the 266 * camera model and make. 267 * <p> 268 * The component count for this tag includes all of the bytes needed for the makernote. 269 */ 270 public static final int TAG_MAKERNOTE = 0x927C; 271 262 272 public static final int TAG_USER_COMMENT = 0x9286; 273 263 274 public static final int TAG_SUBSECOND_TIME = 0x9290; 264 275 public static final int TAG_SUBSECOND_TIME_ORIGINAL = 0x9291; 265 276 public static final int TAG_SUBSECOND_TIME_DIGITIZED = 0x9292; 277 266 278 public static final int TAG_FLASHPIX_VERSION = 0xA000; 267 279 /** … … 274 286 public static final int TAG_EXIF_IMAGE_HEIGHT = 0xA003; 275 287 public static final int TAG_RELATED_SOUND_FILE = 0xA004; 276 public static final int TAG_FOCAL_PLANE_X_RES = 0xA20E; 277 public static final int TAG_FOCAL_PLANE_Y_RES = 0xA20F; 288 289 /** This tag is a pointer to the Exif Interop IFD. */ 290 public static final int TAG_INTEROP_OFFSET = 0xA005; 291 292 public static final int TAG_FOCAL_PLANE_X_RESOLUTION = 0xA20E; 293 public static final int TAG_FOCAL_PLANE_Y_RESOLUTION = 0xA20F; 278 294 /** 279 295 * Unit of FocalPlaneXResolution/FocalPlaneYResolution. '1' means no-unit, … … 285 301 * been changed to use value '2' but it doesn't match to actual value also. 286 302 */ 287 public static final int TAG_FOCAL_PLANE_ UNIT = 0xA210;303 public static final int TAG_FOCAL_PLANE_RESOLUTION_UNIT = 0xA210; 288 304 public static final int TAG_EXPOSURE_INDEX = 0xA215; 289 305 public static final int TAG_SENSING_METHOD = 0xA217; … … 512 528 _tagNameMap.put(0x0200, "JPEG Proc"); 513 529 _tagNameMap.put(TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL, "Compressed Bits Per Pixel"); 514 _tagNameMap.put( 0x927C, "Maker Note");515 _tagNameMap.put( 0xA005, "Interoperability Offset");530 _tagNameMap.put(TAG_MAKERNOTE, "Makernote"); 531 _tagNameMap.put(TAG_INTEROP_OFFSET, "Interoperability Offset"); 516 532 517 533 _tagNameMap.put(TAG_NEW_SUBFILE_TYPE, "New Subfile Type"); … … 586 602 _tagNameMap.put(TAG_SPATIAL_FREQ_RESPONSE_2, "Spatial Frequency Response"); 587 603 // 0x920E in TIFF/EP 588 _tagNameMap.put(TAG_FOCAL_PLANE_X_RES , "Focal Plane X Resolution");604 _tagNameMap.put(TAG_FOCAL_PLANE_X_RESOLUTION, "Focal Plane X Resolution"); 589 605 // 0x920F in TIFF/EP 590 _tagNameMap.put(TAG_FOCAL_PLANE_Y_RES , "Focal Plane Y Resolution");606 _tagNameMap.put(TAG_FOCAL_PLANE_Y_RESOLUTION, "Focal Plane Y Resolution"); 591 607 // 0x9210 in TIFF/EP 592 _tagNameMap.put(TAG_FOCAL_PLANE_ UNIT, "Focal Plane Resolution Unit");608 _tagNameMap.put(TAG_FOCAL_PLANE_RESOLUTION_UNIT, "Focal Plane Resolution Unit"); 593 609 // 0x9214 in TIFF/EP 594 610 _tagNameMap.put(TAG_SUBJECT_LOCATION_2, "Subject Location"); … … 614 630 _tagNameMap.put(TAG_SUBJECT_DISTANCE_RANGE, "Subject Distance Range"); 615 631 _tagNameMap.put(TAG_IMAGE_UNIQUE_ID, "Unique Image ID"); 616 632 617 633 _tagNameMap.put(TAG_CAMERA_OWNER_NAME, "Camera Owner Name"); 618 634 _tagNameMap.put(TAG_BODY_SERIAL_NUMBER, "Body Serial Number"); … … 634 650 } 635 651 652 @Override 636 653 @NotNull 637 654 public String getName() … … 640 657 } 641 658 659 @Override 642 660 @NotNull 643 661 protected HashMap<Integer, String> getTagNameMap() -
trunk/src/com/drew/metadata/exif/ExifThumbnailDescriptor.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 … … 27 27 import com.drew.metadata.TagDescriptor; 28 28 29 import static com.drew.metadata.exif.ExifThumbnailDirectory.*; 30 29 31 /** 30 * Provides human-readable string representations of tag values stored in a <code>ExifThumbnailDirectory</code>.31 * 32 * @author Drew Noakes http ://drewnoakes.com32 * Provides human-readable string representations of tag values stored in a {@link ExifThumbnailDirectory}. 33 * 34 * @author Drew Noakes https://drewnoakes.com 33 35 */ 34 36 public class ExifThumbnailDescriptor extends TagDescriptor<ExifThumbnailDirectory> … … 52 54 53 55 /** 54 * Returns a descriptive value of the thespecified tag for this image.56 * Returns a descriptive value of the specified tag for this image. 55 57 * Where possible, known values will be substituted here in place of the raw 56 58 * tokens actually kept in the Exif segment. If no substitution is 57 59 * available, the value provided by getString(int) will be returned. 60 * 58 61 * @param tagType the tag to find a description for 59 62 * @return a description of the image's value for the specified tag, or 60 63 * <code>null</code> if the tag hasn't been defined. 61 64 */ 65 @Override 62 66 @Nullable 63 67 public String getDescription(int tagType) 64 68 { 65 69 switch (tagType) { 66 case ExifThumbnailDirectory.TAG_ORIENTATION:70 case TAG_ORIENTATION: 67 71 return getOrientationDescription(); 68 case ExifThumbnailDirectory.TAG_RESOLUTION_UNIT:72 case TAG_RESOLUTION_UNIT: 69 73 return getResolutionDescription(); 70 case ExifThumbnailDirectory.TAG_YCBCR_POSITIONING:74 case TAG_YCBCR_POSITIONING: 71 75 return getYCbCrPositioningDescription(); 72 case ExifThumbnailDirectory.TAG_X_RESOLUTION:76 case TAG_X_RESOLUTION: 73 77 return getXResolutionDescription(); 74 case ExifThumbnailDirectory.TAG_Y_RESOLUTION:78 case TAG_Y_RESOLUTION: 75 79 return getYResolutionDescription(); 76 case ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET:80 case TAG_THUMBNAIL_OFFSET: 77 81 return getThumbnailOffsetDescription(); 78 case ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH:82 case TAG_THUMBNAIL_LENGTH: 79 83 return getThumbnailLengthDescription(); 80 case ExifThumbnailDirectory.TAG_THUMBNAIL_IMAGE_WIDTH:84 case TAG_THUMBNAIL_IMAGE_WIDTH: 81 85 return getThumbnailImageWidthDescription(); 82 case ExifThumbnailDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT:86 case TAG_THUMBNAIL_IMAGE_HEIGHT: 83 87 return getThumbnailImageHeightDescription(); 84 case ExifThumbnailDirectory.TAG_BITS_PER_SAMPLE:88 case TAG_BITS_PER_SAMPLE: 85 89 return getBitsPerSampleDescription(); 86 case ExifThumbnailDirectory.TAG_THUMBNAIL_COMPRESSION:90 case TAG_THUMBNAIL_COMPRESSION: 87 91 return getCompressionDescription(); 88 case ExifThumbnailDirectory.TAG_PHOTOMETRIC_INTERPRETATION:92 case TAG_PHOTOMETRIC_INTERPRETATION: 89 93 return getPhotometricInterpretationDescription(); 90 case ExifThumbnailDirectory.TAG_ROWS_PER_STRIP:94 case TAG_ROWS_PER_STRIP: 91 95 return getRowsPerStripDescription(); 92 case ExifThumbnailDirectory.TAG_STRIP_BYTE_COUNTS:96 case TAG_STRIP_BYTE_COUNTS: 93 97 return getStripByteCountsDescription(); 94 case ExifThumbnailDirectory.TAG_SAMPLES_PER_PIXEL:98 case TAG_SAMPLES_PER_PIXEL: 95 99 return getSamplesPerPixelDescription(); 96 case ExifThumbnailDirectory.TAG_PLANAR_CONFIGURATION:100 case TAG_PLANAR_CONFIGURATION: 97 101 return getPlanarConfigurationDescription(); 98 case ExifThumbnailDirectory.TAG_YCBCR_SUBSAMPLING:102 case TAG_YCBCR_SUBSAMPLING: 99 103 return getYCbCrSubsamplingDescription(); 100 case ExifThumbnailDirectory.TAG_REFERENCE_BLACK_WHITE:104 case TAG_REFERENCE_BLACK_WHITE: 101 105 return getReferenceBlackWhiteDescription(); 102 106 default: … … 108 112 public String getReferenceBlackWhiteDescription() 109 113 { 110 int[] ints = _directory.getIntArray( ExifThumbnailDirectory.TAG_REFERENCE_BLACK_WHITE);111 if (ints ==null)114 int[] ints = _directory.getIntArray(TAG_REFERENCE_BLACK_WHITE); 115 if (ints == null || ints.length < 6) 112 116 return null; 113 117 int blackR = ints[0]; … … 117 121 int blackB = ints[4]; 118 122 int whiteB = ints[5]; 119 return "[" + blackR + "," + blackG + "," + blackB + "] " + 120 "[" + whiteR + "," + whiteG + "," + whiteB + "]"; 123 return String.format("[%d,%d,%d] [%d,%d,%d]", blackR, blackG, blackB, whiteR, whiteG, whiteB); 121 124 } 122 125 … … 124 127 public String getYCbCrSubsamplingDescription() 125 128 { 126 int[] positions = _directory.getIntArray( ExifThumbnailDirectory.TAG_YCBCR_SUBSAMPLING);127 if (positions ==null || positions.length < 2)129 int[] positions = _directory.getIntArray(TAG_YCBCR_SUBSAMPLING); 130 if (positions == null || positions.length < 2) 128 131 return null; 129 132 if (positions[0] == 2 && positions[1] == 1) { … … 143 146 // pixel. If value is '2', Y/Cb/Cr value is separated and stored to Y plane/Cb plane/Cr 144 147 // plane format. 145 Integer value = _directory.getInteger(ExifThumbnailDirectory.TAG_PLANAR_CONFIGURATION); 146 if (value==null) 147 return null; 148 switch (value) { 149 case 1: return "Chunky (contiguous for each subsampling pixel)"; 150 case 2: return "Separate (Y-plane/Cb-plane/Cr-plane format)"; 151 default: 152 return "Unknown configuration"; 153 } 148 return getIndexedDescription(TAG_PLANAR_CONFIGURATION, 149 1, 150 "Chunky (contiguous for each subsampling pixel)", 151 "Separate (Y-plane/Cb-plane/Cr-plane format)" 152 ); 154 153 } 155 154 … … 157 156 public String getSamplesPerPixelDescription() 158 157 { 159 String value = _directory.getString( ExifThumbnailDirectory.TAG_SAMPLES_PER_PIXEL);160 return value ==null ? null : value + " samples/pixel";158 String value = _directory.getString(TAG_SAMPLES_PER_PIXEL); 159 return value == null ? null : value + " samples/pixel"; 161 160 } 162 161 … … 164 163 public String getRowsPerStripDescription() 165 164 { 166 final String value = _directory.getString( ExifThumbnailDirectory.TAG_ROWS_PER_STRIP);167 return value ==null ? null : value + " rows/strip";165 final String value = _directory.getString(TAG_ROWS_PER_STRIP); 166 return value == null ? null : value + " rows/strip"; 168 167 } 169 168 … … 171 170 public String getStripByteCountsDescription() 172 171 { 173 final String value = _directory.getString( ExifThumbnailDirectory.TAG_STRIP_BYTE_COUNTS);174 return value ==null ? null : value + " bytes";172 final String value = _directory.getString(TAG_STRIP_BYTE_COUNTS); 173 return value == null ? null : value + " bytes"; 175 174 } 176 175 … … 179 178 { 180 179 // Shows the color space of the image data components 181 Integer value = _directory.getInteger( ExifThumbnailDirectory.TAG_PHOTOMETRIC_INTERPRETATION);182 if (value ==null)180 Integer value = _directory.getInteger(TAG_PHOTOMETRIC_INTERPRETATION); 181 if (value == null) 183 182 return null; 184 183 switch (value) { … … 205 204 public String getCompressionDescription() 206 205 { 207 Integer value = _directory.getInteger( ExifThumbnailDirectory.TAG_THUMBNAIL_COMPRESSION);208 if (value ==null)206 Integer value = _directory.getInteger(TAG_THUMBNAIL_COMPRESSION); 207 if (value == null) 209 208 return null; 210 209 switch (value) { … … 244 243 public String getBitsPerSampleDescription() 245 244 { 246 String value = _directory.getString( ExifThumbnailDirectory.TAG_BITS_PER_SAMPLE);247 return value ==null ? null : value + " bits/component/pixel";245 String value = _directory.getString(TAG_BITS_PER_SAMPLE); 246 return value == null ? null : value + " bits/component/pixel"; 248 247 } 249 248 … … 251 250 public String getThumbnailImageWidthDescription() 252 251 { 253 String value = _directory.getString( ExifThumbnailDirectory.TAG_THUMBNAIL_IMAGE_WIDTH);254 return value ==null ? null : value + " pixels";252 String value = _directory.getString(TAG_THUMBNAIL_IMAGE_WIDTH); 253 return value == null ? null : value + " pixels"; 255 254 } 256 255 … … 258 257 public String getThumbnailImageHeightDescription() 259 258 { 260 String value = _directory.getString( ExifThumbnailDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT);261 return value ==null ? null : value + " pixels";259 String value = _directory.getString(TAG_THUMBNAIL_IMAGE_HEIGHT); 260 return value == null ? null : value + " pixels"; 262 261 } 263 262 … … 265 264 public String getThumbnailLengthDescription() 266 265 { 267 String value = _directory.getString( ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH);268 return value ==null ? null : value + " bytes";266 String value = _directory.getString(TAG_THUMBNAIL_LENGTH); 267 return value == null ? null : value + " bytes"; 269 268 } 270 269 … … 272 271 public String getThumbnailOffsetDescription() 273 272 { 274 String value = _directory.getString( ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET);275 return value ==null ? null : value + " bytes";273 String value = _directory.getString(TAG_THUMBNAIL_OFFSET); 274 return value == null ? null : value + " bytes"; 276 275 } 277 276 … … 279 278 public String getYResolutionDescription() 280 279 { 281 Rational value = _directory.getRational( ExifThumbnailDirectory.TAG_Y_RESOLUTION);282 if (value ==null)280 Rational value = _directory.getRational(TAG_Y_RESOLUTION); 281 if (value == null) 283 282 return null; 284 283 final String unit = getResolutionDescription(); 285 284 return value.toSimpleString(_allowDecimalRepresentationOfRationals) + 286 287 (unit==null ? "unit" : unit.toLowerCase());285 " dots per " + 286 (unit == null ? "unit" : unit.toLowerCase()); 288 287 } 289 288 … … 291 290 public String getXResolutionDescription() 292 291 { 293 Rational value = _directory.getRational( ExifThumbnailDirectory.TAG_X_RESOLUTION);294 if (value ==null)292 Rational value = _directory.getRational(TAG_X_RESOLUTION); 293 if (value == null) 295 294 return null; 296 295 final String unit = getResolutionDescription(); 297 296 return value.toSimpleString(_allowDecimalRepresentationOfRationals) + 298 299 (unit==null ? "unit" : unit.toLowerCase());297 " dots per " + 298 (unit == null ? "unit" : unit.toLowerCase()); 300 299 } 301 300 … … 303 302 public String getYCbCrPositioningDescription() 304 303 { 305 Integer value = _directory.getInteger(ExifThumbnailDirectory.TAG_YCBCR_POSITIONING); 306 if (value==null) 307 return null; 308 switch (value) { 309 case 1: return "Center of pixel array"; 310 case 2: return "Datum point"; 311 default: 312 return String.valueOf(value); 313 } 304 return getIndexedDescription(TAG_YCBCR_POSITIONING, 1, "Center of pixel array", "Datum point"); 314 305 } 315 306 … … 317 308 public String getOrientationDescription() 318 309 { 319 Integer value = _directory.getInteger(ExifThumbnailDirectory.TAG_ORIENTATION); 320 if (value==null) 321 return null; 322 switch (value) { 323 case 1: return "Top, left side (Horizontal / normal)"; 324 case 2: return "Top, right side (Mirror horizontal)"; 325 case 3: return "Bottom, right side (Rotate 180)"; 326 case 4: return "Bottom, left side (Mirror vertical)"; 327 case 5: return "Left side, top (Mirror horizontal and rotate 270 CW)"; 328 case 6: return "Right side, top (Rotate 90 CW)"; 329 case 7: return "Right side, bottom (Mirror horizontal and rotate 90 CW)"; 330 case 8: return "Left side, bottom (Rotate 270 CW)"; 331 default: 332 return String.valueOf(value); 333 } 310 return getIndexedDescription(TAG_ORIENTATION, 1, 311 "Top, left side (Horizontal / normal)", 312 "Top, right side (Mirror horizontal)", 313 "Bottom, right side (Rotate 180)", 314 "Bottom, left side (Mirror vertical)", 315 "Left side, top (Mirror horizontal and rotate 270 CW)", 316 "Right side, top (Rotate 90 CW)", 317 "Right side, bottom (Mirror horizontal and rotate 90 CW)", 318 "Left side, bottom (Rotate 270 CW)"); 334 319 } 335 320 … … 338 323 { 339 324 // '1' means no-unit, '2' means inch, '3' means centimeter. Default value is '2'(inch) 340 Integer value = _directory.getInteger(ExifThumbnailDirectory.TAG_RESOLUTION_UNIT); 341 if (value==null) 342 return null; 343 switch (value) { 344 case 1: return "(No unit)"; 345 case 2: return "Inch"; 346 case 3: return "cm"; 347 default: 348 return ""; 349 } 325 return getIndexedDescription(TAG_RESOLUTION_UNIT, 1, "(No unit)", "Inch", "cm"); 350 326 } 351 327 } -
trunk/src/com/drew/metadata/exif/ExifThumbnailDirectory.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 … … 34 34 * One of several Exif directories. Otherwise known as IFD1, this directory holds information about an embedded thumbnail image. 35 35 * 36 * @author Drew Noakes http ://drewnoakes.com36 * @author Drew Noakes https://drewnoakes.com 37 37 */ 38 38 public class ExifThumbnailDirectory extends Directory … … 57 57 * 7 = JPEG 58 58 * 8 = Adobe Deflate 59 * 9 = JBIG B& W59 * 9 = JBIG B&W 60 60 * 10 = JBIG Color 61 61 * 32766 = Next … … 98 98 public static final int TAG_PHOTOMETRIC_INTERPRETATION = 0x0106; 99 99 100 /** The position in the file of raster data. */ 100 /** 101 * The position in the file of raster data. 102 */ 101 103 public static final int TAG_STRIP_OFFSETS = 0x0111; 102 104 public static final int TAG_ORIENTATION = 0x0112; 103 /** Each pixel is composed of this many samples. */ 105 /** 106 * Each pixel is composed of this many samples. 107 */ 104 108 public static final int TAG_SAMPLES_PER_PIXEL = 0x0115; 105 /** The raster is codified by a single block of data holding this many rows. */ 109 /** 110 * The raster is codified by a single block of data holding this many rows. 111 */ 106 112 public static final int TAG_ROWS_PER_STRIP = 0x116; 107 /** The size of the raster data in bytes. */ 113 /** 114 * The size of the raster data in bytes. 115 */ 108 116 public static final int TAG_STRIP_BYTE_COUNTS = 0x0117; 109 117 /** … … 117 125 public static final int TAG_PLANAR_CONFIGURATION = 0x011C; 118 126 public static final int TAG_RESOLUTION_UNIT = 0x0128; 119 /** The offset to thumbnail image bytes. */ 127 /** 128 * The offset to thumbnail image bytes. 129 */ 120 130 public static final int TAG_THUMBNAIL_OFFSET = 0x0201; 121 /** The size of the thumbnail image data in bytes. */ 131 /** 132 * The size of the thumbnail image data in bytes. 133 */ 122 134 public static final int TAG_THUMBNAIL_LENGTH = 0x0202; 123 135 public static final int TAG_YCBCR_COEFFICIENTS = 0x0211; … … 129 141 protected static final HashMap<Integer, String> _tagNameMap = new HashMap<Integer, String>(); 130 142 131 static 132 { 143 static { 133 144 _tagNameMap.put(TAG_THUMBNAIL_IMAGE_WIDTH, "Thumbnail Image Width"); 134 145 _tagNameMap.put(TAG_THUMBNAIL_IMAGE_HEIGHT, "Thumbnail Image Height"); … … 161 172 } 162 173 174 @Override 163 175 @NotNull 164 176 public String getName() … … 167 179 } 168 180 181 @Override 169 182 @NotNull 170 183 protected HashMap<Integer, String> getTagNameMap() … … 193 206 byte[] data = _thumbnailData; 194 207 195 if (data ==null)208 if (data == null) 196 209 throw new MetadataException("No thumbnail data exists."); 197 210 … … 201 214 stream.write(data); 202 215 } finally { 203 if (stream !=null)216 if (stream != null) 204 217 stream.close(); 205 218 } -
trunk/src/com/drew/metadata/exif/GpsDescriptor.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.exif; … … 29 29 import java.text.DecimalFormat; 30 30 31 import static com.drew.metadata.exif.GpsDirectory.*; 32 31 33 /** 32 * Provides human-readable string representations of tag values stored in a <code>GpsDirectory</code>.33 * 34 * @author Drew Noakes http ://drewnoakes.com34 * Provides human-readable string representations of tag values stored in a {@link GpsDirectory}. 35 * 36 * @author Drew Noakes https://drewnoakes.com 35 37 */ 36 38 public class GpsDescriptor extends TagDescriptor<GpsDirectory> … … 41 43 } 42 44 45 @Override 43 46 @Nullable 44 47 public String getDescription(int tagType) 45 48 { 46 49 switch (tagType) { 47 case GpsDirectory.TAG_GPS_VERSION_ID:50 case TAG_VERSION_ID: 48 51 return getGpsVersionIdDescription(); 49 case GpsDirectory.TAG_GPS_ALTITUDE:52 case TAG_ALTITUDE: 50 53 return getGpsAltitudeDescription(); 51 case GpsDirectory.TAG_GPS_ALTITUDE_REF:54 case TAG_ALTITUDE_REF: 52 55 return getGpsAltitudeRefDescription(); 53 case GpsDirectory.TAG_GPS_STATUS:56 case TAG_STATUS: 54 57 return getGpsStatusDescription(); 55 case GpsDirectory.TAG_GPS_MEASURE_MODE:58 case TAG_MEASURE_MODE: 56 59 return getGpsMeasureModeDescription(); 57 case GpsDirectory.TAG_GPS_SPEED_REF:60 case TAG_SPEED_REF: 58 61 return getGpsSpeedRefDescription(); 59 case GpsDirectory.TAG_GPS_TRACK_REF:60 case GpsDirectory.TAG_GPS_IMG_DIRECTION_REF:61 case GpsDirectory.TAG_GPS_DEST_BEARING_REF:62 case TAG_TRACK_REF: 63 case TAG_IMG_DIRECTION_REF: 64 case TAG_DEST_BEARING_REF: 62 65 return getGpsDirectionReferenceDescription(tagType); 63 case GpsDirectory.TAG_GPS_TRACK:64 case GpsDirectory.TAG_GPS_IMG_DIRECTION:65 case GpsDirectory.TAG_GPS_DEST_BEARING:66 case TAG_TRACK: 67 case TAG_IMG_DIRECTION: 68 case TAG_DEST_BEARING: 66 69 return getGpsDirectionDescription(tagType); 67 case GpsDirectory.TAG_GPS_DEST_DISTANCE_REF:70 case TAG_DEST_DISTANCE_REF: 68 71 return getGpsDestinationReferenceDescription(); 69 case GpsDirectory.TAG_GPS_TIME_STAMP:72 case TAG_TIME_STAMP: 70 73 return getGpsTimeStampDescription(); 71 case GpsDirectory.TAG_GPS_LONGITUDE:74 case TAG_LONGITUDE: 72 75 // three rational numbers -- displayed in HH"MM"SS.ss 73 76 return getGpsLongitudeDescription(); 74 case GpsDirectory.TAG_GPS_LATITUDE:77 case TAG_LATITUDE: 75 78 // three rational numbers -- displayed in HH"MM"SS.ss 76 79 return getGpsLatitudeDescription(); 77 case GpsDirectory.TAG_GPS_DIFFERENTIAL:80 case TAG_DIFFERENTIAL: 78 81 return getGpsDifferentialDescription(); 79 82 default: … … 85 88 private String getGpsVersionIdDescription() 86 89 { 87 return convertBytesToVersionString(_directory.getIntArray(GpsDirectory.TAG_GPS_VERSION_ID), 1);90 return getVersionBytesDescription(TAG_VERSION_ID, 1); 88 91 } 89 92 … … 92 95 { 93 96 GeoLocation location = _directory.getGeoLocation(); 94 95 if (location == null) 96 return null; 97 98 return GeoLocation.decimalToDegreesMinutesSecondsString(location.getLatitude()); 97 return location == null ? null : GeoLocation.decimalToDegreesMinutesSecondsString(location.getLatitude()); 99 98 } 100 99 … … 103 102 { 104 103 GeoLocation location = _directory.getGeoLocation(); 105 106 if (location == null) 107 return null; 108 109 return GeoLocation.decimalToDegreesMinutesSecondsString(location.getLongitude()); 104 return location == null ? null : GeoLocation.decimalToDegreesMinutesSecondsString(location.getLongitude()); 110 105 } 111 106 … … 114 109 { 115 110 // time in hour, min, sec 116 int[] timeComponents = _directory.getIntArray(GpsDirectory.TAG_GPS_TIME_STAMP); 117 if (timeComponents==null) 118 return null; 119 StringBuilder description = new StringBuilder(); 120 description.append(timeComponents[0]); 121 description.append(":"); 122 description.append(timeComponents[1]); 123 description.append(":"); 124 description.append(timeComponents[2]); 125 description.append(" UTC"); 126 return description.toString(); 111 int[] timeComponents = _directory.getIntArray(TAG_TIME_STAMP); 112 return timeComponents == null ? null : String.format("%d:%d:%d UTC", timeComponents[0], timeComponents[1], timeComponents[2]); 127 113 } 128 114 … … 130 116 public String getGpsDestinationReferenceDescription() 131 117 { 132 final String value = _directory.getString( GpsDirectory.TAG_GPS_DEST_DISTANCE_REF);133 if (value ==null)118 final String value = _directory.getString(TAG_DEST_DISTANCE_REF); 119 if (value == null) 134 120 return null; 135 121 String distanceRef = value.trim(); … … 151 137 // provide a decimal version of rational numbers in the description, to avoid strings like "35334/199 degrees" 152 138 String value = angle != null 153 ? new DecimalFormat("0.##").format(angle.doubleValue()) 154 : _directory.getString(tagType); 155 if (value==null || value.trim().length()==0) 156 return null; 157 return value.trim() + " degrees"; 139 ? new DecimalFormat("0.##").format(angle.doubleValue()) 140 : _directory.getString(tagType); 141 return value == null || value.trim().length() == 0 ? null : value.trim() + " degrees"; 158 142 } 159 143 … … 162 146 { 163 147 final String value = _directory.getString(tagType); 164 if (value ==null)148 if (value == null) 165 149 return null; 166 150 String gpsDistRef = value.trim(); … … 177 161 public String getGpsSpeedRefDescription() 178 162 { 179 final String value = _directory.getString( GpsDirectory.TAG_GPS_SPEED_REF);180 if (value ==null)163 final String value = _directory.getString(TAG_SPEED_REF); 164 if (value == null) 181 165 return null; 182 166 String gpsSpeedRef = value.trim(); … … 195 179 public String getGpsMeasureModeDescription() 196 180 { 197 final String value = _directory.getString( GpsDirectory.TAG_GPS_MEASURE_MODE);198 if (value ==null)181 final String value = _directory.getString(TAG_MEASURE_MODE); 182 if (value == null) 199 183 return null; 200 184 String gpsSpeedMeasureMode = value.trim(); … … 211 195 public String getGpsStatusDescription() 212 196 { 213 final String value = _directory.getString( GpsDirectory.TAG_GPS_STATUS);214 if (value ==null)197 final String value = _directory.getString(TAG_STATUS); 198 if (value == null) 215 199 return null; 216 200 String gpsStatus = value.trim(); … … 227 211 public String getGpsAltitudeRefDescription() 228 212 { 229 Integer value = _directory.getInteger(GpsDirectory.TAG_GPS_ALTITUDE_REF); 230 if (value==null) 231 return null; 232 if (value == 0) 233 return "Sea level"; 234 if (value == 1) 235 return "Below sea level"; 236 return "Unknown (" + value + ")"; 213 return getIndexedDescription(TAG_ALTITUDE_REF, "Sea level", "Below sea level"); 237 214 } 238 215 … … 240 217 public String getGpsAltitudeDescription() 241 218 { 242 final Rational value = _directory.getRational(GpsDirectory.TAG_GPS_ALTITUDE); 243 if (value==null) 244 return null; 245 return value.intValue() + " metres"; 219 final Rational value = _directory.getRational(TAG_ALTITUDE); 220 return value == null ? null : value.intValue() + " metres"; 246 221 } 247 222 … … 249 224 public String getGpsDifferentialDescription() 250 225 { 251 final Integer value = _directory.getInteger(GpsDirectory.TAG_GPS_DIFFERENTIAL); 252 if (value==null) 253 return null; 254 if (value == 0) 255 return "No Correction"; 256 if (value == 1) 257 return "Differential Corrected"; 258 return "Unknown (" + value + ")"; 226 return getIndexedDescription(TAG_DIFFERENTIAL, "No Correction", "Differential Corrected"); 259 227 } 260 228 … … 263 231 { 264 232 GeoLocation location = _directory.getGeoLocation(); 265 266 if (location == null) 267 return null; 268 269 return location.toDMSString(); 233 return location == null ? null : location.toDMSString(); 270 234 } 271 235 } -
trunk/src/com/drew/metadata/exif/GpsDirectory.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.exif; … … 32 32 * Describes Exif tags that contain Global Positioning System (GPS) data. 33 33 * 34 * @author Drew Noakes http ://drewnoakes.com34 * @author Drew Noakes https://drewnoakes.com 35 35 */ 36 36 public class GpsDirectory extends Directory 37 37 { 38 38 /** GPS tag version GPSVersionID 0 0 BYTE 4 */ 39 public static final int TAG_ GPS_VERSION_ID = 0x0000;39 public static final int TAG_VERSION_ID = 0x0000; 40 40 /** North or South Latitude GPSLatitudeRef 1 1 ASCII 2 */ 41 public static final int TAG_ GPS_LATITUDE_REF = 0x0001;41 public static final int TAG_LATITUDE_REF = 0x0001; 42 42 /** Latitude GPSLatitude 2 2 RATIONAL 3 */ 43 public static final int TAG_ GPS_LATITUDE = 0x0002;43 public static final int TAG_LATITUDE = 0x0002; 44 44 /** East or West Longitude GPSLongitudeRef 3 3 ASCII 2 */ 45 public static final int TAG_ GPS_LONGITUDE_REF = 0x0003;45 public static final int TAG_LONGITUDE_REF = 0x0003; 46 46 /** Longitude GPSLongitude 4 4 RATIONAL 3 */ 47 public static final int TAG_ GPS_LONGITUDE = 0x0004;47 public static final int TAG_LONGITUDE = 0x0004; 48 48 /** Altitude reference GPSAltitudeRef 5 5 BYTE 1 */ 49 public static final int TAG_ GPS_ALTITUDE_REF = 0x0005;49 public static final int TAG_ALTITUDE_REF = 0x0005; 50 50 /** Altitude GPSAltitude 6 6 RATIONAL 1 */ 51 public static final int TAG_ GPS_ALTITUDE = 0x0006;51 public static final int TAG_ALTITUDE = 0x0006; 52 52 /** GPS time (atomic clock) GPSTimeStamp 7 7 RATIONAL 3 */ 53 public static final int TAG_ GPS_TIME_STAMP = 0x0007;53 public static final int TAG_TIME_STAMP = 0x0007; 54 54 /** GPS satellites used for measurement GPSSatellites 8 8 ASCII Any */ 55 public static final int TAG_ GPS_SATELLITES = 0x0008;55 public static final int TAG_SATELLITES = 0x0008; 56 56 /** GPS receiver status GPSStatus 9 9 ASCII 2 */ 57 public static final int TAG_ GPS_STATUS = 0x0009;57 public static final int TAG_STATUS = 0x0009; 58 58 /** GPS measurement mode GPSMeasureMode 10 A ASCII 2 */ 59 public static final int TAG_ GPS_MEASURE_MODE = 0x000A;59 public static final int TAG_MEASURE_MODE = 0x000A; 60 60 /** Measurement precision GPSDOP 11 B RATIONAL 1 */ 61 public static final int TAG_ GPS_DOP = 0x000B;61 public static final int TAG_DOP = 0x000B; 62 62 /** Speed unit GPSSpeedRef 12 C ASCII 2 */ 63 public static final int TAG_ GPS_SPEED_REF = 0x000C;63 public static final int TAG_SPEED_REF = 0x000C; 64 64 /** Speed of GPS receiver GPSSpeed 13 D RATIONAL 1 */ 65 public static final int TAG_ GPS_SPEED = 0x000D;65 public static final int TAG_SPEED = 0x000D; 66 66 /** Reference for direction of movement GPSTrackRef 14 E ASCII 2 */ 67 public static final int TAG_ GPS_TRACK_REF = 0x000E;67 public static final int TAG_TRACK_REF = 0x000E; 68 68 /** Direction of movement GPSTrack 15 F RATIONAL 1 */ 69 public static final int TAG_ GPS_TRACK = 0x000F;69 public static final int TAG_TRACK = 0x000F; 70 70 /** Reference for direction of image GPSImgDirectionRef 16 10 ASCII 2 */ 71 public static final int TAG_ GPS_IMG_DIRECTION_REF = 0x0010;71 public static final int TAG_IMG_DIRECTION_REF = 0x0010; 72 72 /** Direction of image GPSImgDirection 17 11 RATIONAL 1 */ 73 public static final int TAG_ GPS_IMG_DIRECTION = 0x0011;73 public static final int TAG_IMG_DIRECTION = 0x0011; 74 74 /** Geodetic survey data used GPSMapDatum 18 12 ASCII Any */ 75 public static final int TAG_ GPS_MAP_DATUM = 0x0012;75 public static final int TAG_MAP_DATUM = 0x0012; 76 76 /** Reference for latitude of destination GPSDestLatitudeRef 19 13 ASCII 2 */ 77 public static final int TAG_ GPS_DEST_LATITUDE_REF = 0x0013;77 public static final int TAG_DEST_LATITUDE_REF = 0x0013; 78 78 /** Latitude of destination GPSDestLatitude 20 14 RATIONAL 3 */ 79 public static final int TAG_ GPS_DEST_LATITUDE = 0x0014;79 public static final int TAG_DEST_LATITUDE = 0x0014; 80 80 /** Reference for longitude of destination GPSDestLongitudeRef 21 15 ASCII 2 */ 81 public static final int TAG_ GPS_DEST_LONGITUDE_REF = 0x0015;81 public static final int TAG_DEST_LONGITUDE_REF = 0x0015; 82 82 /** Longitude of destination GPSDestLongitude 22 16 RATIONAL 3 */ 83 public static final int TAG_ GPS_DEST_LONGITUDE = 0x0016;83 public static final int TAG_DEST_LONGITUDE = 0x0016; 84 84 /** Reference for bearing of destination GPSDestBearingRef 23 17 ASCII 2 */ 85 public static final int TAG_ GPS_DEST_BEARING_REF = 0x0017;85 public static final int TAG_DEST_BEARING_REF = 0x0017; 86 86 /** Bearing of destination GPSDestBearing 24 18 RATIONAL 1 */ 87 public static final int TAG_ GPS_DEST_BEARING = 0x0018;87 public static final int TAG_DEST_BEARING = 0x0018; 88 88 /** Reference for distance to destination GPSDestDistanceRef 25 19 ASCII 2 */ 89 public static final int TAG_ GPS_DEST_DISTANCE_REF = 0x0019;89 public static final int TAG_DEST_DISTANCE_REF = 0x0019; 90 90 /** Distance to destination GPSDestDistance 26 1A RATIONAL 1 */ 91 public static final int TAG_ GPS_DEST_DISTANCE = 0x001A;91 public static final int TAG_DEST_DISTANCE = 0x001A; 92 92 93 93 /** Values of "GPS", "CELLID", "WLAN" or "MANUAL" by the EXIF spec. */ 94 public static final int TAG_ GPS_PROCESSING_METHOD = 0x001B;95 public static final int TAG_ GPS_AREA_INFORMATION = 0x001C;96 public static final int TAG_ GPS_DATE_STAMP = 0x001D;97 public static final int TAG_ GPS_DIFFERENTIAL = 0x001E;94 public static final int TAG_PROCESSING_METHOD = 0x001B; 95 public static final int TAG_AREA_INFORMATION = 0x001C; 96 public static final int TAG_DATE_STAMP = 0x001D; 97 public static final int TAG_DIFFERENTIAL = 0x001E; 98 98 99 99 @NotNull … … 102 102 static 103 103 { 104 _tagNameMap.put(TAG_ GPS_VERSION_ID, "GPS Version ID");105 _tagNameMap.put(TAG_ GPS_LATITUDE_REF, "GPS Latitude Ref");106 _tagNameMap.put(TAG_ GPS_LATITUDE, "GPS Latitude");107 _tagNameMap.put(TAG_ GPS_LONGITUDE_REF, "GPS Longitude Ref");108 _tagNameMap.put(TAG_ GPS_LONGITUDE, "GPS Longitude");109 _tagNameMap.put(TAG_ GPS_ALTITUDE_REF, "GPS Altitude Ref");110 _tagNameMap.put(TAG_ GPS_ALTITUDE, "GPS Altitude");111 _tagNameMap.put(TAG_ GPS_TIME_STAMP, "GPS Time-Stamp");112 _tagNameMap.put(TAG_ GPS_SATELLITES, "GPS Satellites");113 _tagNameMap.put(TAG_ GPS_STATUS, "GPS Status");114 _tagNameMap.put(TAG_ GPS_MEASURE_MODE, "GPS Measure Mode");115 _tagNameMap.put(TAG_ GPS_DOP, "GPS DOP");116 _tagNameMap.put(TAG_ GPS_SPEED_REF, "GPS Speed Ref");117 _tagNameMap.put(TAG_ GPS_SPEED, "GPS Speed");118 _tagNameMap.put(TAG_ GPS_TRACK_REF, "GPS Track Ref");119 _tagNameMap.put(TAG_ GPS_TRACK, "GPS Track");120 _tagNameMap.put(TAG_ GPS_IMG_DIRECTION_REF, "GPS Img Direction Ref");121 _tagNameMap.put(TAG_ GPS_IMG_DIRECTION, "GPS Img Direction");122 _tagNameMap.put(TAG_ GPS_MAP_DATUM, "GPS Map Datum");123 _tagNameMap.put(TAG_ GPS_DEST_LATITUDE_REF, "GPS Dest Latitude Ref");124 _tagNameMap.put(TAG_ GPS_DEST_LATITUDE, "GPS Dest Latitude");125 _tagNameMap.put(TAG_ GPS_DEST_LONGITUDE_REF, "GPS Dest Longitude Ref");126 _tagNameMap.put(TAG_ GPS_DEST_LONGITUDE, "GPS Dest Longitude");127 _tagNameMap.put(TAG_ GPS_DEST_BEARING_REF, "GPS Dest Bearing Ref");128 _tagNameMap.put(TAG_ GPS_DEST_BEARING, "GPS Dest Bearing");129 _tagNameMap.put(TAG_ GPS_DEST_DISTANCE_REF, "GPS Dest Distance Ref");130 _tagNameMap.put(TAG_ GPS_DEST_DISTANCE, "GPS Dest Distance");131 _tagNameMap.put(TAG_ GPS_PROCESSING_METHOD, "GPS Processing Method");132 _tagNameMap.put(TAG_ GPS_AREA_INFORMATION, "GPS Area Information");133 _tagNameMap.put(TAG_ GPS_DATE_STAMP, "GPS Date Stamp");134 _tagNameMap.put(TAG_ GPS_DIFFERENTIAL, "GPS Differential");104 _tagNameMap.put(TAG_VERSION_ID, "GPS Version ID"); 105 _tagNameMap.put(TAG_LATITUDE_REF, "GPS Latitude Ref"); 106 _tagNameMap.put(TAG_LATITUDE, "GPS Latitude"); 107 _tagNameMap.put(TAG_LONGITUDE_REF, "GPS Longitude Ref"); 108 _tagNameMap.put(TAG_LONGITUDE, "GPS Longitude"); 109 _tagNameMap.put(TAG_ALTITUDE_REF, "GPS Altitude Ref"); 110 _tagNameMap.put(TAG_ALTITUDE, "GPS Altitude"); 111 _tagNameMap.put(TAG_TIME_STAMP, "GPS Time-Stamp"); 112 _tagNameMap.put(TAG_SATELLITES, "GPS Satellites"); 113 _tagNameMap.put(TAG_STATUS, "GPS Status"); 114 _tagNameMap.put(TAG_MEASURE_MODE, "GPS Measure Mode"); 115 _tagNameMap.put(TAG_DOP, "GPS DOP"); 116 _tagNameMap.put(TAG_SPEED_REF, "GPS Speed Ref"); 117 _tagNameMap.put(TAG_SPEED, "GPS Speed"); 118 _tagNameMap.put(TAG_TRACK_REF, "GPS Track Ref"); 119 _tagNameMap.put(TAG_TRACK, "GPS Track"); 120 _tagNameMap.put(TAG_IMG_DIRECTION_REF, "GPS Img Direction Ref"); 121 _tagNameMap.put(TAG_IMG_DIRECTION, "GPS Img Direction"); 122 _tagNameMap.put(TAG_MAP_DATUM, "GPS Map Datum"); 123 _tagNameMap.put(TAG_DEST_LATITUDE_REF, "GPS Dest Latitude Ref"); 124 _tagNameMap.put(TAG_DEST_LATITUDE, "GPS Dest Latitude"); 125 _tagNameMap.put(TAG_DEST_LONGITUDE_REF, "GPS Dest Longitude Ref"); 126 _tagNameMap.put(TAG_DEST_LONGITUDE, "GPS Dest Longitude"); 127 _tagNameMap.put(TAG_DEST_BEARING_REF, "GPS Dest Bearing Ref"); 128 _tagNameMap.put(TAG_DEST_BEARING, "GPS Dest Bearing"); 129 _tagNameMap.put(TAG_DEST_DISTANCE_REF, "GPS Dest Distance Ref"); 130 _tagNameMap.put(TAG_DEST_DISTANCE, "GPS Dest Distance"); 131 _tagNameMap.put(TAG_PROCESSING_METHOD, "GPS Processing Method"); 132 _tagNameMap.put(TAG_AREA_INFORMATION, "GPS Area Information"); 133 _tagNameMap.put(TAG_DATE_STAMP, "GPS Date Stamp"); 134 _tagNameMap.put(TAG_DIFFERENTIAL, "GPS Differential"); 135 135 } 136 136 … … 140 140 } 141 141 142 @Override 142 143 @NotNull 143 144 public String getName() … … 146 147 } 147 148 149 @Override 148 150 @NotNull 149 151 protected HashMap<Integer, String> getTagNameMap() … … 161 163 public GeoLocation getGeoLocation() 162 164 { 163 Rational[] latitudes = getRationalArray(GpsDirectory.TAG_ GPS_LATITUDE);164 Rational[] longitudes = getRationalArray(GpsDirectory.TAG_ GPS_LONGITUDE);165 String latitudeRef = getString(GpsDirectory.TAG_ GPS_LATITUDE_REF);166 String longitudeRef = getString(GpsDirectory.TAG_ GPS_LONGITUDE_REF);165 Rational[] latitudes = getRationalArray(GpsDirectory.TAG_LATITUDE); 166 Rational[] longitudes = getRationalArray(GpsDirectory.TAG_LONGITUDE); 167 String latitudeRef = getString(GpsDirectory.TAG_LATITUDE_REF); 168 String longitudeRef = getString(GpsDirectory.TAG_LONGITUDE_REF); 167 169 168 170 // Make sure we have the required values -
trunk/src/com/drew/metadata/iptc/IptcDescriptor.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.iptc; … … 27 27 28 28 /** 29 * Provides human-readable string representations of tag values stored in a <code>IptcDirectory</code>.30 * <p />29 * Provides human-readable string representations of tag values stored in a {@link IptcDirectory}. 30 * <p> 31 31 * As the IPTC directory already stores values as strings, this class simply returns the tag's value. 32 32 * 33 * @author Drew Noakes http ://drewnoakes.com33 * @author Drew Noakes https://drewnoakes.com 34 34 */ 35 35 public class IptcDescriptor extends TagDescriptor<IptcDirectory> … … 40 40 } 41 41 42 @Override 42 43 @Nullable 43 44 public String getDescription(int tagType) -
trunk/src/com/drew/metadata/iptc/IptcDirectory.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.iptc; … … 32 32 * Describes tags used by the International Press Telecommunications Council (IPTC) metadata format. 33 33 * 34 * @author Drew Noakes http ://drewnoakes.com34 * @author Drew Noakes https://drewnoakes.com 35 35 */ 36 36 public class IptcDirectory extends Directory … … 211 211 } 212 212 213 @Override 213 214 @NotNull 214 215 public String getName() 215 216 { 216 return "Iptc"; 217 } 218 217 return "IPTC"; 218 } 219 220 @Override 219 221 @NotNull 220 222 protected HashMap<Integer, String> getTagNameMap() -
trunk/src/com/drew/metadata/iptc/IptcReader.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.iptc; 22 22 23 import com.drew.lang.BufferBoundsException; 24 import com.drew.lang.BufferReader; 23 import com.drew.imaging.jpeg.JpegSegmentMetadataReader; 24 import com.drew.imaging.jpeg.JpegSegmentType; 25 import com.drew.lang.SequentialByteArrayReader; 26 import com.drew.lang.SequentialReader; 25 27 import com.drew.lang.annotations.NotNull; 26 28 import com.drew.metadata.Directory; 27 29 import com.drew.metadata.Metadata; 28 import com.drew.metadata.MetadataReader; 29 30 31 import java.io.IOException; 32 import java.util.Arrays; 30 33 import java.util.Date; 31 34 32 35 /** 33 * Decodes IPTC binary data, populating a <code>Metadata</code> object with tag values in an <code>IptcDirectory</code>. 34 * 35 * @author Drew Noakes http://drewnoakes.com 36 * Decodes IPTC binary data, populating a {@link Metadata} object with tag values in an {@link IptcDirectory}. 37 * <p> 38 * http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf 39 * 40 * @author Drew Noakes https://drewnoakes.com 36 41 */ 37 public class IptcReader implements MetadataReader42 public class IptcReader implements JpegSegmentMetadataReader 38 43 { 39 44 // TODO consider breaking the IPTC section up into multiple directories and providing segregation of each IPTC directory … … 52 57 */ 53 58 54 /** Performs the IPTC data extraction, adding found values to the specified instance of <code>Metadata</code>. */ 55 public void extract(@NotNull final BufferReader reader, @NotNull final Metadata metadata) 59 @NotNull 60 public Iterable<JpegSegmentType> getSegmentTypes() 61 { 62 return Arrays.asList(JpegSegmentType.APPD); 63 } 64 65 public boolean canProcess(@NotNull byte[] segmentBytes, @NotNull JpegSegmentType segmentType) 66 { 67 // Check whether the first byte resembles 68 return segmentBytes.length != 0 && segmentBytes[0] == 0x1c; 69 } 70 71 public void extract(@NotNull byte[] segmentBytes, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType) 72 { 73 extract(new SequentialByteArrayReader(segmentBytes), metadata, segmentBytes.length); 74 } 75 76 /** 77 * Performs the IPTC data extraction, adding found values to the specified instance of {@link Metadata}. 78 */ 79 public void extract(@NotNull final SequentialReader reader, @NotNull final Metadata metadata, long length) 56 80 { 57 81 IptcDirectory directory = metadata.getOrCreateDirectory(IptcDirectory.class); … … 59 83 int offset = 0; 60 84 61 /*62 // find start-of-segment marker (potentially need to skip some ASCII photoshop header info)63 try {64 while (offset < data.length - 1 && reader.getUInt16(offset) != 0x1c01 && reader.getUInt16(offset) != 0x1c02)65 offset++;66 } catch (BufferBoundsException e) {67 directory.addError("Couldn't find start of IPTC data (invalid segment)");68 return;69 }70 */71 72 85 // for each tag 73 while (offset < reader.getLength()) {86 while (offset < length) { 74 87 75 88 // identifies start of a tag 76 89 short startByte; 77 90 try { 78 startByte = reader.getUInt8(offset); 79 } catch (BufferBoundsException e) { 91 startByte = reader.getUInt8(); 92 offset++; 93 } catch (IOException e) { 80 94 directory.addError("Unable to read starting byte of IPTC tag"); 81 break;95 return; 82 96 } 83 97 84 98 if (startByte != 0x1c) { 85 directory.addError("Invalid start to IPTC tag"); 86 break; 99 // NOTE have seen images where there was one extra byte at the end, giving 100 // offset==length at this point, which is not worth logging as an error. 101 if (offset != length) 102 directory.addError("Invalid IPTC tag marker at offset " + (offset - 1) + ". Expected '0x1c' but got '0x" + Integer.toHexString(startByte) + "'."); 103 return; 87 104 } 88 105 89 106 // we need at least five bytes left to read a tag 90 if (offset + 5 >= reader.getLength()) {107 if (offset + 5 >= length) { 91 108 directory.addError("Too few bytes remain for a valid IPTC tag"); 92 break; 93 } 94 95 offset++; 109 return; 110 } 96 111 97 112 int directoryType; … … 99 114 int tagByteCount; 100 115 try { 101 directoryType = reader.getUInt8(offset++); 102 tagType = reader.getUInt8(offset++); 103 tagByteCount = reader.getUInt16(offset); 104 offset += 2; 105 } catch (BufferBoundsException e) { 116 directoryType = reader.getUInt8(); 117 tagType = reader.getUInt8(); 118 // TODO support Extended DataSet Tag (see 1.5(c), p14, IPTC-IIMV4.2.pdf) 119 tagByteCount = reader.getUInt16(); 120 offset += 4; 121 } catch (IOException e) { 106 122 directory.addError("IPTC data segment ended mid-way through tag descriptor"); 107 123 return; 108 124 } 109 125 110 if (offset + tagByteCount > reader.getLength()) {126 if (offset + tagByteCount > length) { 111 127 directory.addError("Data for tag extends beyond end of IPTC segment"); 128 return; 129 } 130 131 try { 132 processTag(reader, directory, directoryType, tagType, tagByteCount); 133 } catch (IOException e) { 134 directory.addError("Error processing IPTC tag"); 135 return; 136 } 137 138 offset += tagByteCount; 139 } 140 } 141 142 private void processTag(@NotNull SequentialReader reader, @NotNull Directory directory, int directoryType, int tagType, int tagByteCount) throws IOException 143 { 144 int tagIdentifier = tagType | (directoryType << 8); 145 146 // Some images have been seen that specify a zero byte tag, which cannot be of much use. 147 // We elect here to completely ignore the tag. The IPTC specification doesn't mention 148 // anything about the interpretation of this situation. 149 // https://raw.githubusercontent.com/wiki/drewnoakes/metadata-extractor/docs/IPTC-IIMV4.2.pdf 150 if (tagByteCount == 0) { 151 directory.setString(tagIdentifier, ""); 152 return; 153 } 154 155 String string = null; 156 157 switch (tagIdentifier) { 158 case IptcDirectory.TAG_CODED_CHARACTER_SET: 159 byte[] bytes = reader.getBytes(tagByteCount); 160 String charset = Iso2022Converter.convertISO2022CharsetToJavaCharset(bytes); 161 if (charset == null) { 162 // Unable to determine the charset, so fall through and treat tag as a regular string 163 string = new String(bytes); 164 break; 165 } 166 directory.setString(tagIdentifier, charset); 167 return; 168 case IptcDirectory.TAG_ENVELOPE_RECORD_VERSION: 169 case IptcDirectory.TAG_APPLICATION_RECORD_VERSION: 170 case IptcDirectory.TAG_FILE_VERSION: 171 case IptcDirectory.TAG_ARM_VERSION: 172 case IptcDirectory.TAG_PROGRAM_VERSION: 173 // short 174 if (tagByteCount >= 2) { 175 int shortValue = reader.getUInt16(); 176 reader.skip(tagByteCount - 2); 177 directory.setInt(tagIdentifier, shortValue); 178 return; 179 } 112 180 break; 113 }114 115 try {116 processTag(reader, directory, directoryType, tagType, offset, tagByteCount);117 } catch (BufferBoundsException e) {118 directory.addError("Error processing IPTC tag");119 break;120 }121 122 offset += tagByteCount;123 }124 }125 126 private void processTag(@NotNull BufferReader reader, @NotNull Directory directory, int directoryType, int tagType, int offset, int tagByteCount) throws BufferBoundsException127 {128 int tagIdentifier = tagType | (directoryType << 8);129 130 switch (tagIdentifier) {131 case IptcDirectory.TAG_APPLICATION_RECORD_VERSION:132 // short133 int shortValue = reader.getUInt16(offset);134 directory.setInt(tagIdentifier, shortValue);135 return;136 181 case IptcDirectory.TAG_URGENCY: 137 182 // byte 138 directory.setInt(tagIdentifier, reader.getUInt8(offset)); 183 directory.setInt(tagIdentifier, reader.getUInt8()); 184 reader.skip(tagByteCount - 1); 139 185 return; 140 186 case IptcDirectory.TAG_RELEASE_DATE: … … 142 188 // Date object 143 189 if (tagByteCount >= 8) { 144 String dateStr = reader.getString(offset,tagByteCount);190 string = reader.getString(tagByteCount); 145 191 try { 146 int year = Integer.parseInt( dateStr.substring(0, 4));147 int month = Integer.parseInt( dateStr.substring(4, 6)) - 1;148 int day = Integer.parseInt( dateStr.substring(6, 8));192 int year = Integer.parseInt(string.substring(0, 4)); 193 int month = Integer.parseInt(string.substring(4, 6)) - 1; 194 int day = Integer.parseInt(string.substring(6, 8)); 149 195 Date date = new java.util.GregorianCalendar(year, month, day).getTime(); 150 196 directory.setDate(tagIdentifier, date); 151 197 return; 152 198 } catch (NumberFormatException e) { 153 // fall through and we'll store whatever was there as a String199 // fall through and we'll process the 'string' value below 154 200 } 201 } else { 202 reader.skip(tagByteCount); 155 203 } 156 204 case IptcDirectory.TAG_RELEASE_TIME: … … 162 210 163 211 // If we haven't returned yet, treat it as a string 164 String str; 165 if (tagByteCount < 1) { 166 str = ""; 167 } else { 168 str = reader.getString(offset, tagByteCount, System.getProperty("file.encoding")); // "ISO-8859-1" 212 // NOTE that there's a chance we've already loaded the value as a string above, but failed to parse the value 213 if (string == null) { 214 String encoding = directory.getString(IptcDirectory.TAG_CODED_CHARACTER_SET); 215 if (encoding != null) { 216 string = reader.getString(tagByteCount, encoding); 217 } else { 218 byte[] bytes = reader.getBytes(tagByteCount); 219 encoding = Iso2022Converter.guessEncoding(bytes); 220 string = encoding != null ? new String(bytes, encoding) : new String(bytes); 221 } 169 222 } 170 223 … … 179 232 System.arraycopy(oldStrings, 0, newStrings, 0, oldStrings.length); 180 233 } 181 newStrings[newStrings.length - 1] = str ;234 newStrings[newStrings.length - 1] = string; 182 235 directory.setStringArray(tagIdentifier, newStrings); 183 236 } else { 184 directory.setString(tagIdentifier, str );237 directory.setString(tagIdentifier, string); 185 238 } 186 239 } -
trunk/src/com/drew/metadata/jpeg/JpegCommentDescriptor.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.jpeg; … … 26 26 27 27 /** 28 * Provides human-readable string representations of tag values stored in a <code>JpegCommentDirectory</code>.28 * Provides human-readable string representations of tag values stored in a {@link JpegCommentDirectory}. 29 29 * 30 * @author Drew Noakes http ://drewnoakes.com30 * @author Drew Noakes https://drewnoakes.com 31 31 */ 32 32 public class JpegCommentDescriptor extends TagDescriptor<JpegCommentDirectory> … … 40 40 public String getJpegCommentDescription() 41 41 { 42 return _directory.getString(JpegCommentDirectory.TAG_ JPEG_COMMENT);42 return _directory.getString(JpegCommentDirectory.TAG_COMMENT); 43 43 } 44 44 } -
trunk/src/com/drew/metadata/jpeg/JpegCommentDirectory.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.jpeg; … … 29 29 * Describes tags used by a JPEG file comment. 30 30 * 31 * @author Drew Noakes http ://drewnoakes.com31 * @author Drew Noakes https://drewnoakes.com 32 32 */ 33 33 public class JpegCommentDirectory extends Directory … … 37 37 * consistency with other directory types. 38 38 */ 39 public static final int TAG_ JPEG_COMMENT = 0;39 public static final int TAG_COMMENT = 0; 40 40 41 41 @NotNull … … 43 43 44 44 static { 45 _tagNameMap.put(TAG_ JPEG_COMMENT, "JpegComment");45 _tagNameMap.put(TAG_COMMENT, "JPEG Comment"); 46 46 } 47 47 … … 51 51 } 52 52 53 @Override 53 54 @NotNull 54 55 public String getName() … … 57 58 } 58 59 60 @Override 59 61 @NotNull 60 62 protected HashMap<Integer, String> getTagNameMap() -
trunk/src/com/drew/metadata/jpeg/JpegCommentReader.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.jpeg; 22 22 23 import com.drew. lang.BufferBoundsException;24 import com.drew. lang.BufferReader;23 import com.drew.imaging.jpeg.JpegSegmentMetadataReader; 24 import com.drew.imaging.jpeg.JpegSegmentType; 25 25 import com.drew.lang.annotations.NotNull; 26 26 import com.drew.metadata.Metadata; 27 import com.drew.metadata.MetadataReader; 27 28 import java.util.Arrays; 28 29 29 30 /** 30 * Decodes the comment stored within J peg files, populating a <code>Metadata</code>object with tag values in a31 * <code>JpegCommentDirectory</code>.31 * Decodes the comment stored within JPEG files, populating a {@link Metadata} object with tag values in a 32 * {@link JpegCommentDirectory}. 32 33 * 33 * @author Drew Noakes http ://drewnoakes.com34 * @author Drew Noakes https://drewnoakes.com 34 35 */ 35 public class JpegCommentReader implements MetadataReader36 public class JpegCommentReader implements JpegSegmentMetadataReader 36 37 { 37 /** 38 * Performs the Jpeg data extraction, adding found values to the specified 39 * instance of <code>Metadata</code>. 40 */ 41 public void extract(@NotNull final BufferReader reader, @NotNull Metadata metadata) 38 @NotNull 39 public Iterable<JpegSegmentType> getSegmentTypes() 40 { 41 return Arrays.asList(JpegSegmentType.COM); 42 } 43 44 public boolean canProcess(@NotNull byte[] segmentBytes, @NotNull JpegSegmentType segmentType) 45 { 46 // The entire contents of the byte[] is the comment. There's nothing here to discriminate upon. 47 return true; 48 } 49 50 public void extract(@NotNull byte[] segmentBytes, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType) 42 51 { 43 52 JpegCommentDirectory directory = metadata.getOrCreateDirectory(JpegCommentDirectory.class); 44 53 45 try { 46 directory.setString(JpegCommentDirectory.TAG_JPEG_COMMENT, reader.getString(0, (int)reader.getLength())); 47 } catch (BufferBoundsException e) { 48 directory.addError("Exception reading JPEG comment string"); 49 } 54 // The entire contents of the directory are the comment 55 directory.setString(JpegCommentDirectory.TAG_COMMENT, new String(segmentBytes)); 50 56 } 51 57 } -
trunk/src/com/drew/metadata/jpeg/JpegComponent.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.jpeg; … … 26 26 27 27 /** 28 * Stores information about a J pegimage component such as the component id, horiz/vert sampling factor and28 * Stores information about a JPEG image component such as the component id, horiz/vert sampling factor and 29 29 * quantization table number. 30 30 * 31 * @author Drew Noakes http ://drewnoakes.com31 * @author Drew Noakes https://drewnoakes.com 32 32 */ 33 33 public class JpegComponent implements Serializable -
trunk/src/com/drew/metadata/jpeg/JpegDescriptor.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.jpeg; … … 29 29 * Thanks to Darrell Silver (www.darrellsilver.com) for the initial version of this class. 30 30 * 31 * @author Drew Noakes http ://drewnoakes.com31 * @author Drew Noakes https://drewnoakes.com 32 32 */ 33 33 public class JpegDescriptor extends TagDescriptor<JpegDirectory> … … 38 38 } 39 39 40 @Override 40 41 @Nullable 41 42 public String getDescription(int tagType) … … 43 44 switch (tagType) 44 45 { 45 case JpegDirectory.TAG_ JPEG_COMPRESSION_TYPE:46 case JpegDirectory.TAG_COMPRESSION_TYPE: 46 47 return getImageCompressionTypeDescription(); 47 case JpegDirectory.TAG_ JPEG_COMPONENT_DATA_1:48 case JpegDirectory.TAG_COMPONENT_DATA_1: 48 49 return getComponentDataDescription(0); 49 case JpegDirectory.TAG_ JPEG_COMPONENT_DATA_2:50 case JpegDirectory.TAG_COMPONENT_DATA_2: 50 51 return getComponentDataDescription(1); 51 case JpegDirectory.TAG_ JPEG_COMPONENT_DATA_3:52 case JpegDirectory.TAG_COMPONENT_DATA_3: 52 53 return getComponentDataDescription(2); 53 case JpegDirectory.TAG_ JPEG_COMPONENT_DATA_4:54 case JpegDirectory.TAG_COMPONENT_DATA_4: 54 55 return getComponentDataDescription(3); 55 case JpegDirectory.TAG_ JPEG_DATA_PRECISION:56 case JpegDirectory.TAG_DATA_PRECISION: 56 57 return getDataPrecisionDescription(); 57 case JpegDirectory.TAG_ JPEG_IMAGE_HEIGHT:58 case JpegDirectory.TAG_IMAGE_HEIGHT: 58 59 return getImageHeightDescription(); 59 case JpegDirectory.TAG_ JPEG_IMAGE_WIDTH:60 case JpegDirectory.TAG_IMAGE_WIDTH: 60 61 return getImageWidthDescription(); 61 62 default: … … 67 68 public String getImageCompressionTypeDescription() 68 69 { 69 Integer value = _directory.getInteger(JpegDirectory.TAG_ JPEG_COMPRESSION_TYPE);70 Integer value = _directory.getInteger(JpegDirectory.TAG_COMPRESSION_TYPE); 70 71 if (value==null) 71 72 return null; … … 93 94 public String getImageWidthDescription() 94 95 { 95 final String value = _directory.getString(JpegDirectory.TAG_ JPEG_IMAGE_WIDTH);96 final String value = _directory.getString(JpegDirectory.TAG_IMAGE_WIDTH); 96 97 if (value==null) 97 98 return null; … … 102 103 public String getImageHeightDescription() 103 104 { 104 final String value = _directory.getString(JpegDirectory.TAG_ JPEG_IMAGE_HEIGHT);105 final String value = _directory.getString(JpegDirectory.TAG_IMAGE_HEIGHT); 105 106 if (value==null) 106 107 return null; … … 111 112 public String getDataPrecisionDescription() 112 113 { 113 final String value = _directory.getString(JpegDirectory.TAG_ JPEG_DATA_PRECISION);114 final String value = _directory.getString(JpegDirectory.TAG_DATA_PRECISION); 114 115 if (value==null) 115 116 return null; … … 125 126 return null; 126 127 127 StringBuilder sb = new StringBuilder(); 128 sb.append(value.getComponentName()); 129 sb.append(" component: Quantization table "); 130 sb.append(value.getQuantizationTableNumber()); 131 sb.append(", Sampling factors "); 132 sb.append(value.getHorizontalSamplingFactor()); 133 sb.append(" horiz/"); 134 sb.append(value.getVerticalSamplingFactor()); 135 sb.append(" vert"); 136 return sb.toString(); 128 return value.getComponentName() + " component: Quantization table " + value.getQuantizationTableNumber() 129 + ", Sampling factors " + value.getHorizontalSamplingFactor() 130 + " horiz/" + value.getVerticalSamplingFactor() + " vert"; 137 131 } 138 132 } -
trunk/src/com/drew/metadata/jpeg/JpegDirectory.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.jpeg; … … 29 29 30 30 /** 31 * Directory of tags and values for the SOF0 J pegsegment. This segment holds basic metadata about the image.31 * Directory of tags and values for the SOF0 JPEG segment. This segment holds basic metadata about the image. 32 32 * 33 * @author Darrell Silver http://www.darrellsilver.com and Drew Noakes http ://drewnoakes.com33 * @author Darrell Silver http://www.darrellsilver.com and Drew Noakes https://drewnoakes.com 34 34 */ 35 35 public class JpegDirectory extends Directory 36 36 { 37 public static final int TAG_ JPEG_COMPRESSION_TYPE = -3;37 public static final int TAG_COMPRESSION_TYPE = -3; 38 38 /** This is in bits/sample, usually 8 (12 and 16 not supported by most software). */ 39 public static final int TAG_ JPEG_DATA_PRECISION = 0;39 public static final int TAG_DATA_PRECISION = 0; 40 40 /** The image's height. Necessary for decoding the image, so it should always be there. */ 41 public static final int TAG_ JPEG_IMAGE_HEIGHT = 1;41 public static final int TAG_IMAGE_HEIGHT = 1; 42 42 /** The image's width. Necessary for decoding the image, so it should always be there. */ 43 public static final int TAG_ JPEG_IMAGE_WIDTH = 3;43 public static final int TAG_IMAGE_WIDTH = 3; 44 44 /** 45 45 * Usually 1 = grey scaled, 3 = color YcbCr or YIQ, 4 = color CMYK … … 48 48 * sampling factors (1byte) (bit 0-3 vertical., 4-7 horizontal.), 49 49 * quantization table number (1 byte). 50 * <p />50 * <p> 51 51 * This info is from http://www.funducode.com/freec/Fileformats/format3/format3b.htm 52 52 */ 53 public static final int TAG_ JPEG_NUMBER_OF_COMPONENTS = 5;53 public static final int TAG_NUMBER_OF_COMPONENTS = 5; 54 54 55 55 // NOTE! Component tag type int values must increment in steps of 1 56 56 57 /** the first of a possible 4 color components. Number of components specified in TAG_ JPEG_NUMBER_OF_COMPONENTS. */58 public static final int TAG_ JPEG_COMPONENT_DATA_1 = 6;59 /** the second of a possible 4 color components. Number of components specified in TAG_ JPEG_NUMBER_OF_COMPONENTS. */60 public static final int TAG_ JPEG_COMPONENT_DATA_2 = 7;61 /** the third of a possible 4 color components. Number of components specified in TAG_ JPEG_NUMBER_OF_COMPONENTS. */62 public static final int TAG_ JPEG_COMPONENT_DATA_3 = 8;63 /** the fourth of a possible 4 color components. Number of components specified in TAG_ JPEG_NUMBER_OF_COMPONENTS. */64 public static final int TAG_ JPEG_COMPONENT_DATA_4 = 9;57 /** the first of a possible 4 color components. Number of components specified in TAG_NUMBER_OF_COMPONENTS. */ 58 public static final int TAG_COMPONENT_DATA_1 = 6; 59 /** the second of a possible 4 color components. Number of components specified in TAG_NUMBER_OF_COMPONENTS. */ 60 public static final int TAG_COMPONENT_DATA_2 = 7; 61 /** the third of a possible 4 color components. Number of components specified in TAG_NUMBER_OF_COMPONENTS. */ 62 public static final int TAG_COMPONENT_DATA_3 = 8; 63 /** the fourth of a possible 4 color components. Number of components specified in TAG_NUMBER_OF_COMPONENTS. */ 64 public static final int TAG_COMPONENT_DATA_4 = 9; 65 65 66 66 @NotNull … … 68 68 69 69 static { 70 _tagNameMap.put(TAG_ JPEG_COMPRESSION_TYPE, "Compression Type");71 _tagNameMap.put(TAG_ JPEG_DATA_PRECISION, "Data Precision");72 _tagNameMap.put(TAG_ JPEG_IMAGE_WIDTH, "Image Width");73 _tagNameMap.put(TAG_ JPEG_IMAGE_HEIGHT, "Image Height");74 _tagNameMap.put(TAG_ JPEG_NUMBER_OF_COMPONENTS, "Number of Components");75 _tagNameMap.put(TAG_ JPEG_COMPONENT_DATA_1, "Component 1");76 _tagNameMap.put(TAG_ JPEG_COMPONENT_DATA_2, "Component 2");77 _tagNameMap.put(TAG_ JPEG_COMPONENT_DATA_3, "Component 3");78 _tagNameMap.put(TAG_ JPEG_COMPONENT_DATA_4, "Component 4");70 _tagNameMap.put(TAG_COMPRESSION_TYPE, "Compression Type"); 71 _tagNameMap.put(TAG_DATA_PRECISION, "Data Precision"); 72 _tagNameMap.put(TAG_IMAGE_WIDTH, "Image Width"); 73 _tagNameMap.put(TAG_IMAGE_HEIGHT, "Image Height"); 74 _tagNameMap.put(TAG_NUMBER_OF_COMPONENTS, "Number of Components"); 75 _tagNameMap.put(TAG_COMPONENT_DATA_1, "Component 1"); 76 _tagNameMap.put(TAG_COMPONENT_DATA_2, "Component 2"); 77 _tagNameMap.put(TAG_COMPONENT_DATA_3, "Component 3"); 78 _tagNameMap.put(TAG_COMPONENT_DATA_4, "Component 4"); 79 79 } 80 80 … … 84 84 } 85 85 86 @Override 86 87 @NotNull 87 88 public String getName() 88 89 { 89 return "J peg";90 return "JPEG"; 90 91 } 91 92 93 @Override 92 94 @NotNull 93 95 protected HashMap<Integer, String> getTagNameMap() … … 104 106 public JpegComponent getComponent(int componentNumber) 105 107 { 106 int tagType = JpegDirectory.TAG_ JPEG_COMPONENT_DATA_1 + componentNumber;108 int tagType = JpegDirectory.TAG_COMPONENT_DATA_1 + componentNumber; 107 109 return (JpegComponent)getObject(tagType); 108 110 } … … 110 112 public int getImageWidth() throws MetadataException 111 113 { 112 return getInt(JpegDirectory.TAG_ JPEG_IMAGE_WIDTH);114 return getInt(JpegDirectory.TAG_IMAGE_WIDTH); 113 115 } 114 116 115 117 public int getImageHeight() throws MetadataException 116 118 { 117 return getInt(JpegDirectory.TAG_ JPEG_IMAGE_HEIGHT);119 return getInt(JpegDirectory.TAG_IMAGE_HEIGHT); 118 120 } 119 121 120 122 public int getNumberOfComponents() throws MetadataException 121 123 { 122 return getInt(JpegDirectory.TAG_ JPEG_NUMBER_OF_COMPONENTS);124 return getInt(JpegDirectory.TAG_NUMBER_OF_COMPONENTS); 123 125 } 124 126 } -
trunk/src/com/drew/metadata/jpeg/JpegReader.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.jpeg; 22 22 23 import com.drew.lang.BufferBoundsException; 24 import com.drew.lang.BufferReader; 23 import com.drew.imaging.jpeg.JpegSegmentMetadataReader; 24 import com.drew.imaging.jpeg.JpegSegmentType; 25 import com.drew.lang.SequentialByteArrayReader; 26 import com.drew.lang.SequentialReader; 25 27 import com.drew.lang.annotations.NotNull; 26 28 import com.drew.metadata.Metadata; 27 import com.drew.metadata.MetadataReader; 29 30 import java.io.IOException; 31 import java.util.Arrays; 28 32 29 33 /** 30 * Decodes J peg SOF0 data, populating a <code>Metadata</code> object with tag values in a <code>JpegDirectory</code>.34 * Decodes JPEG SOFn data, populating a {@link Metadata} object with tag values in a {@link JpegDirectory}. 31 35 * 32 * @author Darrell Silver http://www.darrellsilver.com and Drew Noakes http://drewnoakes.com 36 * @author Drew Noakes https://drewnoakes.com 37 * @author Darrell Silver http://www.darrellsilver.com 33 38 */ 34 public class JpegReader implements MetadataReader39 public class JpegReader implements JpegSegmentMetadataReader 35 40 { 36 /** 37 * Performs the Jpeg data extraction, adding found values to the specified 38 * instance of <code>Metadata</code>. 39 */ 40 public void extract(@NotNull final BufferReader reader, @NotNull Metadata metadata) 41 @NotNull 42 public Iterable<JpegSegmentType> getSegmentTypes() 41 43 { 44 // NOTE that some SOFn values do not exist 45 return Arrays.asList( 46 JpegSegmentType.SOF0, 47 JpegSegmentType.SOF1, 48 JpegSegmentType.SOF2, 49 JpegSegmentType.SOF3, 50 // JpegSegmentType.SOF4, 51 JpegSegmentType.SOF5, 52 JpegSegmentType.SOF6, 53 JpegSegmentType.SOF7, 54 JpegSegmentType.SOF8, 55 JpegSegmentType.SOF9, 56 JpegSegmentType.SOF10, 57 JpegSegmentType.SOF11, 58 // JpegSegmentType.SOF12, 59 JpegSegmentType.SOF13, 60 JpegSegmentType.SOF14, 61 JpegSegmentType.SOF15 62 ); 63 } 64 65 public boolean canProcess(@NotNull byte[] segmentBytes, @NotNull JpegSegmentType segmentType) 66 { 67 return true; 68 } 69 70 public void extract(@NotNull byte[] segmentBytes, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType) 71 { 72 if (metadata.containsDirectory(JpegDirectory.class)) { 73 // If this directory is already present, discontinue this operation. 74 // We only store metadata for the *first* matching SOFn segment. 75 return; 76 } 77 42 78 JpegDirectory directory = metadata.getOrCreateDirectory(JpegDirectory.class); 43 79 80 // The value of TAG_COMPRESSION_TYPE is determined by the segment type found 81 directory.setInt(JpegDirectory.TAG_COMPRESSION_TYPE, segmentType.byteValue - JpegSegmentType.SOF0.byteValue); 82 83 SequentialReader reader = new SequentialByteArrayReader(segmentBytes); 84 44 85 try { 45 // data precision 46 int dataPrecision = reader.getUInt8(JpegDirectory.TAG_JPEG_DATA_PRECISION); 47 directory.setInt(JpegDirectory.TAG_JPEG_DATA_PRECISION, dataPrecision); 48 49 // process height 50 int height = reader.getUInt16(JpegDirectory.TAG_JPEG_IMAGE_HEIGHT); 51 directory.setInt(JpegDirectory.TAG_JPEG_IMAGE_HEIGHT, height); 52 53 // process width 54 int width = reader.getUInt16(JpegDirectory.TAG_JPEG_IMAGE_WIDTH); 55 directory.setInt(JpegDirectory.TAG_JPEG_IMAGE_WIDTH, width); 56 57 // number of components 58 int numberOfComponents = reader.getUInt8(JpegDirectory.TAG_JPEG_NUMBER_OF_COMPONENTS); 59 directory.setInt(JpegDirectory.TAG_JPEG_NUMBER_OF_COMPONENTS, numberOfComponents); 86 directory.setInt(JpegDirectory.TAG_DATA_PRECISION, reader.getUInt8()); 87 directory.setInt(JpegDirectory.TAG_IMAGE_HEIGHT, reader.getUInt16()); 88 directory.setInt(JpegDirectory.TAG_IMAGE_WIDTH, reader.getUInt16()); 89 short componentCount = reader.getUInt8(); 90 directory.setInt(JpegDirectory.TAG_NUMBER_OF_COMPONENTS, componentCount); 60 91 61 92 // for each component, there are three bytes of data: … … 63 94 // 2 - Sampling factors: bit 0-3 vertical, 4-7 horizontal 64 95 // 3 - Quantization table number 65 int offset = 6; 66 for (int i = 0; i < numberOfComponents; i++) { 67 int componentId = reader.getUInt8(offset++); 68 int samplingFactorByte = reader.getUInt8(offset++); 69 int quantizationTableNumber = reader.getUInt8(offset++); 70 JpegComponent component = new JpegComponent(componentId, samplingFactorByte, quantizationTableNumber); 71 directory.setObject(JpegDirectory.TAG_JPEG_COMPONENT_DATA_1 + i, component); 96 for (int i = 0; i < (int)componentCount; i++) { 97 final int componentId = reader.getUInt8(); 98 final int samplingFactorByte = reader.getUInt8(); 99 final int quantizationTableNumber = reader.getUInt8(); 100 final JpegComponent component = new JpegComponent(componentId, samplingFactorByte, quantizationTableNumber); 101 directory.setObject(JpegDirectory.TAG_COMPONENT_DATA_1 + i, component); 72 102 } 73 103 74 } catch ( BufferBoundsException ex) {104 } catch (IOException ex) { 75 105 directory.addError(ex.getMessage()); 76 106 } -
trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
r8126 r8132 627 627 628 628 try { 629 double speed = dirGps.getDouble(GpsDirectory.TAG_ GPS_SPEED);630 String speedRef = dirGps.getString(GpsDirectory.TAG_ GPS_SPEED_REF);629 double speed = dirGps.getDouble(GpsDirectory.TAG_SPEED); 630 String speedRef = dirGps.getString(GpsDirectory.TAG_SPEED_REF); 631 631 if (speedRef != null) { 632 632 if (speedRef.equalsIgnoreCase("M")) { … … 645 645 646 646 try { 647 double ele = dirGps.getDouble(GpsDirectory.TAG_ GPS_ALTITUDE);648 int d = dirGps.getInt(GpsDirectory.TAG_ GPS_ALTITUDE_REF);647 double ele = dirGps.getDouble(GpsDirectory.TAG_ALTITUDE); 648 int d = dirGps.getInt(GpsDirectory.TAG_ALTITUDE_REF); 649 649 if (d == 1) { 650 650 ele *= -1; … … 679 679 // 2) GPS_DATE_STAMP not set -> use EXIF date or set to default 680 680 // 3) GPS_TIME_STAMP and GPS_DATE_STAMP are set 681 int[] timeStampComps = dirGps.getIntArray(GpsDirectory.TAG_ GPS_TIME_STAMP);681 int[] timeStampComps = dirGps.getIntArray(GpsDirectory.TAG_TIME_STAMP); 682 682 if (timeStampComps != null) { 683 683 int gpsHour = timeStampComps[0]; … … 688 688 // We have the time. Next step is to check if the GPS date stamp is set. 689 689 // dirGps.getString() always succeeds, but the return value might be null. 690 String dateStampStr = dirGps.getString(GpsDirectory.TAG_ GPS_DATE_STAMP);690 String dateStampStr = dirGps.getString(GpsDirectory.TAG_DATE_STAMP); 691 691 if (dateStampStr != null && dateStampStr.matches("^\\d+:\\d+:\\d+$")) { 692 692 String[] dateStampComps = dateStampStr.split(":"); -
trunk/src/org/openstreetmap/josm/tools/ExifReader.java
r7956 r8132 2 2 package org.openstreetmap.josm.tools; 3 3 4 import java.awt.geom.AffineTransform; 4 5 import java.io.File; 5 6 import java.io.IOException; … … 21 22 import com.drew.metadata.exif.ExifSubIFDDirectory; 22 23 import com.drew.metadata.exif.GpsDirectory; 23 import java.awt.geom.AffineTransform;24 24 25 25 /** … … 125 125 public static LatLon readLatLon(GpsDirectory dirGps) throws MetadataException { 126 126 if (dirGps != null) { 127 double lat = readAxis(dirGps, GpsDirectory.TAG_ GPS_LATITUDE, GpsDirectory.TAG_GPS_LATITUDE_REF, 'S');128 double lon = readAxis(dirGps, GpsDirectory.TAG_ GPS_LONGITUDE, GpsDirectory.TAG_GPS_LONGITUDE_REF, 'W');127 double lat = readAxis(dirGps, GpsDirectory.TAG_LATITUDE, GpsDirectory.TAG_LATITUDE_REF, 'S'); 128 double lon = readAxis(dirGps, GpsDirectory.TAG_LONGITUDE, GpsDirectory.TAG_LONGITUDE_REF, 'W'); 129 129 return new LatLon(lat, lon); 130 130 } … … 159 159 public static Double readDirection(GpsDirectory dirGps) { 160 160 if (dirGps != null) { 161 Rational direction = dirGps.getRational(GpsDirectory.TAG_ GPS_IMG_DIRECTION);161 Rational direction = dirGps.getRational(GpsDirectory.TAG_IMG_DIRECTION); 162 162 if (direction != null) { 163 163 return direction.doubleValue();
Note:
See TracChangeset
for help on using the changeset viewer.