Support #1290

write exif to a libgphoto2 buffer image

Added by Nacho Sánchez Moreno 4 days ago. Updated about 17 hours ago.

Status:ClosedStart date:21 Apr 2017
Priority:NormalDue date:
Assignee:Robin Mills% Done:

100%

Category:not-a-bugEstimated time:4.00 hours
Target version:0.26

Description

Hello,

I'm trying to write exif data to a libgphoto2 buffer.

unsigned long int file_size = 0;
const char *file_data = NULL;

with gp_file_get_data_and_size(file, &file_data, &file_size) i retrive the buffer and size for an NEF image (nikkon d810)
After i try:

Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const Exiv2::byte*)file_data, file_size);

Exiv2::ExifData exifData;
exifData["Exif.Image.Software"] = this->_metadataExif.Software;
exifData["Exif.Image.Artist"] = this->_metadataExif.Artist;
exifData["Exif.Image.Copyright"] = this->_metadataExif.Copyright;

// Set EXIF data and write it to the file
image->setExifData(exifData);
image->writeMetadata();

file_size = image->io().size();
image->io().seek(0,Exiv2::BasicIo::beg);
Exiv2::DataBuf buff = image->io().read(file_size);

// ---------------------------------------------------------
// Write the buff to disk
FILE * filen = ::fopen(fname.c_str(), "w");
::fwrite(buff.pData_, file_size, 1, filen);
::fclose(filen);

gp_file_free(file);

them, the exif metadata information is ok, but the file is corrupted.

where is the problem?
thanks

History

#1 Updated by Robin Mills 4 days ago

  • Category set to not-a-bug
  • Status changed from New to Assigned
  • Assignee set to Robin Mills
  • Target version set to 0.26

Nacho

I don't know libgphoto. I believe you are using Exiv2 to manipulate the image in memory and then use fopen/fwrite/fclose to write the image to disk. I believe this should work, so without your libgphoto code, I can't say why this isn't working for you.

I've written the following code to simulate libgphoto using our old friends malloc/stat/fread:

// g++ testExiv2MemIO.cpp -lexiv2 -o testExiv2MemIO

#include <exiv2/exiv2.hpp>

#include <sys/stat.h>
#include <unistd.h>

int main(int argc,const char* argv[])
{
    const char*           program  = argv[0];
    if ( argc != 3 )      return printf("syntax %s in-path out-path\n",program);

    const char*           path_in  = argv[1];
    const char*           path_out = argv[2];

    struct stat           buf      ;
    if (    stat(path_in,&buf) ) return printf("path %s does not exist\n",path_in);

    // allocate a memory buffer into which to read path_in
    unsigned long int     file_size = buf.st_size;
    const char*           file_data = (const char*) ::malloc(file_size);
    if ( !file_data)      return printf("unable to allocate %lu bytes for %s",file_size,path_in);

    // read path_in into a memory buffer
    FILE*                 file_in   = ::fopen(path_in,"rb");
    if ( !file_in )       return printf("unable to open %s\n",path_in);
    int                   n         = fread( (void*) file_data,1,file_size,file_in);
    printf("fread %ld bytes from %s\n",file_size,path_in);

    // create an Exiv2 image from the buffer
    Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const Exiv2::byte*)file_data, file_size);

    // modify the Exiv2 metadata
    Exiv2::ExifData       exifData;
    exifData["Exif.Image.Software"] = program;
    exifData["Exif.Image.Artist"]   = program;

    // write metadata to memory
    image->setExifData(exifData);
    image->writeMetadata();

    // recover Exiv2 image buff
    file_size = image->io().size();
    image->io().seek(0,Exiv2::BasicIo::beg);
    Exiv2::DataBuf buff = image->io().read(file_size);

    // Write buff to disk
    FILE *                file_out = ::fopen(path_out, "w");
    n = ::fwrite((void*)  buff.pData_, 1,file_size, file_out);
    ::fclose(file_out);
    printf("fwrite %ld bytes to %s\n",file_size,path_out);

    ::free((void*)file_data);

    return 0;
}
This seems to work fine:
559 rmills@rmillsmbp:~/temp $ cp ~/Pictures/Wallpapers/Stonehenge\,England.jpg Stonehenge.jpg
560 rmills@rmillsmbp:~/temp $ exiv2 -pa --grep Software --grep Artist Stonehenge.jpg 
Exif.Image.Software                          Ascii      10  Ver.1.00 
561 rmills@rmillsmbp:~/temp $ ./testExiv2MemIO Stonehenge.jpg S.jpg
fread 6780056 bytes from Stonehenge.jpg
fwrite 6764223 bytes to S.jpg
562 rmills@rmillsmbp:~/temp $ exiv2 -pa --grep Software --grep Artist S.jpg 
Exif.Image.Software                          Ascii      17  ./testExiv2MemIO
Exif.Image.Artist                            Ascii      17  ./testExiv2MemIO
563 rmills@rmillsmbp:~/temp $ 
I've tried the code on a .NEF from my Nikon D5300:
564 rmills@rmillsmbp:~/temp $ cp /Photos/2016/Raw/DSC_0001.NEF .
565 rmills@rmillsmbp:~/temp $ ./testExiv2MemIO DSC_0001.NEF D.NEF
fread 22330428 bytes from DSC_0001.NEF
fwrite 18158964 bytes to D.NEF
566 rmills@rmillsmbp:~/temp $ exiv2 -pa --grep Software --grep Artist D.NEF 
Exif.Image.Software                          Ascii      17  ./testExiv2MemIO
Exif.Image.Artist                            Ascii      17  ./testExiv2MemIO
567 rmills@rmillsmbp:~/temp $ 

