New C++ interface for ExifTool

Started by Phil Harvey, December 02, 2013, 07:37:31 AM

Previous topic - Next topic

Phil Harvey

I have just released version 1.00 of a C++ interface for ExifTool.  I wrote this set of objects to make it easier to call exiftool from a C++ application.  It uses the exiftool -stay_open feature to keep the exiftool process running, which eliminates the startup latency associated with launching the exiftool application for each command.

I used standard C process and pipe controls so it should be fairly portable, and have tested it on Mac, Linux and Cygwin, although there is a niggling problem with the write pipe on Cygwin that makes it so you can't queue as many commands while exiftool is busy (it doesn't seem to work in non-blocking mode for some reason, but I'm hopeful that I'll find a way around this).

Anyway, I hope this turns out to be useful for someone.  It was more work than I anticipated.

It still needs some improvement in the documentation of the numerical error messages. Currently the only way to determine the meaning of an error is from the comments in the source code.  But I may wait to see if there is any interest in this before I do any more work on it.

- 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 ($).

Maurizio.Loreti

I wrote in the last days a C++ toy-program intended for geotagging my jpeg images; the GPS coordinates come directly from Google Maps, while the file names are given as arguments in the command line.  The main loop looks like:

  while (--argc) {
    if (access(*(++argv), W_OK) != 0) {
      cerr << progName << ": couldn't access file \"" << *argv
           << "\" for writing\n";
      perror("access");
      continue;
    }

    pET->WriteInfo(*argv, "-overwrite_original");

    if (pET->Complete() > 0) {
      cout << progName << ": geotagged file \"" << *argv
           << '\"' << endl;
    } else {
      cerr << progName << ": error from exiftool, on file \""
           << *argv << "\"\n";
      cerr << pET->GetError();
      break;
    }
  }


To test the error return from exiftool, I gave as file name the source of my program, geotag.cxx - and, to my surprise, the output has been:

...
geotag: geotagged file "geotag.cxx"


Shouldn't that code trigger an error?  Of course, exiftool -l geotag.cxx from the command line gives the output

...
Error
      Unknown file type


Thank you in advance for your help; and, if somebody is interested in the code, it is free (under GPL) - just ask.

Phil Harvey

#2
I should probably be more clear in the documentation, but Complete() returns a success code if the command was completed properly (ie. the communication with the exiftool process completed without errors), even if the command itself gave errors.  If Complete() returns a positive number, then GetOutput() and GetError() may be called to obtain the command output.

Determining whether the command was successful then involves a bit of work.  For this command, you could check GetOutput() for "# image files updated" and make sure that "#" is "1", or check GetError() for any line beginning with "Error:" since this will indicate a write failure.  I should probably add some helper functions to make this easier.

- Phil

Edit: I added a note to the documentation to empahsize this point.
...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 ($).

Maurizio.Loreti

Thank you for the very quick and exhaustive answer.  I changed the relevant statements to
Quote
    if (pET->Complete() > 0) {
      cout << progName << ": file \"" << *argv
           << "\":" << pET->GetOutput();
    } else {
and the output seems to be self-explanatory.

And, btw, thank you for maintaining such an useful package.

Phil Harvey

Just to make things a bit easier, I have added a new helper method called GetSummary() that allows easier access to the exiftool summary statistics if necessary from your program.

- 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 ($).

Maurizio.Loreti

Final version:

    if (pET->Complete() > 0) {
      cout << progName << ": ";
      if (pET->GetSummary(SUMMARY_IMAGE_FILES_UPDATED)) {
        cout << "file \"" << *argv << "\" geotagged" << endl;
      } else {
        cout << pET->GetError();
      }
    } else {
      cerr << progName
           << ": couldn't connect to exiftool process, file \""
           << *argv << "\"\n";
      cerr << pET->GetError();
      break;
    }

Thank you, for your impeccable interactions with my needs  ;D  (and sorry for the bad english)

Phil Harvey

Excellent.  Just one minor suggestion.  I would test for a number greater than 0 here:

      if (pET->GetSummary(SUMMARY_IMAGE_FILES_UPDATED) > 0) {

because GetSummary() will return -1 if the summary information isn't available.  For your command, this should never happen, but better safe than sorry.

Also, if Compete() returns <= 0, then GetError() will return NULL, so you can remove the GetError() call from inside your "else" block.

- 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 ($).

west suhanic

Hello All:

I just started using cpp_exiftool. I am trying to replicate the exiftool command:

./exiftool -b -previewImage -w %d%f.tif ~/IMG_0643.CR2

by writing a program using cpp_exiftool. I am able to replicate the above command using
the following:

    // create our ExifTool object
    ExifTool et;
    int cmdNum = et.Command("-b\n-previewImage\n-w\n%d%f.tif\n/home/wsuhanic/IMG_0643.CR2");
    if (cmdNum < 0) {
             puts("\nError extracting information\n");
             return -1;
    }
    puts("\nDone.\n");
    return 0;


Is there any other way to accomplish this by using calls other than Commnand? I have attempted to replicate the command by using the sample code from exampl4.cpp .
However this has not been successful. So I have a question. After I make the following call:

TagInfo *info = et->ImageInfo(argv[1], "-b\n-previewImage", 10);

which call do I make to actually write out the preview image? Using WriteInfo
is not, given my limited understanding, not the proper call.

All help is appreciated.

regards,

west suhanic

Phil Harvey

Hi West,

After the ImageInfo call, the "info" pointer will point to a linked list of TagInfo structures.  You would need to scan through them until you found one with the name "PreviewImage", then open an output file for writing and write the tag value like this:

    for (TagInfo *i=info; i; i=i->next) {
        if (strcmp(i->name, "PreviewImage")) continue;
        FILE *fp = fopen(outfile, "wb");
        fwrite(ti->value, 1, ti->valueLen, fp);
        fclose(fp);
        break;
    }
    delete info;


However, using the Command function is probably simpler since it will write the output file for you.  But note that the PreviewImage is JPG, not TIFF format.  Also, technically you should call Compete after Command, but in this case it isn't necessary because the ExifTool object will do this automatically when it is deleted.

- 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 ($).

west suhanic

Hi Phil:

Thank you for your answer. It is appreciated.

One other question. I also use exiv2. The following command:

exiv2 -pp ~/IMG_0643.CR2

lists three previews, two jpgs and one tiff.

I can use the following command to extract the tif file:

exiv2 -ep2 ~/IMG_0643.CR2

Can exiftool find this tiff preview image?
I, because of my limited knowledge of exiftool, cannot figure out how to do this.
I am hoping that the tiff preview file can be extracted using the cpp_exiftool.

regards,

west

Phil Harvey

Hi West,

ExifTool currently only extracts JPEG previews.

- 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 ($).