Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.media;
     18 
     19 import java.io.IOException;
     20 import java.text.ParsePosition;
     21 import java.text.SimpleDateFormat;
     22 import java.util.Date;
     23 import java.util.HashMap;
     24 import java.util.Map;
     25 import java.util.TimeZone;
     26 
     27 /**
     28  * This is a class for reading and writing Exif tags in a JPEG file.
     29  */
     30 public class ExifInterface {
     31     // The Exif tag names
     32     /** Type is int. */
     33     public static final String TAG_ORIENTATION = "Orientation";
     34     /** Type is String. */
     35     public static final String TAG_DATETIME = "DateTime";
     36     /** Type is String. */
     37     public static final String TAG_MAKE = "Make";
     38     /** Type is String. */
     39     public static final String TAG_MODEL = "Model";
     40     /** Type is int. */
     41     public static final String TAG_FLASH = "Flash";
     42     /** Type is int. */
     43     public static final String TAG_IMAGE_WIDTH = "ImageWidth";
     44     /** Type is int. */
     45     public static final String TAG_IMAGE_LENGTH = "ImageLength";
     46     /** String. Format is "num1/denom1,num2/denom2,num3/denom3". */
     47     public static final String TAG_GPS_LATITUDE = "GPSLatitude";
     48     /** String. Format is "num1/denom1,num2/denom2,num3/denom3". */
     49     public static final String TAG_GPS_LONGITUDE = "GPSLongitude";
     50     /** Type is String. */
     51     public static final String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef";
     52     /** Type is String. */
     53     public static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef";
     54     /** Type is String. */
     55     public static final String TAG_EXPOSURE_TIME = "ExposureTime";
     56     /** Type is String. */
     57     public static final String TAG_APERTURE = "FNumber";
     58     /** Type is String. */
     59     public static final String TAG_ISO = "ISOSpeedRatings";
     60 
     61     /**
     62      * The altitude (in meters) based on the reference in TAG_GPS_ALTITUDE_REF.
     63      * Type is rational.
     64      */
     65     public static final String TAG_GPS_ALTITUDE = "GPSAltitude";
     66 
     67     /**
     68      * 0 if the altitude is above sea level. 1 if the altitude is below sea
     69      * level. Type is int.
     70      */
     71     public static final String TAG_GPS_ALTITUDE_REF = "GPSAltitudeRef";
     72 
     73     /** Type is String. */
     74     public static final String TAG_GPS_TIMESTAMP = "GPSTimeStamp";
     75     /** Type is String. */
     76     public static final String TAG_GPS_DATESTAMP = "GPSDateStamp";
     77     /** Type is int. */
     78     public static final String TAG_WHITE_BALANCE = "WhiteBalance";
     79     /** Type is rational. */
     80     public static final String TAG_FOCAL_LENGTH = "FocalLength";
     81     /** Type is String. Name of GPS processing method used for location finding. */
     82     public static final String TAG_GPS_PROCESSING_METHOD = "GPSProcessingMethod";
     83 
     84     // Constants used for the Orientation Exif tag.
     85     public static final int ORIENTATION_UNDEFINED = 0;
     86     public static final int ORIENTATION_NORMAL = 1;
     87     public static final int ORIENTATION_FLIP_HORIZONTAL = 2;  // left right reversed mirror
     88     public static final int ORIENTATION_ROTATE_180 = 3;
     89     public static final int ORIENTATION_FLIP_VERTICAL = 4;  // upside down mirror
     90     public static final int ORIENTATION_TRANSPOSE = 5;  // flipped about top-left <--> bottom-right axis
     91     public static final int ORIENTATION_ROTATE_90 = 6;  // rotate 90 cw to right it
     92     public static final int ORIENTATION_TRANSVERSE = 7;  // flipped about top-right <--> bottom-left axis
     93     public static final int ORIENTATION_ROTATE_270 = 8;  // rotate 270 to right it
     94 
     95     // Constants used for white balance
     96     public static final int WHITEBALANCE_AUTO = 0;
     97     public static final int WHITEBALANCE_MANUAL = 1;
     98     private static SimpleDateFormat sFormatter;
     99 
    100     static {
    101         System.loadLibrary("exif_jni");
    102         sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
    103         sFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
    104     }
    105 
    106     private String mFilename;
    107     private HashMap<String, String> mAttributes;
    108     private boolean mHasThumbnail;
    109 
    110     // Because the underlying implementation (jhead) uses static variables,
    111     // there can only be one user at a time for the native functions (and
    112     // they cannot keep state in the native code across function calls). We
    113     // use sLock to serialize the accesses.
    114     private static final Object sLock = new Object();
    115 
    116     /**
    117      * Reads Exif tags from the specified JPEG file.
    118      */
    119     public ExifInterface(String filename) throws IOException {
    120         mFilename = filename;
    121         loadAttributes();
    122     }
    123 
    124     /**
    125      * Returns the value of the specified tag or {@code null} if there
    126      * is no such tag in the JPEG file.
    127      *
    128      * @param tag the name of the tag.
    129      */
    130     public String getAttribute(String tag) {
    131         return mAttributes.get(tag);
    132     }
    133 
    134     /**
    135      * Returns the integer value of the specified tag. If there is no such tag
    136      * in the JPEG file or the value cannot be parsed as integer, return
    137      * <var>defaultValue</var>.
    138      *
    139      * @param tag the name of the tag.
    140      * @param defaultValue the value to return if the tag is not available.
    141      */
    142     public int getAttributeInt(String tag, int defaultValue) {
    143         String value = mAttributes.get(tag);
    144         if (value == null) return defaultValue;
    145         try {
    146             return Integer.valueOf(value);
    147         } catch (NumberFormatException ex) {
    148             return defaultValue;
    149         }
    150     }
    151 
    152     /**
    153      * Returns the double value of the specified rational tag. If there is no
    154      * such tag in the JPEG file or the value cannot be parsed as double, return
    155      * <var>defaultValue</var>.
    156      *
    157      * @param tag the name of the tag.
    158      * @param defaultValue the value to return if the tag is not available.
    159      */
    160     public double getAttributeDouble(String tag, double defaultValue) {
    161         String value = mAttributes.get(tag);
    162         if (value == null) return defaultValue;
    163         try {
    164             int index = value.indexOf("/");
    165             if (index == -1) return defaultValue;
    166             double denom = Double.parseDouble(value.substring(index + 1));
    167             if (denom == 0) return defaultValue;
    168             double num = Double.parseDouble(value.substring(0, index));
    169             return num / denom;
    170         } catch (NumberFormatException ex) {
    171             return defaultValue;
    172         }
    173     }
    174 
    175     /**
    176      * Set the value of the specified tag.
    177      *
    178      * @param tag the name of the tag.
    179      * @param value the value of the tag.
    180      */
    181     public void setAttribute(String tag, String value) {
    182         mAttributes.put(tag, value);
    183     }
    184 
    185     /**
    186      * Initialize mAttributes with the attributes from the file mFilename.
    187      *
    188      * mAttributes is a HashMap which stores the Exif attributes of the file.
    189      * The key is the standard tag name and the value is the tag's value: e.g.
    190      * Model -> Nikon. Numeric values are stored as strings.
    191      *
    192      * This function also initialize mHasThumbnail to indicate whether the
    193      * file has a thumbnail inside.
    194      */
    195     private void loadAttributes() throws IOException {
    196         // format of string passed from native C code:
    197         // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
    198         // example:
    199         // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
    200         mAttributes = new HashMap<String, String>();
    201 
    202         String attrStr;
    203         synchronized (sLock) {
    204             attrStr = getAttributesNative(mFilename);
    205         }
    206 
    207         // get count
    208         int ptr = attrStr.indexOf(' ');
    209         int count = Integer.parseInt(attrStr.substring(0, ptr));
    210         // skip past the space between item count and the rest of the attributes
    211         ++ptr;
    212 
    213         for (int i = 0; i < count; i++) {
    214             // extract the attribute name
    215             int equalPos = attrStr.indexOf('=', ptr);
    216             String attrName = attrStr.substring(ptr, equalPos);
    217             ptr = equalPos + 1;     // skip past =
    218 
    219             // extract the attribute value length
    220             int lenPos = attrStr.indexOf(' ', ptr);
    221             int attrLen = Integer.parseInt(attrStr.substring(ptr, lenPos));
    222             ptr = lenPos + 1;       // skip pas the space
    223 
    224             // extract the attribute value
    225             String attrValue = attrStr.substring(ptr, ptr + attrLen);
    226             ptr += attrLen;
    227 
    228             if (attrName.equals("hasThumbnail")) {
    229                 mHasThumbnail = attrValue.equalsIgnoreCase("true");
    230             } else {
    231                 mAttributes.put(attrName, attrValue);
    232             }
    233         }
    234     }
    235 
    236     /**
    237      * Save the tag data into the JPEG file. This is expensive because it involves
    238      * copying all the JPG data from one file to another and deleting the old file
    239      * and renaming the other. It's best to use {@link #setAttribute(String,String)}
    240      * to set all attributes to write and make a single call rather than multiple
    241      * calls for each attribute.
    242      */
    243     public void saveAttributes() throws IOException {
    244         // format of string passed to native C code:
    245         // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
    246         // example:
    247         // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
    248         StringBuilder sb = new StringBuilder();
    249         int size = mAttributes.size();
    250         if (mAttributes.containsKey("hasThumbnail")) {
    251             --size;
    252         }
    253         sb.append(size + " ");
    254         for (Map.Entry<String, String> iter : mAttributes.entrySet()) {
    255             String key = iter.getKey();
    256             if (key.equals("hasThumbnail")) {
    257                 // this is a fake attribute not saved as an exif tag
    258                 continue;
    259             }
    260             String val = iter.getValue();
    261             sb.append(key + "=");
    262             sb.append(val.length() + " ");
    263             sb.append(val);
    264         }
    265         String s = sb.toString();
    266         synchronized (sLock) {
    267             saveAttributesNative(mFilename, s);
    268             commitChangesNative(mFilename);
    269         }
    270     }
    271 
    272     /**
    273      * Returns true if the JPEG file has a thumbnail.
    274      */
    275     public boolean hasThumbnail() {
    276         return mHasThumbnail;
    277     }
    278 
    279     /**
    280      * Returns the thumbnail inside the JPEG file, or {@code null} if there is no thumbnail.
    281      * The returned data is in JPEG format and can be decoded using
    282      * {@link android.graphics.BitmapFactory#decodeByteArray(byte[],int,int)}
    283      */
    284     public byte[] getThumbnail() {
    285         synchronized (sLock) {
    286             return getThumbnailNative(mFilename);
    287         }
    288     }
    289 
    290     /**
    291      * Stores the latitude and longitude value in a float array. The first element is
    292      * the latitude, and the second element is the longitude. Returns false if the
    293      * Exif tags are not available.
    294      */
    295     public boolean getLatLong(float output[]) {
    296         String latValue = mAttributes.get(ExifInterface.TAG_GPS_LATITUDE);
    297         String latRef = mAttributes.get(ExifInterface.TAG_GPS_LATITUDE_REF);
    298         String lngValue = mAttributes.get(ExifInterface.TAG_GPS_LONGITUDE);
    299         String lngRef = mAttributes.get(ExifInterface.TAG_GPS_LONGITUDE_REF);
    300 
    301         if (latValue != null && latRef != null && lngValue != null && lngRef != null) {
    302             try {
    303                 output[0] = convertRationalLatLonToFloat(latValue, latRef);
    304                 output[1] = convertRationalLatLonToFloat(lngValue, lngRef);
    305                 return true;
    306             } catch (IllegalArgumentException e) {
    307                 // if values are not parseable
    308             }
    309         }
    310 
    311         return false;
    312     }
    313 
    314     /**
    315      * Return the altitude in meters. If the exif tag does not exist, return
    316      * <var>defaultValue</var>.
    317      *
    318      * @param defaultValue the value to return if the tag is not available.
    319      */
    320     public double getAltitude(double defaultValue) {
    321         double altitude = getAttributeDouble(TAG_GPS_ALTITUDE, -1);
    322         int ref = getAttributeInt(TAG_GPS_ALTITUDE_REF, -1);
    323 
    324         if (altitude >= 0 && ref >= 0) {
    325             return (double) (altitude * ((ref == 1) ? -1 : 1));
    326         } else {
    327             return defaultValue;
    328         }
    329     }
    330 
    331     /**
    332      * Returns number of milliseconds since Jan. 1, 1970, midnight.
    333      * Returns -1 if the date time information if not available.
    334      * @hide
    335      */
    336     public long getDateTime() {
    337         String dateTimeString = mAttributes.get(TAG_DATETIME);
    338         if (dateTimeString == null) return -1;
    339 
    340         ParsePosition pos = new ParsePosition(0);
    341         try {
    342             Date datetime = sFormatter.parse(dateTimeString, pos);
    343             if (datetime == null) return -1;
    344             return datetime.getTime();
    345         } catch (IllegalArgumentException ex) {
    346             return -1;
    347         }
    348     }
    349 
    350     /**
    351      * Returns number of milliseconds since Jan. 1, 1970, midnight UTC.
    352      * Returns -1 if the date time information if not available.
    353      * @hide
    354      */
    355     public long getGpsDateTime() {
    356         String date = mAttributes.get(TAG_GPS_DATESTAMP);
    357         String time = mAttributes.get(TAG_GPS_TIMESTAMP);
    358         if (date == null || time == null) return -1;
    359 
    360         String dateTimeString = date + ' ' + time;
    361         if (dateTimeString == null) return -1;
    362 
    363         ParsePosition pos = new ParsePosition(0);
    364         try {
    365             Date datetime = sFormatter.parse(dateTimeString, pos);
    366             if (datetime == null) return -1;
    367             return datetime.getTime();
    368         } catch (IllegalArgumentException ex) {
    369             return -1;
    370         }
    371     }
    372 
    373     private static float convertRationalLatLonToFloat(
    374             String rationalString, String ref) {
    375         try {
    376             String [] parts = rationalString.split(",");
    377 
    378             String [] pair;
    379             pair = parts[0].split("/");
    380             double degrees = Double.parseDouble(pair[0].trim())
    381                     / Double.parseDouble(pair[1].trim());
    382 
    383             pair = parts[1].split("/");
    384             double minutes = Double.parseDouble(pair[0].trim())
    385                     / Double.parseDouble(pair[1].trim());
    386 
    387             pair = parts[2].split("/");
    388             double seconds = Double.parseDouble(pair[0].trim())
    389                     / Double.parseDouble(pair[1].trim());
    390 
    391             double result = degrees + (minutes / 60.0) + (seconds / 3600.0);
    392             if ((ref.equals("S") || ref.equals("W"))) {
    393                 return (float) -result;
    394             }
    395             return (float) result;
    396         } catch (NumberFormatException e) {
    397             // Some of the nubmers are not valid
    398             throw new IllegalArgumentException();
    399         } catch (ArrayIndexOutOfBoundsException e) {
    400             // Some of the rational does not follow the correct format
    401             throw new IllegalArgumentException();
    402         }
    403     }
    404 
    405     private native boolean appendThumbnailNative(String fileName,
    406             String thumbnailFileName);
    407 
    408     private native void saveAttributesNative(String fileName,
    409             String compressedAttributes);
    410 
    411     private native String getAttributesNative(String fileName);
    412 
    413     private native void commitChangesNative(String fileName);
    414 
    415     private native byte[] getThumbnailNative(String fileName);
    416 }
    417