Extract Tags and thumbnail at the same time

Started by tiwa, December 17, 2016, 11:56:28 AM

Previous topic - Next topic

tiwa

Hello
I try to read exif tags and thumbnail in the same call to the tool. I use stay_open=true. My command looks like:

-e
-charset filename=UTF8
-n
-S
-DateTimeOriginal
etc ...
-b
-ThumbnailImage
-w
thumb.jpg
c:/image/myImage.jpg
-execute

The output is

    1 output files created
{ready}

and i got a file in c:/image with name myImagethumb.jpg containing the tag values without the tag name and no thumbnail!

Is there a way to get the tag values in the output stream and the thumbnail in a file?

Thanks

**Edit**
My mistake: removing the -e flag makes the thumbnail to be present in the generated file. However, tag values and thumbnail bytes are all output in the generated file.

Hayo Baan

Wouldn't this do what you want (i.e., run the two steps separately)?

-e
-charset filename=UTF8
-n
-S
-DateTimeOriginal
etc ...
c:/image/myImage.jpg
-execute
-b
-ThumbnailImage
-w
thumb.jpg
c:/image/myImage.jpg
-execute
Hayo Baan – Photography
Web: www.hayobaan.nl

tiwa

Thank you for your answer. This should work but I want to avoid reading the image twice.

tiwa

I thing i have found a way to do that:

-charset filename=UTF8
-n
-S
-DateTimeOriginal
etc ...
-b
-j
-ThumbnailImage
-w x
c:/image/myImage.jpg
-execute

Notice tha new flag -j and the change in the -w.

The output stream then looks like this:

[{
  "SourceFile": "C:/image/myImage.jpg",
  "DateTimeOriginal": "2016:08:22 19:08:18",
  "Orientation": 3,
  ...
  "ThumbnailImage": "base64:/9j/2wCEABUOEBIQDRUSERIYFhUZHzQiHx0d ..."
}]
{ready}

Then it is just a matter of decoding the output stream. This also save a write/read of the thumbnail to the file system!

I will post some java code latter if this works as expected.

Hayo Baan

Hayo Baan – Photography
Web: www.hayobaan.nl

tiwa

#5
For those that could be interrested, here is a Java code using the java exiftool wrapper at https://github.com/rkalla/exiftool/blob/master/src/main/java/com/thebuzzmedia/exiftool/ExifTool.java. (Note: this class is quite old and the version checking is broken)

First add the following at the end of the Tag class:

        MIME_TYPE("MIMEType", String.class),
// This is not exactly an EXIF tag
THUMBNAIL_IMAGE("ThumbnailImage", Base64.class);


Add the following constant in the ExifTool class

/**
* Compiled {@link Pattern} used to split json output from ExifTool evenly
* into name/value pairs.
*/
private static final Pattern TAG_JSON_VALUE_PATTERN = Pattern
.compile("  \\\"(.*)\\\": (\\\"(.*)\\\",|(.*),|\\\"base64:(.*)\\\")");


Create the following method (this is an adaptation of the getImageMeta(...) method)

