Monday, September 3, 2012

Exif metadata handler for Android (pure JAVA)

In this article I will introduce you a single, pure JAVA one-class-based Exif metadata driver, Which allows you to read or modify any information in Exif area of JPEG file.

If you are trying to work with Exif metadata of JPEG images,  you probably know that Android provides native ExifInterface. Sadly this class does not allow you to modify some tags, namely Copyright, Artist, User description, e.t.c. So I have decided to write my own simple Exif driver, which allows me to do anything I need to do with the Exif data.

The work with the Exif driver looks like this:
exifDriver = ExifDriver.getInstance(originalFile.getAbsolutePath());
if (exifDriver != null) {
        exifManager = new ExifManager(exifDriver, mContext);
        // We can modify some info
        exifManager.setArtist("The artist");
        exifManager.setUserComment("User comment ");
        // Save the image - do not use the same one !
        exifDriver.save(modifiedFile.getAbsolutePath());
        // Here we go - read info from the modified Exif file
        exifDriver = ExifDriver.getInstance(modifiedFile.getAbsolutePath());
        if (exifDriver != null) {
          exifManager = new ExifManager(exifDriver, mContext);
          logTag(exifManager.getExifRelated(ExifDriver.TAG_USER_COMMENT));

The complete code of an example activity follows. The activity let's user take image with camera, modifies Artist and User comment tags and then displays all tags, which are set in image. Code looks long, but it is almost because the number of different tags is so high.
public class MainActivity extends Activity {
  private final String LOGTAG = getClass().getName();
  private final int REQUEST_FROM_CAMERA = 1;
  private ProgressDialog progressDialog;
  private File origFile;
  private File modifiedFile;
  private Uri photoUri;
  private ImageView imagePreview;
  private Context mContext;

  /**
   * Usual onCreate - nothing special there
   */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mContext = this;
    setContentView(R.layout.activity_main);
    imagePreview = (ImageView) findViewById(R.id.imagePreview);
  }

  /**
   * Convenience method - test if there is any camera app in the device
   * 
   * @param intent
   * @return
   */
  private boolean isIntentAvailable(Intent intent) {
    final PackageManager mgr = getPackageManager();
    return mgr.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
        .size() > 0;
  }

  /**
   * Button handler. Create intent for camera, create temp file and run intent
   */
  public void handleCamera(View _view) {
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if (isIntentAvailable(intent)) {
      String storageState = Environment.getExternalStorageState();
      if (storageState.equals(Environment.MEDIA_MOUNTED)) {
        GregorianCalendar today = new GregorianCalendar();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
        origFile = new File(Environment.getExternalStorageDirectory()
            + File.separator + getText(R.string.app_name) + File.separator
            + format.format(today.getTime()) + ".jpg");
        modifiedFile = new File(Environment.getExternalStorageDirectory()
            + File.separator + getText(R.string.app_name) + File.separator
            + format.format(today.getTime()) + "_modif.jpg");
        try {
          if (origFile.exists()) {
            origFile.delete();
          }
          origFile.getParentFile().mkdirs();
          origFile.createNewFile();

        } catch (IOException e) {
          Log.e(LOGTAG, "Could not create file.", e);
        }
        photoUri = Uri.fromFile(origFile);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
        startActivityForResult(intent, REQUEST_FROM_CAMERA);
      } else {
        new AlertDialog.Builder(this)
            .setMessage(
                "External Storeage (SD Card) is required.\n\nCurrent state: "
                    + storageState).setCancelable(true).create().show();
      }
    } else {
      new AlertDialog.Builder(this).setTitle("No camera")
          .setMessage("There is no camera application to take the picture")
          .setPositiveButton("OK", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface arg0, int arg1) {
            }
          }).show();
    }
  }

  /**
   * Get result from camera
   */
  @Override
  public void onActivityResult(int requestCode, int resultCode, Intent _intent) {
    if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_FROM_CAMERA) {
      ImageDisplayer displayer = new ImageDisplayer();
      displayer.execute(origFile);
    }
  }

  /**
   * This is it.
   * 
   */
  private class ImageDisplayer extends AsyncTask<File, Drawable, Drawable> {
    @Override
    protected void onPreExecute() {
      progressDialog = ProgressDialog.show(mContext, "Loading", "");
    }

    private void logTag(String[] _keyValue) {
      if (_keyValue != null && _keyValue[0] != null && _keyValue[1] != null){
      Log.v(LOGTAG, _keyValue[0] + ": " + _keyValue[1]);
      }
    }

    @Override
    protected Drawable doInBackground(File... _path) {
      ExifManager exifManager;
      ExifDriver exifDriver = ExifDriver
          .getInstance(_path[0].getAbsolutePath());
      if (exifDriver != null) {
        exifManager = new ExifManager(exifDriver, mContext);
        // We can modify some info
        exifManager.setArtist("The artist");
        exifManager.setUserComment("User comment ");
        // Save the image - do not use the same one !
        exifDriver.save(modifiedFile.getAbsolutePath());
        // Here we go - read info from the modified Exif file
        exifDriver = ExifDriver.getInstance(modifiedFile.getAbsolutePath());
        if (exifDriver != null) {
          exifManager = new ExifManager(exifDriver, mContext);
          logTag(exifManager.getExifRelated(ExifDriver.TAG_USER_COMMENT));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_IMAGE_WIDTH));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_IMAGE_HEIGHT));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_BITS_PER_SAMPLE));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_COMPRESSION));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_PHOTOMETRIC_INTERPRETATION));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_ORIENTATION));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_SAMPLES_PER_PIXEL));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_PLANAR_CONFIGURATION));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_YCBCR_SUBSAMPLING));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_YCBCRPOSITIONING));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_XRESOLUTION));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_YRESOLUTION));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_RESOLUTION_UNIT));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_STRIP_OFFSETS));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_ROWS_PER_STRIP));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_STRIP_BYTECOUNTS));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_TRANSFER_FUNCTION));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_WHITE_POINT));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_PRIMARY_CHROMATICITIES));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_YCBCR_COEFICIENTS));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_REFERENCE_BLACK_WHITE));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_DATETIME));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_IMAGE_DESCRIPTION));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_MAKE));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_MODEL));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_SOFTWARE));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_ARTIST));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_COPYRIGHT));
          // IFD Exif tags
          logTag(exifManager.getExifRelated(ExifDriver.TAG_EXIF_VERSION));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_FLASHPIX_VERSION));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_COLOR_SPACE));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_COMPONENT_CONFIGURATION));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_COMPRESSED_BITS_PER_PIXEL));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_PIXEL_X_DIMENSION));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_PIXEL_Y_DIMENSION));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_MARKER_NOTE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_USER_COMMENT));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_RELATED_SOUND_FILE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_DATETIME_ORIGINAL));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_DATETIME_DIGITIZED));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_SUB_SEC_TIME));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_SUB_SEC_TIME_ORIGINAL));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_SUB_SEC_TIME_DIGITIZED));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_IMAGE_UNIQUE_ID));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_EXPOSURE_TIME));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_FNUMBER));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_EXPOSURE_PROGRAM));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_SPECTRAL_SENSITIVITY));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_ISO_SPEED_RATINGS));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_OECF));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_SHUTTER_SPEED_VALUE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_APERTURE_VALUE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_BRIGHTNESS_VALUE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_EXPOSURE_BIAS_VALUE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_MAX_APERTURE_VALUE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_SUBJECT_DISTANCE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_METERING_MODE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_LIGHT_SOURCE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_FLASH));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_FOCAL_LENGTH));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_SUBJECT_AREA));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_FLASH_ENERGY));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_SPATIAL_FREQUENCY_RESPONSE));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_FOCAL_PLANE_X_RESOLUTION));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_FOCAL_PLANE_Y_RESOLUTION));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_FOCAL_PLANE_RESOLUTION_UNIT));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_SUBJECT_LOCATION));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_EXPOSURE_INDEX));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_SENSING_METHOD));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_FILE_SOURCE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_SCENE_TYPE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_CFA_PATTERN));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_CUSTOM_RENDERED));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_EXPOSURE_MODE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_WHITE_BALANCE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_DIGITAL_ZOOM_RATIO));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_FOCAL_LENGTH_35MM_FILM));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_SCENE_CAPTURE_TYPE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_GAIN_CONTROL));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_CONTRAST));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_SATURATION));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_SHARPNESS));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_DEVICE_SETTING_DESCRIPTION));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_SUBJECT_DISTANCE_RANGE));
          // Thumbnail tags
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_IMAGE_WIDTH));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_IMAGE_HEIGHT));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_JPEG_INTERCHANGE_FORMAT));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_BITS_PER_SAMPLE));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_COMPRESSION));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_PHOTOMETRIC_INTERPRETATION));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_ORIENTATION));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_SAMPLES_PER_PIXEL));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_PLANAR_CONFIGURATION));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_YCBCR_SUBSAMPLING));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_YCBCRPOSITIONING));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_XRESOLUTION));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_YRESOLUTION));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_RESOLUTION_UNIT));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_STRIP_OFFSETS));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_ROWS_PER_STRIP));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_STRIP_BYTECOUNTS));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_TRANSFER_FUNCTION));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_WHITE_POINT));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_PRIMARY_CHROMATICITIES));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_YCBCR_COEFICIENTS));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_REFERENCE_BLACK_WHITE));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_DATETIME));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_MAKE));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_MODEL));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_SOFTWARE));
          // IFD GPS tags
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_VERSION_ID));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_LATITUDE_REF));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_LATITUDE));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_LONGITUDE_REF));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_LONGITUDE));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_ALTITUDE_REF));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_ALTITUDE));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_TIME_STAMP));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_SATELITES));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_STATUS));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_MEASURE_MODE));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_DOP));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_SPEED_REF));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_SPEED));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_TRACK_REF));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_TRACK));
          logTag(exifManager
              .getGpsRelated(ExifDriver.TAG_GPS_SLMG_DIRECTION_REF));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_SLMG_DIRECTION));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_MAP_DATUM));
          logTag(exifManager
              .getGpsRelated(ExifDriver.TAG_GPS_DEST_LATITUDE_REF));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_DEST_LATITUDE));
          logTag(exifManager
              .getGpsRelated(ExifDriver.TAG_GPS_DEST_LONGITUDE_REF));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_DEST_LONGITUDE));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_DEST_BEARING_REF));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_DEST_BEARING));
          logTag(exifManager
              .getGpsRelated(ExifDriver.TAG_GPS_DEST_DISTANCE_REF));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_DEST_DISTANCE));
          logTag(exifManager
              .getGpsRelated(ExifDriver.TAG_GPS_PROCESSING_METHOD));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_AREA_INFORMATION));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_DATE_STAMP));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_DIFFERENTIAL));
        } else {
          Log.e(LOGTAG,
              "Could not read exif info from the modified image - and this is bad");
        }
      } else {
        Log.e(LOGTAG, "Could not read exif info from an original image");
      }
      return Drawable.createFromPath(_path[0].getAbsolutePath());
    }

    @Override
    protected void onPostExecute(Drawable _drawable) {
      imagePreview.setImageDrawable(_drawable);
      progressDialog.dismiss();
    }
  }
}

