0

I'm using the Apache Commons Imaging library (Java 8, you can find my code here) and I've come across a few problems with tags:

If I open the image info of e.g. this .jpg file with Win 10, there are "Origin" tags, e.g. "Authors" and "Date acquired":

enter image description here

You can find a list of EXIF tags here and it includes the "Authors" one and also an additional "Artist" one. The "Artist" tag seemed to have existed in the library at one point (source) but the apidocs don't list it anymore and ExifTagConstants.TIFF_TAG_ARTIST doesn't exist. Same thing with the "GPSAltitude" tag: It should exist according to the EXIF list but I can't seem to find it in the library.

I tried to use the "Maker" tag instead:

final TiffOutputDirectory exifDir = outputSet.getOrCreateExifDirectory();
exifDir.removeField(ExifTagConstants.EXIF_TAG_MAKER_NOTE);
exifDir.add(ExifTagConstants.EXIF_TAG_MAKER_NOTE, "Test Maker");

But ExifTagConstants.EXIF_TAG_MAKER_NOTE's type of TagInfoUndefineds doesn't seem to be valid for exifDir.add.

I also tried to add the date:

exifDir.removeField(ExifTagConstants.EXIF_TAG_DATE_TIME_DIGITIZED);
exifDir.add(ExifTagConstants.EXIF_TAG_DATE_TIME_DIGITIZED, "1970/01/01");

This just throws an exception:

org.apache.commons.imaging.ImageWriteException: Tag expects 20 byte(s), not 1

The only tag I've managed to successfully write so far is ExifTagConstants.EXIF_TAG_USER_COMMENT.

How do I use/write these tags (author/artist, date, altitude,...)?

Edit:

I managed to find two of the tags:

exifDir.add(MicrosoftTagConstants.EXIF_TAG_XPAUTHOR, "Test Author");//Author
exifDir.add(GpsTagConstants.GPS_TAG_GPS_ALTITUDE, new RationalNumber(3, 1));//Altitude

... but they aren't written into the file.

exifDir.add(ExifTagConstants.EXIF_TAG_USER_COMMENT, "my comment");

works, so I know it's actually writing tags, it just doesn't work for the two above. Any idea what's wrong?