public Map<Tag, String> getImageMetaAndThumbnail(@NonNull final File image, @NonNull final Format format,
final Tag... tags) throws SecurityException, IOException {
if (tags == null || tags.length == 0) {
return new HashMap<>(0);
}

if (!image.canRead()) {
throw new SecurityException("Unable to read the given image [" + image.getAbsolutePath()
+ "], ensure that the image exists at the given path and that the executing Java process has permissions to read it.");
}

final long startTime = System.currentTimeMillis();

/*
* Create a result map big enough to hold results for each of the tags
* and avoid collisions while inserting.
*/
final Map<Tag, String> resultMap = new HashMap<>(tags.length * 3);

if (DEBUG) {
log("Querying %d tags from image: %s", tags.length, image.getAbsolutePath());
}

long exifToolCallElapsedTime = 0;

/*
* Using ExifTool in daemon mode (-stay_open True) executes different
* code paths below. So establish the flag for this once and it is
* reused a multitude of times later in this method to figure out where
* to branch to.
*/

// Clear process args
args.clear();

// Note: Windows Unicode file name are not supported in the stayOpen
// mode
if (stayOpen) {
log("\tUsing ExifTool in daemon mode (-stay_open True)...");

// Always reset the cleanup task.
resetCleanupTask();

/*
* If this is our first time calling getImageMeta with a stayOpen
* connection, set up the persistent process and run it so it is
* ready to receive commands from us.
*/
if (streams == null) {
streams = startDeamon();
}

log("\tStreaming arguments to ExifTool process...");

streams.writer.write("-charset filename=UTF8\n");

if (format == Format.NUMERIC) {
streams.writer.write("-n\n"); // numeric output
}

streams.writer.write("-S\n"); // compact output

for (final Tag tag : tags) {
streams.writer.write('-');
streams.writer.write(tag.name);
streams.writer.write("\n");
}

// Thumbnail
streams.writer.write("-b\n-j\n-ThumbnailImage\n-w x\n");

streams.writer.write(image.getAbsolutePath());
streams.writer.write("\n");

log("\tExecuting ExifTool...");

// Begin tracking the duration ExifTool takes to respond.
exifToolCallElapsedTime = System.currentTimeMillis();

// Run ExifTool on our file with all the given arguments.
streams.writer.write("-execute\n");
streams.writer.flush();
} else {
log("\tUsing ExifTool in non-daemon mode (-stay_open False)...");

/*
* Since we are not using a stayOpen process, we need to setup the
* execution arguments completely each time.
*/
args.add(EXIF_TOOL_PATH);

args.add("-charset filename=UTF8");

if (format == Format.NUMERIC) {
args.add("-n"); // numeric output
}

args.add("-S"); // compact output

for (final Tag tag : tags) {
args.add("-" + tag.name);
}

// Thumbnail
args.add("-b");
args.add("-j");
args.add("-ThumbnailImage");
args.add("-w x");

args.add(image.getAbsolutePath());

// Run the ExifTool with our args.
streams = startExifToolProcess(args);

// Begin tracking the duration ExifTool takes to respond.
exifToolCallElapsedTime = System.currentTimeMillis();
}

log("\tReading response back from ExifTool...");

String line = null;

while ((line = streams.reader.readLine()) != null) {

/*
* json open and close
*/
if (line.equals("[{") || line.equals("}]")) {
continue;
}

/*
* When using a persistent ExifTool process, it terminates its
* output to us with a "{ready}" clause on a new line, we need to
* look for it and break from this loop when we see it otherwise
* this process will hang indefinitely blocking on the input stream
* with no data to read.
*/
if (stayOpen && line.equals("{ready}")) {
break;
}

final Matcher matcher = TAG_JSON_VALUE_PATTERN.matcher(line);

if (matcher.find()) {
// Tag Name
final String tagName = matcher.group(1);
// String value?
String value = matcher.group(3);
if (value == null) {
// Numeric value?
value = matcher.group(4);
}
if (value == null) {
// Thumbnail?
value = matcher.group(5);
}

// Determine the tag represented by this value.
final Tag tag = Tag.forName(tagName);

/*
* Store the tag and the associated value in the result map only
* if we were able to map the name back to a Tag instance. If
* not, then this is an unknown/unexpected tag return value and
* we skip it since we cannot translate it back to one of our
* supported tags.
*/
if (tag != null) {
resultMap.put(tag, value);
log("\t\tRead Tag [name=%s, value=%s]", tagName, value);
}
}
}

// Print out how long the call to external ExifTool process took.
log("\tFinished reading ExifTool response in %d ms.", System.currentTimeMillis() - exifToolCallElapsedTime);

/*
* If we are not using a persistent ExifTool process, then after running
* the command above, the process exited in which case we need to clean
* our streams up since it no longer exists. If we were using a
* persistent ExifTool process, leave the streams open for future calls.
*/
if (!stayOpen) {
streams.close();
streams = null;
}

if (DEBUG) {
log("\tImage Meta Processed in %d ms [queried %d tags and found %d values]",
System.currentTimeMillis() - startTime, tags.length, resultMap.size());
}

return resultMap;
}


Then you can do (using Java8 java.util.Base64 class):

                private static final ExifTool EXIF_TOOL = new ExifTool(true);
                ...
                Map<Tag,String> valueMap = EXIF_TOOL.getImageMetaAndThumbnail(image, Format.NUMERIC, Tag.DATE_TIME_ORIGINAL, Tag.ORIENTATION);


// Thumbnail
                final BufferedImage thumbImg = null;
String base64 = valueMap.get(Tag.THUMBNAIL_IMAGE);
if (base64 != null) {
try {
final byte[] bytes = Base64.getDecoder().decode(base64);
if (bytes != null) {
thumbImg = ImageIO.read(new ByteArrayInputStream(bytes));
}
} catch (final Exception ignore) {
// Should not occurs!
}
}


Have fun!

Hayo Baan

Wow, that's quite some work, but I think it should work 8)

However, since you are going to program anyway, why don't you program this directly in Perl? After understanding the basic steps required to e.g. load the file and acquire the tags, it should be peanuts to get the thumbnail in one and the metadata in another output file.

But it's nice to see a Java interface exists as well, didn't know that!
Hayo Baan – Photography
Web: www.hayobaan.nl

tiwa

Not so much works (copy, paste and adapt the code!).
ExifTool is going to be used in a java program and the pictures could potentially be on lan. So it is important to minimize calls and access to file system.
My tests with your suggestion and my solution show real improvement.
Great to see how versatile is ExifTool.

Phil Harvey

Yes, -j and -X are two ways to get multiple tags with encoded binary output.

I think you will find that your line "-w x" can be removed.  It should be doing nothing (there is no "w x" option, so this is interpreted as a tag name, but the tag "w x" will never exist -- I'll see about adding a warning message for invalid tag names like this).

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

tiwa

Hi Phil
Right -w x do nothing and could be removed!
Thanks