javajpegexiftwelvemonkeys

Read Image, modify metadata, and re-write image in pure Java


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:

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?


Solution

  • 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.