1

I need to be able to update image metadata (namely, tags, creator, description, comments) and do it within regular Exif and XMP. Most likely, i'll be reading the Exif, and writing XMP.

After much searching for a library that works ALSO for writing, I came across twelvemonkeys.

https://github.com/haraldk/TwelveMonkeys

This seemed to be promising. And indeed, with little effort I was able to read, already, the Exif containing description in one of my images. Not with the standard javax API, mind you, but with a twelvemonkeys API. That's fine with me. Whatever works!

At this point, I was happy to avoid the standard API as much as possible, as it seemed horribly convoluted and inefficient. I started about reading in my Exif, and coding the modification for my proof-of-concept. The idea being, the most efficient way to achieve what I want (quick and safe modification of metadata within JPEG files) was to perform the following steps:

  • Read all segments into a list
  • Find the segment that needs modification
  • Do that modification
  • Write all segments, sequentially, to a temp file
  • If all goes well, rename the original to later safely delete, rename the copy to the original name, and finally, delete the original file.

However, I was a bit dismayed when I discovered that there seems to be no implementation of

com.twelvemonkeys.imageio.metadata.Directory

which implements the methods

add(Entry)

and

remove(Object)

with anything other than a

throw new UnsupportedOperationException("Directory is read-only");

If this is not the way to efficiently (and safely) achieve what I want to do... Does anyone have a suggestion on how, in pure Java, I can do this?

svaens
  • 649
  • 7
  • 23

1 Answers1

0

Disclaimer: I designed and wrote the various metadata readers/writers mainly for internal use in my ImageIO library, and did not carefully consider use by third parties. For this reason, the API may not be "perfect" in this sense. But what you want to do should be perfectly doable. :-)


While the specific Directory implementations are indeed read-only, you can easily create your own subclass of AbstractDirectory that is mutable. Or just use any Collection<? extends Entry> you like and wrap it in a TIFFDirectory or IFD before writing. I prefer the latter, so I'll show that first.

Note that a typical JPEG Exif segment contains two IFDs, IFD0 for the main JPEG image, and IFD1 for the thumbnail. Thus you need to tread it as a CompoundDirectory:

CompoundDirectory exif = (CompoundDirectory) new TIFFReader().read(input);
List<Directory> ifds = new ArrayList<>;

for (int i = 0; i < exif.directoryCount(); i++) {
    List<Entry> entries = new ArrayList<>();

    for (Entry entry : exif.getDirectory(i)) {
        entries.add(entry);
    }

    // TODO: Do stuff with entries, remove, add, change, etc...

    ifds.add(new IFD(entries));
}

// Write Exif
new TIFFWriter().write(new TIFFDirectory(ifds), output);

You could also create your own mutable Directory:

public final class MutableDirectory extends AbstractDirectory {
    public MutableDirectory (final Collection<? extends Entry> entries) {
        super(entries);
    }

    public boolean isReadOnly() {
        return false;
    }

    // NOTE: While the above is all you need to make it *mutable*, 
    // TIFF/Exif does not allow entries with duplicate IDs, 
    // you need to handle this somehow. The below code is untested...
    @Override
    public boolean add(Entry entry) {
        Entry existing = getEntryById(entry.getIdentifier());

        if (existing != null) {
            remove(existing);
        }

        super.add(entry);
    }
}

The reason for not implementing mutable directories is exactly that the semantics for how entries are handled may differ from format to format.

Harald K
  • 26,314
  • 7
  • 65
  • 111
  • Hi Harald! Thank you for replying; Sorry for my late reply (time is limited). I tried out your code (solution 1), and apart from the fact that TIFFDirectory is package private (I had to extend AbstractCompoundDirectory myself instead) it all works (I never doubted ;) ), thank you for your answer, and all your hard work :) Perhaps you will consider making TIFFDirectory public? – svaens Mar 14 '19 at 16:10
  • @svaens Great! TIFFDirectory should be public in the latest release. – Harald K Mar 14 '19 at 16:29
  • A related follow-up question: I had intended on getting all Segments using JPEGSegmentUtil.readSegments(imageInputStream, JPEGSegmentUtil.ALL_SEGMENTS); However, I notice now that this does not return all segments (looking at the file manually (using bless) my count agrees with the count of the ArrayList 'markerSequence' in type JPEGMetadata, having read this using JPEGImageReader::getImageMetaData, which seems to have a full list of all segments in the whole image. Am I doing something obviously wrong (I'll look more deeply and if I have no success follow up with a new S.O.F question)? – svaens Mar 14 '19 at 16:38
  • Ahh...hmmm..... @haraldK actually, now I notice that the JPEGMetadata class contains only metadata marker segments ;) (the data of the others is null) Makes sense... which explains why the JPEGSegmentUtil.readSegments method only returns ... metadata segments. And i'm left looking for a more general ... "give me all Markers/Segments" method from which I can extract interesting EXIF Metadata segments.... and just write the others unchanged. – svaens Mar 14 '19 at 17:02
  • @svaens I believe the `JPEGSegmentUtil` should give you all segments if you specify `ALL_SEGMENTS`. I just didn't bother to implement `toString` for the tables etc... They are not really `null` or empty. Just kjeep in mind that there's not always Exif metadata present in all "JPEG" files. I'm no longer sure how your code looks, so it would really help if you posted a new question, with further details. – Harald K Mar 19 '19 at 12:03
  • sorry for the very late response. Again, I do very much appreciate you following that up with me; I am a little overwhelmed at home with my 1 yr old... As requested, here is my, hopefully not too confused, question here: https://stackoverflow.com/questions/55336863/is-there-anyway-to-configure-twelve-monkeys-jpegsegmentutil-segment-reader-to-re @haraldK – svaens Mar 25 '19 at 11:34