You can look at the code of the driver here:
https://code.google.com/p/exif-driver/source/browse/src/rcs34/android/libs/ExifDriver/
or you can cone the code from git repository
git clone https://code.google.com/p/exif-driver/

The driver tries to conform with Exif Version 2.2. For more info see http://exif.org/ . It can handle both - pure Exif images and JFIF images with included Exif data. Of course there is a relatively high probability of bugs, because the class is quite young and not well tested. You are welcome to send me images, which the ExifDriver will not work with.

 Hope this will work for you.

3 comments:

  1. Hello! your class is really very helpful! But there are some problems.
    When I try save file, which didn't have ifdExif and ifdGPS folders, some exif tags got corrupted. In save function we need fill this info, before calculate size of each dir.

    ReplyDelete
  2. Thanks for the bug report. This time I'm quite busy with my dissertation work, but when it will be over, I plan to review the Exif driver code. I would really appreciate if you could send me some example (not working) file.

    ReplyDelete
  3. Harrah's Philadelphia Casino & Racetrack - JTM Hub
    Harrah's Philadelphia 수원 출장안마 Casino & 경주 출장마사지 Racetrack. 777 Harrah's Blvd, Chester, PA 김포 출장안마 18702. (800) 광명 출장안마 572-6640. 경상남도 출장샵 harrahs.phillycasino.com.

    ReplyDelete