I suspect the issue lies in the libgphoto buffer. A modified strategy for you is to write the libgphoto buffer to disk. Then use Exiv2 to open, read, modify and write your file. Can you try this?

If you're still stuck, please share your libgphoto code and I will download libgphoto and build your code. When we are "both on the same page", I believe we'll quickly resolve this matter.

Robin

#2 Updated by Nacho Sánchez Moreno 4 days ago

Hi Robin,

Thanks a lot for your answer.

Yes, i tryed too with the strategy of to write the libgphoto2 to disk, and i've had some problems depending the code, with errors like:

basicio.cpp:598: virtual long int Exiv2::FileIo::write(Exiv2::BasicIo&): Assertion `p_->fp_ != 0' failed when i use fileio.write(image->io());

or:

Program received signal SIGSEGV, Segmentation fault.
0x766e5404 in fwrite () from /lib/arm-linux-gnueabihf/libc.so.6 when i use ::fwrite(buff.pData_, buffersize, 1, fileio);

Anyway I will continue to insist on the initial solution, ie using the image in memory because i've retrive the image from the camera previously and i think that is the moment to write de exif data before to write to disk.

thanks again

#3 Updated by Robin Mills 4 days ago

  • Status changed from Assigned to Resolved
  • % Done changed from 0 to 100
  • Estimated time set to 1.00

You are asking me to debug a scenario that I cannot reproduce. Can I ask you to strip your libgphoto code to the minimum and let me build/reproduce your error.

I'm going out for three hours. Drop me your code and together, we can fix this today.

#4 Updated by Nacho Sánchez Moreno 4 days ago

Hi,

Thank you very much,

I also have to leave, and probably until Tuesday can not continue. I think I'm not taking precaution with memory somewhere. Because the files it generates them, but all of the same size and corrupted, but with the exif data.

At the finaly of the code i've comment some tries about the second scenario, ie save to disk previously

Basically the code is:

CameraFile *file;

ret = gp_file_new(&file);

if (ret != GP_OK)
    continue;

// get de photo from the camera
ret = gp_camera_file_get(this->_camera, cPath.folder, cPath.name, GP_FILE_TYPE_NORMAL, file, this->_ctx);

if (ret != GP_OK)
    continue;

// get buffer and size
ret = gp_file_get_data_and_size (file, &file_data, &file_size);
if (ret != GP_OK)
    continue;

try {
    // Open image file (WITH YOUR NOTES)
    Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const Exiv2::byte*)file_data, file_size);

    Exiv2::ExifData exifData;
    exifData["Exif.Image.Software"] = this->_metadataExif.Software; // IT'S OK
    exifData["Exif.Image.Artist"] = this->_metadataExif.Artist; // IT'S OK
    exifData["Exif.Image.Copyright"] = this->_metadataExif.Copyright; // IT'S OK

    // write metadata to memory
    image->setExifData(exifData);
    image->writeMetadata();

    // recover Exiv2 image buff
    file_size = image->io().size();
    image->io().seek(0,Exiv2::BasicIo::beg);
    Exiv2::DataBuf buff = image->io().read(file_size);

    // The file's name:
    fname = this->_pName + "/" + fname;

    // ---------------------------------------------------------
    // Write the buff to disk
    FILE * file_out = ::fopen(fname.c_str(), "w"); 
    ::fwrite((void*) buff.pData_, 1, file_size, file_out);
    ::fclose(file_out); 
}
catch (Exiv2::AnyError& e) {
    std::cout << "Caught Exiv2 exception '" << e << "'\n";
}

fname = this->_pName + "/" + fname;

//ret = gp_file_save(file, fname.c_str());

gp_file_free(file);

//if (ret != GP_OK)
//    continue;

/*
Exiv2::DataBuf buf = Exiv2::readFile(fname.c_str());
Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(buf.pData_, buf.size_);
image->readMetadata();
Exiv2::ExifData &exifData = image->exifData();
exifData["Exif.Image.Software"] = this->_metadataExif.Software;
exifData["Exif.Image.Artist"] = this->_metadataExif.Artist;
exifData["Exif.Image.Copyright"] = this->_metadataExif.Copyright;
image->writeMetadata();

string fname1 = this->_pName + "/" + "_try_" + fname;

Exiv2::FileIo fileio(fname1.c_str());
fileio.open();
fileio.write(image->io());
*/

/*
file_size = image->io().size(); 
image->io().seek(0,Exiv2::BasicIo::beg);
Exiv2::DataBuf buff = image->io().read(file_size);

FILE * fileio = ::fopen(fname1.c_str(), "w"); 
::fwrite(buff.pData_, file_size, 1, fileio); 
::fclose(fileio); 
*/

file_size = 0;
file_data = NULL;

#5 Updated by Nacho Sánchez Moreno 4 days ago

Sorry,

Strange things have happened with the format

#6 Updated by Robin Mills 4 days ago

Thanks for the code. I'll look at this tonight. We'll successfully fix this.

Don't worry about the format. You should enclose code: <pre> ... </pre>. I'll edit your comments and fix the formatting later.

Have a nice day and weekend. Speak later.

#7 Updated by Robin Mills 3 days ago

  • % Done changed from 100 to 80
  • Estimated time changed from 1.00 to 4.00

I think I know what's wrong with this. I haven't debugged your code, I've deduced it by inspection.

Only a tiny part of an image contains metadata. Most of the data in an image is pixels. When you use image->readMetadata(), Exiv2 finds the metadata and puts it into the Exiv2::ExifData vector. It is possible for image->readMetadata() to be successful on a corrupted image. However image->writeMetadata() could fail if the pixel data in the image is corrupted. It's an uncommon scenario for images to be created from memory buffers. Images are normally created by reading good images into memory.

I think there is something wrong with the data file_data and file_size. Can I ask you to write that data immediately to path foo.image (using a variant of your code: fileio ::fopen ::write ::fclose). If foo.image is corrupt, it has not been corrupted by Exiv2 as no Exiv2 code has seen that data.

You can analyse foo.image with the command:

$ exiv2 -pR foo.image
Please attach foo.image to this report.

My earlier suggestion to write to disk and use Exiv2::ImageFactory::open(path) has been ineffective because the image written to disk is corrupted. Opening an image is essentially (inside Exiv2) ::fopen followed by readMetadata(). The image is still corrupt and will again be detected by your call to image->writeMetadata()

#8 Updated by Robin Mills about 17 hours ago

  • Status changed from Resolved to Closed
  • % Done changed from 80 to 100

Also available in: Atom PDF

Redmine Appliance - Powered by TurnKey Linux