Class ExifTool

java.lang.Object
com.thebuzzmedia.exiftool.ExifTool
All Implemented Interfaces:
AutoCloseable

public class ExifTool extends Object implements AutoCloseable
Class used to provide a Java-like interface to Phil Harvey's excellent, Perl-based ExifTool. There are a number of other basic Java wrappers to ExifTool available online, but most of them only abstract out the actual Java-external-process execution logic and do no additional work to make integration with the external ExifTool any easier or intuitive from the perspective of the Java application written to make use of ExifTool. This class was written in order to make integration with ExifTool inside of a Java application seamless and performant with the goal being that the developer can treat ExifTool as if it were written in Java, garnering all of the benefits with none of the added headache of managing an external native process from Java. Phil Harvey's ExifTool is written in Perl and runs on all major platforms (including Windows) so no portability issues are introduced into your application by utilizing this class.

Usage

Assuming ExifTool is installed on the host system correctly and either in the system path, using this class to communicate with ExifTool is as simple as creating an instance using ExifToolBuilder:

     ExifTool tool = new ExifToolBuilder().build();
 
This mode assume that:
  • Path is set as an environment variable (i.e. -Dexiftool.withPath=/usr/local/exiftool/bin/exiftool).
  • Or globally available.
If you want to set the path of ExifTool, you can also specify it during creation:

     ExifTool tool = new ExifToolBuilder()
         .withPath("/usr/local/exiftool/bin/exiftool")
         .build();
 
Once created, usage is as simple as making calls to getImageMeta(File, Collection) or getImageMeta(File, Format, Collection) with a list of Tag you want to pull values for from the given image. In this default mode, calls to getImageMeta(java.io.File) will automatically start an external ExifTool process to handle the request. After ExifTool has parsed the tag values from the file, the external process exits and this class parses the result before returning it to the caller. Results from calls to getImageMeta(java.io.File) are returned in a Map with the StandardTag values as the keys and String values for every tag that had a value in the image file as the values. StandardTags with no value found in the image are omitted from the result map. While each StandardTag provides a hint at which format the resulting value for that tag is returned as from ExifTool (see Tag.parse(String)), that only applies to values returned with an output format of StandardFormat.NUMERIC and it is ultimately up to the caller to decide how best to parse or convert the returned values. The StandardTag Enum provides the Tag.parse(String)} convenience method for parsing given `String` values according to the Tag hint automatically for you if that is what you plan on doing, otherwise feel free to handle the return values anyway you want.

ExifTool -stay_open Support

ExifTool 8.36 added a new persistent-process feature that allows ExifTool to stay running in a daemon mode and continue accepting commands via a file or stdin. This new mode is controlled via the -stay_open True/False command line argument and in a busy system that is making thousands of calls to ExifTool, can offer speed improvements of up to 60x (yes, really that much). This feature was added to ExifTool shortly after user Christian Etter discovered the overhead for starting up a new Perl interpreter each time ExifTool is loaded accounts for roughly 98.4% of the total runtime. Support for using ExifTool in daemon mode is enabled by explicitly calling ExifToolBuilder.enableStayOpen() method. Calling this method will create an instance of ExifTool with StayOpenStrategy execution strategy. Because this feature requires ExifTool 8.36 or later, this class will actually verify support for the feature in the version of ExifTool before successfully instantiating the class and will notify you via an UnsupportedFeatureException if the native ExifTool doesn't support the requested feature. In the event of an UnsupportedFeatureException, the caller can either upgrade the native ExifTool upgrade to the version required or simply avoid using that feature to work around the exception.

Automatic Resource Cleanup

When stay_open mode is used, there is the potential for leaking both host OS processes (native exiftool processes) as well as the read/write streams used to communicate with it unless close() is called to clean them up when done. Fortunately, this library provides an automatic cleanup mechanism that runs, by default, after 10 minutes of inactivity to clean up those stray resources. The inactivity period can be controlled by modifying the exifTool.processCleanupDelay system variable. A value of 0 or less disabled the automatic cleanup process and requires you to cleanup ExifTool instances on your own by calling close() manually. You can also set this delay manually using ExifToolBuilder:

     ExifTool exifTool = new ExifToolBuilder()
         .enableStayOpen(60000) // Try to clean resources once per minutes.
         .build();
 
