001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools; 003 004import java.io.File; 005import java.io.IOException; 006import java.text.ParseException; 007import java.util.Date; 008 009import org.openstreetmap.josm.data.coor.LatLon; 010 011import com.drew.imaging.jpeg.JpegMetadataReader; 012import com.drew.imaging.jpeg.JpegProcessingException; 013import com.drew.lang.Rational; 014import com.drew.metadata.Directory; 015import com.drew.metadata.Metadata; 016import com.drew.metadata.MetadataException; 017import com.drew.metadata.Tag; 018import com.drew.metadata.exif.ExifIFD0Directory; 019import com.drew.metadata.exif.ExifSubIFDDirectory; 020import com.drew.metadata.exif.GpsDirectory; 021 022/** 023 * Read out EXIF information from a JPEG file 024 * @author Imi 025 * @since 99 026 */ 027public final class ExifReader { 028 029 private ExifReader() { 030 // Hide default constructor for utils classes 031 } 032 033 /** 034 * Returns the date/time from the given JPEG file. 035 * @param filename The JPEG file to read 036 * @return The date/time read in the EXIF section, or {@code null} if not found 037 * @throws ParseException if {@link DateParser#parse} fails to parse date/time 038 */ 039 public static Date readTime(File filename) throws ParseException { 040 try { 041 Metadata metadata = JpegMetadataReader.readMetadata(filename); 042 String dateStr = null; 043 OUTER: 044 for (Directory dirIt : metadata.getDirectories()) { 045 for (Tag tag : dirIt.getTags()) { 046 if (tag.getTagType() == ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL /* 0x9003 */) { 047 dateStr = tag.getDescription(); 048 break OUTER; // prefer this tag 049 } 050 if (tag.getTagType() == ExifIFD0Directory.TAG_DATETIME /* 0x0132 */ || 051 tag.getTagType() == ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED /* 0x9004 */) { 052 dateStr = tag.getDescription(); 053 } 054 } 055 } 056 if (dateStr != null) { 057 dateStr = dateStr.replace('/', ':'); // workaround for HTC Sensation bug, see #7228 058 return DateParser.parse(dateStr); 059 } 060 } catch (ParseException e) { 061 throw e; 062 } catch (Exception e) { 063 e.printStackTrace(); 064 } 065 return null; 066 } 067 068 /** 069 * Returns the image orientation of the given JPEG file. 070 * @param filename The JPEG file to read 071 * @return The image orientation as an {@code int}. Default value is 1. Possible values are listed in EXIF spec as follows:<br> 072 * <ul>1. The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side.</ul> 073 * <ul>2. The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side.</ul> 074 * <ul>3. The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side.</ul> 075 * <ul>4. The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side.</ul> 076 * <ul>5. The 0th row is the visual left-hand side of the image, and the 0th column is the visual top.</ul> 077 * <ul>6. The 0th row is the visual right-hand side of the image, and the 0th column is the visual top.</ul> 078 * <ul>7. The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom.</ul> 079 * <ul>8. The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom.</ul> 080 * @see <a href="http://www.impulseadventure.com/photo/exif-orientation.html">http://www.impulseadventure.com/photo/exif-orientation.html</a> 081 * @see <a href="http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto">http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto</a> 082 */ 083 public static Integer readOrientation(File filename) { 084 try { 085 final Metadata metadata = JpegMetadataReader.readMetadata(filename); 086 final Directory dir = metadata.getDirectory(ExifIFD0Directory.class); 087 return dir.getInt(ExifIFD0Directory.TAG_ORIENTATION); 088 } catch (JpegProcessingException e) { 089 e.printStackTrace(); 090 } catch (MetadataException e) { 091 e.printStackTrace(); 092 } catch (IOException e) { 093 e.printStackTrace(); 094 } 095 return null; 096 } 097 098 /** 099 * Returns the geolocation of the given JPEG file. 100 * @param filename The JPEG file to read 101 * @return The lat/lon read in the EXIF section, or {@code null} if not found 102 * @since 6209 103 */ 104 public static LatLon readLatLon(File filename) { 105 try { 106 final Metadata metadata = JpegMetadataReader.readMetadata(filename); 107 final GpsDirectory dirGps = metadata.getDirectory(GpsDirectory.class); 108 return readLatLon(dirGps); 109 } catch (JpegProcessingException e) { 110 e.printStackTrace(); 111 } catch (IOException e) { 112 e.printStackTrace(); 113 } catch (MetadataException e) { 114 e.printStackTrace(); 115 } 116 return null; 117 } 118 119 /** 120 * Returns the geolocation of the given EXIF GPS directory. 121 * @param dirGps The EXIF GPS directory 122 * @return The lat/lon read in the EXIF section, or {@code null} if {@code dirGps} is null 123 * @throws MetadataException 124 * @since 6209 125 */ 126 public static LatLon readLatLon(GpsDirectory dirGps) throws MetadataException { 127 if (dirGps != null) { 128 double lat = readAxis(dirGps, GpsDirectory.TAG_GPS_LATITUDE, GpsDirectory.TAG_GPS_LATITUDE_REF, 'S'); 129 double lon = readAxis(dirGps, GpsDirectory.TAG_GPS_LONGITUDE, GpsDirectory.TAG_GPS_LONGITUDE_REF, 'W'); 130 return new LatLon(lat, lon); 131 } 132 return null; 133 } 134 135 /** 136 * Returns the direction of the given JPEG file. 137 * @param filename The JPEG file to read 138 * @return The direction of the image when it was captures (in degrees between 0.0 and 359.99), or {@code null} if missing or if {@code dirGps} is null 139 * @since 6209 140 */ 141 public static Double readDirection(File filename) { 142 try { 143 final Metadata metadata = JpegMetadataReader.readMetadata(filename); 144 final GpsDirectory dirGps = metadata.getDirectory(GpsDirectory.class); 145 return readDirection(dirGps); 146 } catch (JpegProcessingException e) { 147 e.printStackTrace(); 148 } catch (IOException e) { 149 e.printStackTrace(); 150 } 151 return null; 152 } 153 154 /** 155 * Returns the direction of the given EXIF GPS directory. 156 * @param dirGps The EXIF GPS directory 157 * @return The direction of the image when it was captures (in degrees between 0.0 and 359.99), or {@code null} if missing or if {@code dirGps} is null 158 * @since 6209 159 */ 160 public static Double readDirection(GpsDirectory dirGps) { 161 if (dirGps != null) { 162 Rational direction = dirGps.getRational(GpsDirectory.TAG_GPS_IMG_DIRECTION); 163 if (direction != null) { 164 return direction.doubleValue(); 165 } 166 } 167 return null; 168 } 169 170 private static double readAxis(GpsDirectory dirGps, int gpsTag, int gpsTagRef, char cRef) throws MetadataException { 171 double value; 172 Rational[] components = dirGps.getRationalArray(gpsTag); 173 if (components != null) { 174 double deg = components[0].doubleValue(); 175 double min = components[1].doubleValue(); 176 double sec = components[2].doubleValue(); 177 178 if (Double.isNaN(deg) && Double.isNaN(min) && Double.isNaN(sec)) 179 throw new IllegalArgumentException(); 180 181 value = (Double.isNaN(deg) ? 0 : deg + (Double.isNaN(min) ? 0 : (min / 60)) + (Double.isNaN(sec) ? 0 : (sec / 3600))); 182 183 if (dirGps.getString(gpsTagRef).charAt(0) == cRef) { 184 value = -value; 185 } 186 } else { 187 // Try to read lon/lat as double value (Nonstandard, created by some cameras -> #5220) 188 value = dirGps.getDouble(gpsTag); 189 } 190 return value; 191 } 192}