Exiftool C++ Interface : how to copy exif tags from one image to another?

Started by xyz123, February 06, 2024, 03:05:09 PM

Previous topic - Next topic

xyz123

Hi there and thank you for the exiftool and C++ interface.

I am trying to copy all exif tags from one image to another using the C++ interface to the exiftool. Something similar to this (assuming both a.jpg and b.jpg exist locally):

exiftool -TagsFromFile a.jpg "-all:all>all:all" b.jpg

Here is what I tried:

/* g++ a.cpp cpp_exiftool/libcppexif.a -I cpp_exiftool/inc/ */
#include <string>
#include <iostream>
#include <ExifTool.h>

using namespace std;

int main(void){
    const char *infile = "a.jpg";
    const char *outfile = "b.jpg";
    ExifTool *et = new ExifTool();
    TagInfo *info = et->ImageInfo(infile);
    if( info == nullptr ){ cerr << "error with ImageInfo" << endl; exit(1); }
    delete et;

    et = new ExifTool();
    int ret = et->WriteInfo(outfile, "-m\n-overwrite_original", info);
    if( ret < 0 ){ cerr << "error calling WriteInfo" << endl; exit(1); }
    char *out = et->GetOutput(); if (out) cout << "stdout:" << endl << string(out) << endl;
    out = et->GetError(); if (out) cout << "stderr:" << endl << string(out) << endl;
    delete et;
    delete info;
}

I have also tried with empty options to WriteInfo() as well. The image b.jpg lacks X/Y Resolution to be 300 and there is nothing reported on stdout,stderr.
Please note that I am aiming to create 2 functions one to read tags (TagInfo*) and the other to write tags to an existing file. That's why I keep the two steps separate and recreate the et object.

My questions:

1) How to make above code copy exif tags from a.jpg to b.jpg
2) Any hints on what is the correct way to do error checking when calling ImageInfo() and WriteInfo().
3) My final aim is to be able to copy tags from/to various image formats, e.g. from tiff to jpeg and tell exiftool to exercise common sense and ignore incompatibilities, warnings, etc.

Thank you,
a.

Phil Harvey

From the documentation for WriteInfo():

Call Complete afterwards to wait for the command to complete before checking the output and error messages with GetOutput and GetError.

But this command will fail because you are also trying to write FileName, and the destination file exists.  One way around this is to add --system:all to avoid reading System tags when calling ImageInfo().

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

FrankB

How about this approach?

et->SetNewValue("tagsfromfile", argv[1]);
Assuming the first parm is the image you want to copy the tags from

et->WriteInfo(argv[2], "-overwrite_original\n-preserve\n");
The second parm is the jpg to receive the tags.
You can add some more parms as needed like "-All:all", "-overwrite_original" or "-ignoreMinorErrors" separated with a newline.

The advantage I see that there is only 1 call to ExifTool.

Since I'm no C++ programmer I verified the code by taking Example2 and modifying a little.

int main(int argc, char **argv)
{
    if (argc < 2) {
        cout << "Example2: Write metadata to an image." << endl;
        cout << "Please specify file name" << endl;
        return 1;
    }

    // create our ExifTool object
    ExifTool *et = new ExifTool();

    // set new values of tags to write
    et->SetNewValue("tagsfromfile", argv[1]);

    // write the information
    et->WriteInfo(argv[2], "-overwrite_original\n-preserve\n");

    // wait for exiftool to finish writing
    int result = et->Complete(10);

Phil Harvey

Sorry for the delay.  I need to think about this, and remind myself about how the interface works.  I'll post back after I have had a chance to do 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 ($).

Phil Harvey

OK.  I've looked into this.

Your technique works by grace and good luck. But it should continue to work in the future.

Internally, the C++ ExifTool object constructs command-line aruments in the form -TAG=VALUE from SetNewValue("TAG","VALUE") calls.

But note there is no documented -tagsFromFile=SRCFILE syntax.

However, at one time this syntax did exist, so ExifTool accepts it for backward compatibility, which is why your technique works.

Good on you for discovering this!  And if you didn't want to copy all tags from SRCFILE, you could add the tags to copy as options in the call to WriteInfo since these are placed last on the command line.  Very cool.

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

FrankB

Quote from: Phil Harvey on February 12, 2024, 10:26:02 AMGood on you for discovering this!

Well actually I did not discover it. If someone did discover it, it was Bogdan. I just looked up how it is done in ExifToolGui and tried to do it as much as possible the same way using the C++ interface.

But thanks for confirming!

Phil Harvey

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

xyz123

Thank you FrankB and Phil Harvey for your help.

I am now waiting via
et->Complete() on the
et->WriteInfo() but the following errs with "
Error: 'a.jpg' already exists - b.jpg", and so I am giving up on this approach and switching to specifying a parameters string with
-TagsFromFile\n<srcimgfile> and passing params to
et->WriteInfo(dstimgfile, params);. I can confirm that setting
et->SetNewValue("tagsfromfile", srcimgfile); (as suggested by FrankB) also works.

Here is what I use:
bool    copy_image_metadata_with_exiftool(
    const char *srcimgfile,
    const char *dstimgfile
){
    size_t sz = strlen(srcimgfile)+strlen(dstimgfile)+100;
    char *params = (char *)malloc(sz);
    if( params == nullptr ){ fprintf(stderr, "copy_image_metadata_with_exiftool() : error, failed to allocate %zu bytes.\n", sz); return false; }
    sprintf(params, "-overwrite_original\n-preserve\n-ignoreMinorErrors\n-TagsFromFile\n%s\n-all:all>all:all\n", srcimgfile);
    fprintf(stdout, "copy_image_metadata_with_exiftool() : spawning exiftool with these arguments:\n%s\n", params);
    ExifTool *et = new ExifTool();
    // this also works out of luck (see https://exiftool.org/forum/index.php?topic=15669.0)
    // if you set this then you do not need -TagsFromFile in params above:
    //et->SetNewValue("tagsfromfile", srcimgfile);
    et->WriteInfo(dstimgfile, params);
    // wait for exiftool to finish writing
    int result = et->Complete();
    if( result <= 0 ){ fprintf(stderr, "%s\n\ncopy_image_metadata_with_exiftool() : error, spawning exiftool and calling WriteInfo() with above arguments has failed.\n", params); return false; }

    delete et;
    free(params);
    return true;
}

Phil Harvey

Quote from: xyz123 on February 14, 2024, 07:48:45 AMbut the following errs with "
Error: 'a.jpg' already exists - b.jpg", and so I am giving up on this approach

Yes.  Because you are trying to write FileName.  Adding --system:all in the call to ImageInfo() as I mentioned will fix this.

But FrankB's technique is probably better.

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