Any class activity by way of calls to getImageMeta will always reset the inactivity timer, so in a busy system the cleanup thread could potentially never run, leaving the original host ExifTool process running forever (which is fine). This design was chosen to help make using the class and not introducing memory leaks and bugs into your code easier as well as making very inactive instances of this class light weight while not in-use by cleaning up after themselves. The only overhead incurred when opening the process back up is a 250-500ms lag while launching the VM interpreter again on the first call (depending on host machine speed and load).

Reusing a "closed" ExifTool Instance

If you or the cleanup thread have called close() on an instance of this class, cleaning up the host process and read/write streams, the instance of this class can still be safely used. Any followup calls to getImageMeta will simply re-instantiate all the required resources necessary to service the call. This can be handy behavior to be aware of when writing scheduled processing jobs that may wake up every hour and process thousands of pictures then go back to sleep. In order for the process to execute as fast as possible, you would want to use ExifTool in daemon mode (use ExifToolBuilder.enableStayOpen()) and when done, instead of close()-ing the instance of this class and throwing it out, you can keep the reference around and re-use it again when the job executes again an hour later.

Performance

Extra care is taken to ensure minimal object creation or unnecessary CPU overhead while communicating with the external process. Patterns used to split the responses from the process are explicitly compiled and reused, string concatenation is minimized, Tag name lookup is done via a static final Map shared by all instances and so on. Additionally, extra care is taken to utilize the most optimal code paths when initiating and using the external process, for example, the ProcessBuilder.command(List) method is used to avoid the copying of array elements when ProcessBuilder.command(String...) is used and avoiding the (hidden) use of StringTokenizer when Runtime.exec(String) is called. All of this effort was done to ensure that imgscalr and its supporting classes continue to provide best-of-breed performance and memory utilization in long running/high performance environments (e.g. web applications).

Thread Safety

Instances of this class are Thread-safe (note that version 1.1 of exiftool was not Thread-safe):
  • If stay_open is disabled, then a one-shot process is used for each command.
  • Otherwise a single process is open and read/write operations are streamed to this process. In this case, each operation will be synchronized to ensure thread-safety.
If you want to use ExifTool in a multi-threaded environment, I strongly suggest you to use a pool size: this is available out of the box. With this configuration, you will get at most a number of open process equal to the size of the pool. If a thread is trying to parse an image and no process is available, then ExifTool will wait for a process to be available. Here is the configuration to get a pool:

     ExifTool exifTool = new ExifToolBuilder()
         .withPoolSize(10) // Allow 10 exiftool process in parallel
         .build();
 

Why ExifTool?

ExifTool is written in Perl and requires an external process call from Java to make use of. While this would normally preclude a piece of software from inclusion into the imgscalr library (more complex integration), there is no other image metadata piece of software available as robust, complete and well-tested as ExifTool. In addition, ExifTool already runs on all major platforms (including Windows), so there was not a lack of portability introduced by providing an integration for it. Allowing it to be used from Java is a boon to any Java project that needs the ability to read/write image-metadata from almost any image or video file format.

Alternatives