Neph
  • 1,823
  • 2
  • 31
  • 69
  • The GPS tags need to be written inside the [GPS sub-IFD](https://www.awaresystems.be/imaging/tiff/tifftags/gpsifd.html). They are not directly part of the Exif IFD. For the MakerNote, that is usually undocumented proprietary [binary data for use by camera manufacturers](https://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif/makernote.html). For dates, you need to follow the [date time format (YYYY:MM:DD HH:MM:SS)](https://www.awaresystems.be/imaging/tiff/tifftags/datetime.html). – Harald K Aug 30 '19 at 12:38
  • @haraldK 1. `The GPS tags need to be written inside the GPS sub-IFD` - How do you do that with the library? `outputSet.setGPSInDegrees(longitude, latitude);` doesn't accept the altitude and `exifDir.add(GpsTagConstants.GPS_TAG_GPS_ALTITUDE, new RationalNumber(3, 1))` won't write anything into the file. 2. I see. The alternative would be `ExifTagConstants.EXIF_TAG_USER_COMMENT` but it's not written into the file. 3. Thanks, I got the date into the right format and it was saved in the file, you need a graphics program to see the data though because Windows' "properties" won't show that tag. – Neph Aug 30 '19 at 13:08
  • Sorry, I can't answer for the library part, I just know a bit about TIFF/Exif in general. – Harald K Aug 30 '19 at 13:10
  • No worries, thanks for your help! Do you know what tag I would use for "Date acquired" (see image in question)? – Neph Aug 30 '19 at 13:13
  • I recommend the TIFF resources I linked to above (they have a search and a tag reference). Also the ExifTool documentation is useful. – Harald K Aug 30 '19 at 13:14
  • The search only spits out 3 relevant "date" tags: "DateTimeOriginal" changes the "Date taken" setting, "DateTimeDigitized" is only visible using a graphics program and "GPSDateStamp" isn't added to the file - but none of them changes the "date acquired" tag. Thanks for the tip with the documentation, unfortunately it doesn't mention the "date acquired" either (only DateTimeOriginal, CreateDate and ModifyDate). :/ – Neph Aug 30 '19 at 13:31
  • If you just want to read metadata, consider using my [metadata-extractor](https://github.com/drewnoakes/metadata-extractor) library. – Drew Noakes Sep 10 '19 at 14:16
  • @DrewNoakes I looked at your library when I started working on this project, unfortunately it only reads metadata but isn't able to write it, which was important for the project, as mentioned in my question. That's why I went for Apache's library, which does both. – Neph Sep 10 '19 at 14:28
  • @Neph, makes sense! – Drew Noakes Sep 13 '19 at 13:15

3 Answers3

1

As @haraldK already mentioned: GPS data isn't part of the actual EFIX directory, which is also the case in the "Apache Commons Imaging" library.

So instead of writing the altitude with

double someDouble = 123.123456789;
int alt = (int) Math.round(someDouble*1000); //round to 3 decimal places
final TiffOutputDirectory exifDir = outputSet.getOrCreateExifDirectory();
exifDir.removeField(GpsTagConstants.GPS_TAG_GPS_ALTITUDE);
exifDir.add(GpsTagConstants.GPS_TAG_GPS_ALTITUDE, new RationalNumber(alt, 1000));

use:

final TiffOutputDirectory gpsDir = outputSet.getOrCreateGPSDirectory();
gpsDir.removeField(GpsTagConstants.GPS_TAG_GPS_ALTITUDE);
gpsDir.add(GpsTagConstants.GPS_TAG_GPS_ALTITUDE, new RationalNumber(alt, 1000));

This will write "123.123" into the "Altitude" field that can be viewed through the Windows Explorer (right-click on image -> Properties -> Details).

As for the other tags:

final TiffOutputDirectory exifDir = outputSet.getOrCreateExifDirectory();
final TiffOutputDirectory gpsDir = outputSet.getOrCreateGPSDirectory();
final TiffOutputDirectory rootDir = outputSet.getOrCreateRootDirectory();
final TiffOutputDirectory intDir = outputSet.getInteroperabilityDirectory(); //Not sure what this one is used for

//Writing into the "Authors" field
rootDir.removeField(MicrosoftTagConstants.EXIF_TAG_XPAUTHOR);
rootDir.add(MicrosoftTagConstants.EXIF_TAG_XPAUTHOR, "Me");

//Writing into the "Program Name" field
rootDir.removeField(ExifTagConstants.EXIF_TAG_SOFTWARE);
rootDir.add(ExifTagConstants.EXIF_TAG_SOFTWARE, "My App");

//Writing into the "Date taken" field
exifDir.removeField(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL);
exifDir.add(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL, "1970:01:01 12:34:56");

//Writing into the "Digitized Date"
exifDir.removeField(ExifTagConstants.EXIF_TAG_DATE_TIME_DIGITIZED);
exifDir.add(ExifTagConstants.EXIF_TAG_DATE_TIME_DIGITIZED, "1970:01:01 12:34:56");
//Not visible in "Properties" (use image editing software to see it)

//Writing the GPS time stamp
gpsDir.removeField(GpsTagConstants.GPS_TAG_GPS_DATE_STAMP);
gpsDir.add(GpsTagConstants.GPS_TAG_GPS_DATE_STAMP, "1970:01:01");
//Apparently only writes year & not visible in "Properties" (use image editing software to see it)

I haven't found the right tag for "Date Acquired" yet, so if someone knows the exact one, please comment on this answer.

Neph
  • 1,823
  • 2
  • 31
  • 69
0

Old way to go : You can looking at a specification of EXIF metadatas and you can implement some custom parsing to get them with How to extract EXIF metadatas from JPEG files.

Recent way to go : You can read EXIF datas with that ImageData class which is working very well

Now if you want to modify/update the images metadatas, you can use the documentation in which you will find the following method :

        /**
48       * This example illustrates how to add/update EXIF metadata in a JPEG file.
49       *
50       * @param jpegImageFile
51       *            A source image file.
52       * @param dst
53       *            The output file.
54       * @throws IOException
55       * @throws ImageReadException
56       * @throws ImageWriteException
57       */
58      public void changeExifMetadata(final File jpegImageFile, final File dst)
59              throws IOException, ImageReadException, ImageWriteException {
60  
61          try (FileOutputStream fos = new FileOutputStream(dst);
62                  OutputStream os = new BufferedOutputStream(fos);) {
63  
64              TiffOutputSet outputSet = null;
65  
66              // note that metadata might be null if no metadata is found.
67              final ImageMetadata metadata = Imaging.getMetadata(jpegImageFile);
68              final JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata;
69              if (null != jpegMetadata) {
70                  // note that exif might be null if no Exif metadata is found.
71                  final TiffImageMetadata exif = jpegMetadata.getExif();
72  
73                  if (null != exif) {
74                      // TiffImageMetadata class is immutable (read-only).
75                      // TiffOutputSet class represents the Exif data to write.
76                      //
77                      // Usually, we want to update existing Exif metadata by
78                      // changing
79                      // the values of a few fields, or adding a field.
80                      // In these cases, it is easiest to use getOutputSet() to
81                      // start with a "copy" of the fields read from the image.
82                      outputSet = exif.getOutputSet();
83                  }
84              }
85  
86              // if file does not contain any exif metadata, we create an empty
87              // set of exif metadata. Otherwise, we keep all of the other
88              // existing tags.
89              if (null == outputSet) {
90                  outputSet = new TiffOutputSet();
91              }
92  
93              {
94                  // Example of how to add a field/tag to the output set.
95                  //
96                  // Note that you should first remove the field/tag if it already
97                  // exists in this directory, or you may end up with duplicate
98                  // tags. See above.
99                  //
100                 // Certain fields/tags are expected in certain Exif directories;
101                 // Others can occur in more than one directory (and often have a
102                 // different meaning in different directories).
103                 //
104                 // TagInfo constants often contain a description of what
105                 // directories are associated with a given tag.
106                 //
107                 final TiffOutputDirectory exifDirectory = outputSet.getOrCreateExifDirectory();
108                 // make sure to remove old value if present (this method will
109                 // not fail if the tag does not exist).
110                 exifDirectory.removeField(ExifTagConstants.EXIF_TAG_APERTURE_VALUE);
111                 exifDirectory.add(ExifTagConstants.EXIF_TAG_APERTURE_VALUE,
112                         new RationalNumber(3, 10));
113             }
114 
115             {
116                 // Example of how to add/update GPS info to output set.
117 
118                 // New York City
119                 final double longitude = -74.0; // 74 degrees W (in Degrees East)
120                 final double latitude = 40 + 43 / 60.0; // 40 degrees N (in Degrees
121                 // North)
122 
123                 outputSet.setGPSInDegrees(longitude, latitude);
124             }
125 
126             // printTagValue(jpegMetadata, TiffConstants.TIFF_TAG_DATE_TIME);
127 
128             new ExifRewriter().updateExifMetadataLossless(jpegImageFile, os,
129                     outputSet);
130         }
131     }

I have just found a usefull example for your use case I think. You will know now how to find them, I am sure it will be easier to modify them :

@Override
public Exif getExif(Photo photo) throws ServiceException {
    File file = new File(photo.getPath());
    String exposure = "not available";
    double aperture = 0.0;
    double focalLength = 0.0;
    int iso = 0;
    boolean flash = false;
    String make = "not available";
    String model = "not available";
    double altitude = 0.0;

    try {
        final ImageMetadata metadata = Imaging.getMetadata(file);
        final JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata;

        if (jpegMetadata.findEXIFValueWithExactMatch(ExifTagConstants.EXIF_TAG_EXPOSURE_TIME)
                != null) {
            exposure = jpegMetadata
                    .findEXIFValueWithExactMatch(ExifTagConstants.EXIF_TAG_EXPOSURE_TIME)
                    .getValueDescription().split(" ")[0];
        }

        if (jpegMetadata.findEXIFValueWithExactMatch(ExifTagConstants.EXIF_TAG_APERTURE_VALUE)
                != null) {
            aperture = jpegMetadata
                    .findEXIFValueWithExactMatch(ExifTagConstants.EXIF_TAG_APERTURE_VALUE)
                    .getDoubleValue();
        }
        if (jpegMetadata.findEXIFValueWithExactMatch(ExifTagConstants.EXIF_TAG_FOCAL_LENGTH)
                != null) {
            focalLength = jpegMetadata
                    .findEXIFValueWithExactMatch(ExifTagConstants.EXIF_TAG_FOCAL_LENGTH)
                    .getDoubleValue();
        }
        if (jpegMetadata.findEXIFValueWithExactMatch(ExifTagConstants.EXIF_TAG_ISO) != null) {
            iso = jpegMetadata.findEXIFValueWithExactMatch(ExifTagConstants.EXIF_TAG_ISO)
                    .getIntValue();
        }

        if (jpegMetadata.findEXIFValueWithExactMatch(ExifTagConstants.EXIF_TAG_FLASH) != null) {
            flash = jpegMetadata.findEXIFValueWithExactMatch(ExifTagConstants.EXIF_TAG_FLASH)
                    .getIntValue() != 0;
        }

        if (jpegMetadata.findEXIFValueWithExactMatch(TiffTagConstants.TIFF_TAG_MAKE) != null) {
            make = jpegMetadata.findEXIFValueWithExactMatch(TiffTagConstants.TIFF_TAG_MAKE)
                    .getValueDescription();
        }
        if (jpegMetadata.findEXIFValueWithExactMatch(TiffTagConstants.TIFF_TAG_MODEL) != null) {
            model = jpegMetadata.findEXIFValueWithExactMatch(TiffTagConstants.TIFF_TAG_MODEL)
                    .getValueDescription();
        }

        if (jpegMetadata.findEXIFValueWithExactMatch(GpsTagConstants.GPS_TAG_GPS_ALTITUDE)
                != null) {
            altitude = jpegMetadata
                    .findEXIFValueWithExactMatch(GpsTagConstants.GPS_TAG_GPS_ALTITUDE)
                    .getDoubleValue();
        }

        return new Exif(photo.getId(), exposure, aperture, focalLength, iso, flash, make, model,
                altitude);
    } catch (IOException | ImageReadException e) {
        throw new ServiceException(e.getMessage(), e);
    }
}
BendaThierry.com
  • 2,080
  • 1
  • 15
  • 17
  • Sorry, this doesn't answer or even address the problems in my question. I know how to look for the metadata (just added a link to my other question with my code) and I know how to write it (check out the code I posted). The problems I'm experiencing are that 1. EXIF tags that should exist (I already linked the same website in my question) aren't there (e.g. "artist") and 2. from the ones that are there, I've only managed to have a single one written into the file, the others either aren't being written or simply throw an exception. – Neph Aug 30 '19 at 11:23
  • OK, and the way @see https://stackoverflow.com/a/36873897/390462 you have to remove the EXIF field and to add it after. You seem to be unable to overwrite an existing field. – BendaThierry.com Aug 30 '19 at 11:33
  • That's exactly the way I'm doing it (please look at the code in my question) and I also tested it with `exifDir.add(ExifTagConstants.EXIF_TAG_SOFTWARE, "SomeKind");` (as suggested in the answer you linked) just now: The new image has the "my comment" tag (`exifDir.add(ExifTagConstants.EXIF_TAG_USER_COMMENT, "my comment");`) but the "Program name" field is blank. That's the exact problem I'm experiencing with the other tags too. – Neph Aug 30 '19 at 11:41
  • Okay, I just tested it again with an image that already has the "Program Name" tag set to "COOLPIX .....": I removed the tag with `exifDir.removeField(ExifTagConstants.EXIF_TAG_SOFTWARE);`, then re-added it with `exifDir.add(ExifTagConstants.EXIF_TAG_SOFTWARE, "SomeKind");`. The result: The image is changed (has a different date) but the tag is still set to "COOLPIX ....."! Removing the tag without re-adding it doesn't do anything, the tag in the image info is still the same instead of being blank. – Neph Aug 30 '19 at 11:50
  • Using MicrosoftTagConstants instead of ExifTagConstants ? – BendaThierry.com Aug 30 '19 at 11:55
  • E.g. `MicrosoftTagConstants.EXIF_TAG_XPAUTHOR` doesn't add anything to the image either, no. You can see everything I've tried so far in my question. Do I maybe need to use a different writer (instead of `ExifRewriter`) for non-'ExifTagConstant' tags? – Neph Aug 30 '19 at 11:56
  • Strange bug I think. Look at your file, is it in readonly mode ? I have tested this with a file from my computer to change metadatas and things go well to change any fields : @see https://github.com/apache/commons-imaging/blob/master/src/test/java/org/apache/commons/imaging/examples/WriteExifMetadataExample.java – BendaThierry.com Aug 30 '19 at 12:27
  • No, none of the files are in readonly mode. Please also test it with `final TiffOutputDirectory exifDir = outputSet.getOrCreateExifDirectory();` in combination with `exifDir.add(MicrosoftTagConstants.EXIF_TAG_XPAUTHOR, "Test Author");`. Does that work for you with the image I linked in the question? – Neph Aug 30 '19 at 12:31
  • Doing many things right now, I will come back here tonight. Hope you have found a solution since. – BendaThierry.com Aug 30 '19 at 13:40
  • I found a solution for everything but the "acquired date" field but I'm still interested to see if it worked for you. Did you test it? – Neph Sep 02 '19 at 12:56
0

I found the Artist Tag:

exifDirectory.add(TiffTagConstants.TIFF_TAG_ARTIST, "JANE DOUGH");

Haven't tested whether this will work though.

EDIT:

It works well.

Online Exif Viewer Image

The image shows that the Artist Tag works.