If integration with an external Perl process is something your app cannot do and you still need image metadata-extraction capability, Drew Noakes has written the 2nd most robust image metadata library I have come across: Metadata Extractor that you might want to look at.
Since:
1.1
Author:
Riyad Kalla ([email protected]), Mickael Jeanroy
  • Method Details

    • close

      public void close() throws Exception
      This method should be used to clean previous execution.
      NOTE: Calling this method prevent this instance of ExifTool from being re-used.
      Specified by:
      close in interface AutoCloseable
      Throws:
      Exception - If an error occurred while closing exiftool client.
    • stop

      public void stop() throws Exception
      Stop `ExifTool` client. NOTE: Calling this method does not preclude this instance of ExifTool from being re-used, it merely disposes of the native and internal resources until the next call to getImageMeta causes them to be re-instantiated.
      Throws:
      Exception - If an error occurred while stopping exiftool client.
    • isRunning

      public boolean isRunning()
      This method is used to determine if there is currently a running ExifTool process associated with this class.
      Any dependent processes and streams can be shutdown using close() and this class will automatically re-create them on the next call to getImageMeta(java.io.File) if necessary.
      Returns:
      true if there is an external ExifTool process is still running otherwise returns false.
    • getVersion

      public Version getVersion()
      Exiftool version pointed by this instance.
      Returns:
      Version.
    • getImageMeta

      public Map<Tag,String> getImageMeta(File image) throws IOException
      Parse image metadata for all tags. Output format is numeric.
      Parameters:
      image - Image.
      Returns:
      Pair of tag associated with the value.
      Throws:
      IOException - If something bad happen during I/O operations.
      NullPointerException - If one parameter is null.
      IllegalArgumentException - If list of tag is empty.
      UnreadableFileException - If image cannot be read.
    • getImageMeta

      public Map<Tag,String> getImageMeta(File image, Format format) throws IOException
      Parse image metadata for all tags.
      Parameters:
      image - Image.
      format - Output format.
      Returns:
      Pair of tag associated with the value.
      Throws:
      IOException - If something bad happen during I/O operations.
      NullPointerException - If one parameter is null.
      IllegalArgumentException - If list of tag is empty.
      UnreadableFileException - If image cannot be read.
    • getImageMeta

      public Map<Tag,String> getImageMeta(File image, ExifToolOptions options) throws IOException
      Parse image metadata for all tags.
      Parameters:
      image - Image.
      options - ExifTool options.
      Returns:
      Pair of tag associated with the value.
      Throws:
      IOException - If something bad happen during I/O operations.
      NullPointerException - If one parameter is null.
      IllegalArgumentException - If list of tag is empty.
      UnreadableFileException - If image cannot be read.
    • getImageMeta

      public Map<Tag,String> getImageMeta(File image, Collection<? extends Tag> tags) throws IOException
      Parse image metadata. Output format is numeric.
      Parameters:
      image - Image.
      tags - List of tags to extract.
      Returns:
      Pair of tag associated with the value.
      Throws:
      IOException - If something bad happen during I/O operations.
      NullPointerException - If one parameter is null.
      IllegalArgumentException - If list of tag is empty.
      UnreadableFileException - If image cannot be read.
    • getImageMeta

      public Map<Tag,String> getImageMeta(File image, Format format, Collection<? extends Tag> tags) throws IOException
      Parse image metadata.
      Parameters:
      image - Image.
      format - Output format.
      tags - List of tags to extract.
      Returns:
      Pair of tag associated with the value.
      Throws:
      IOException - If something bad happen during I/O operations.
      NullPointerException - If one parameter is null.
      IllegalArgumentException - If list of tag is empty.
      UnreadableFileException - If image cannot be read.
    • getImageMeta

      public Map<Tag,String> getImageMeta(File image, ExifToolOptions options, Collection<? extends Tag> tags) throws IOException
      Parse image metadata.
      Parameters:
      image - Image.
      options - ExifTool options.
      tags - List of tags to extract.
      Returns:
      Pair of tag associated with the value.
      Throws:
      IOException - If something bad happen during I/O operations.
      NullPointerException - If one parameter is null.
      IllegalArgumentException - If list of tag is empty.
      UnreadableFileException - If image cannot be read.
    • setImageMeta

      public void setImageMeta(File image, Map<? extends Tag,String> tags) throws IOException
      Write image metadata. Default format is numeric.
      Parameters:
      image - Image.
      tags - Tags to write.
      Throws:
      IOException - If an error occurs during write operation.
    • setImageMeta

      public void setImageMeta(File image, Format format, Map<? extends Tag,String> tags) throws IOException
      Write image metadata in a specific format.
      Parameters:
      image - Image.
      format - Specified format.
      tags - Tags to write.
      Throws:
      IOException - If an error occurs during write operation.
    • setImageMeta

      public void setImageMeta(File image, ExifToolOptions options, Map<? extends Tag,String> tags) throws IOException
      Write image metadata in a specific format.
      Parameters:
      image - Image.
      options - ExifTool options.
      tags - Tags to write.
      Throws:
      IOException - If an error occurs during write operation.