Use exiv2 for metadata handling

This commit is contained in:
Alberto Griggio
2019-05-06 09:27:44 +02:00
parent a2e2ace1c8
commit c360fd7e2c
49 changed files with 1359 additions and 17510 deletions

View File

@@ -24,7 +24,6 @@
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <libiptcdata/iptc-jpeg.h>
#include "rt_math.h"
#include "procparams.h"
#include "../rtgui/options.h"
@@ -37,9 +36,10 @@
#endif
#include "imageio.h"
#include "iptcpairs.h"
//#include "iptcpairs.h"
#include "iccjpeg.h"
#include "color.h"
#include "imagedata.h"
#include "jpeg.h"
@@ -80,97 +80,6 @@ FILE* g_fopen_withBinaryAndLock(const Glib::ustring& fname)
Glib::ustring ImageIO::errorMsg[6] = {"Success", "Cannot read file.", "Invalid header.", "Error while reading header.", "File reading error", "Image format not supported."};
// For only copying the raw input data
void ImageIO::setMetadata (const rtexif::TagDirectory* eroot)
{
if (exifRoot != nullptr) {
delete exifRoot;
exifRoot = nullptr;
}
if (eroot) {
rtexif::TagDirectory* td = ((rtexif::TagDirectory*)eroot)->clone (nullptr);
// make IPTC and XMP pass through
td->keepTag(0x83bb); // IPTC
td->keepTag(0x02bc); // XMP
exifRoot = td;
}
}
// For merging with RT specific data
void ImageIO::setMetadata (const rtexif::TagDirectory* eroot, const rtengine::procparams::ExifPairs& exif, const rtengine::procparams::IPTCPairs& iptcc)
{
// store exif info
exifChange->clear();
*exifChange = exif;
if (exifRoot != nullptr) {
delete exifRoot;
exifRoot = nullptr;
}
if (eroot) {
exifRoot = ((rtexif::TagDirectory*)eroot)->clone (nullptr);
}
if (iptc != nullptr) {
iptc_data_free (iptc);
iptc = nullptr;
}
// build iptc structures for libiptcdata
if (iptcc.empty()) {
return;
}
iptc = iptc_data_new ();
const unsigned char utf8Esc[] = {0x1B, '%', 'G'};
IptcDataSet * ds = iptc_dataset_new ();
iptc_dataset_set_tag (ds, IPTC_RECORD_OBJECT_ENV, IPTC_TAG_CHARACTER_SET);
iptc_dataset_set_data (ds, utf8Esc, 3, IPTC_DONT_VALIDATE);
iptc_data_add_dataset (iptc, ds);
iptc_dataset_unref (ds);
for (rtengine::procparams::IPTCPairs::const_iterator i = iptcc.begin(); i != iptcc.end(); ++i) {
if (i->first == "Keywords" && !(i->second.empty())) {
for (unsigned int j = 0; j < i->second.size(); j++) {
IptcDataSet * ds = iptc_dataset_new ();
iptc_dataset_set_tag (ds, IPTC_RECORD_APP_2, IPTC_TAG_KEYWORDS);
iptc_dataset_set_data (ds, (unsigned char*)i->second.at(j).c_str(), min(static_cast<size_t>(64), i->second.at(j).bytes()), IPTC_DONT_VALIDATE);
iptc_data_add_dataset (iptc, ds);
iptc_dataset_unref (ds);
}
continue;
} else if (i->first == "SupplementalCategories" && !(i->second.empty())) {
for (unsigned int j = 0; j < i->second.size(); j++) {
IptcDataSet * ds = iptc_dataset_new ();
iptc_dataset_set_tag (ds, IPTC_RECORD_APP_2, IPTC_TAG_SUPPL_CATEGORY);
iptc_dataset_set_data (ds, (unsigned char*)i->second.at(j).c_str(), min(static_cast<size_t>(32), i->second.at(j).bytes()), IPTC_DONT_VALIDATE);
iptc_data_add_dataset (iptc, ds);
iptc_dataset_unref (ds);
}
continue;
}
for (int j = 0; j < 16; j++)
if (i->first == strTags[j].field && !(i->second.empty())) {
IptcDataSet * ds = iptc_dataset_new ();
iptc_dataset_set_tag (ds, IPTC_RECORD_APP_2, strTags[j].tag);
iptc_dataset_set_data (ds, (unsigned char*)i->second.at(0).c_str(), min(strTags[j].size, i->second.at(0).bytes()), IPTC_DONT_VALIDATE);
iptc_data_add_dataset (iptc, ds);
iptc_dataset_unref (ds);
}
}
iptc_data_sort (iptc);
}
void ImageIO::setOutputProfile (const char* pdata, int plen)
{
@@ -194,9 +103,6 @@ ImageIO::ImageIO() :
loadedProfileData(nullptr),
loadedProfileDataJpg(false),
loadedProfileLength(0),
exifChange(new procparams::ExifPairs),
iptc(nullptr),
exifRoot(nullptr),
sampleFormat(IIOSF_UNKNOWN),
sampleArrangement(IIOSA_UNKNOWN)
{
@@ -210,7 +116,7 @@ ImageIO::~ImageIO ()
}
deleteLoadedProfileData();
delete exifRoot;
// delete exifRoot;
delete [] profileData;
}
@@ -919,76 +825,6 @@ int ImageIO::loadPPMFromMemory(const char* buffer, int width, int height, bool s
}
namespace {
// Taken from Darktable -- src/imageio/format/png.c
//
/* Write EXIF data to PNG file.
* Code copied from DigiKam's libs/dimg/loaders/pngloader.cpp.
* The EXIF embedding is defined by ImageMagicK.
* It is documented in the ExifTool page:
* http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PNG.html
*
* ..and in turn copied from ufraw. thanks to udi and colleagues
* for making useful code much more readable and discoverable ;)
*/
void PNGwriteRawProfile(png_struct *ping, png_info *ping_info, const char *profile_type, guint8 *profile_data, png_uint_32 length)
{
png_textp text;
long i;
guint8 *sp;
png_charp dp;
png_uint_32 allocated_length, description_length;
const guint8 hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
text = static_cast<png_textp>(png_malloc(ping, sizeof(png_text)));
description_length = strlen(profile_type);
allocated_length = length * 2 + (length >> 5) + 20 + description_length;
text[0].text = static_cast<png_charp>(png_malloc(ping, allocated_length));
text[0].key = static_cast<png_charp>(png_malloc(ping, 80));
text[0].key[0] = '\0';
g_strlcat(text[0].key, "Raw profile type ", 80);
g_strlcat(text[0].key, profile_type, 80);
sp = profile_data;
dp = text[0].text;
*dp++ = '\n';
g_strlcpy(dp, profile_type, allocated_length);
dp += description_length;
*dp++ = '\n';
*dp = '\0';
g_snprintf(dp, allocated_length - strlen(text[0].text), "%8lu ", static_cast<unsigned long int>(length));
dp += 8;
for(i = 0; i < long(length); i++)
{
if(i % 36 == 0) *dp++ = '\n';
*(dp++) = hex[((*sp >> 4) & 0x0f)];
*(dp++) = hex[((*sp++) & 0x0f)];
}
*dp++ = '\n';
*dp = '\0';
text[0].text_length = (dp - text[0].text);
text[0].compression = -1;
if(text[0].text_length <= allocated_length) png_set_text(ping, ping_info, text, 1);
png_free(ping, text[0].text);
png_free(ping, text[0].key);
png_free(ping, text);
}
} // namespace
int ImageIO::savePNG (const Glib::ustring &fname, int bps) const
{
if (getWidth() < 1 || getHeight() < 1) {
@@ -1060,30 +896,6 @@ int ImageIO::savePNG (const Glib::ustring &fname, int bps) const
png_set_iCCP(png, info, const_cast<png_charp>("icc"), 0, profdata, profileLength);
}
{
// buffer for the exif and iptc
unsigned int bufferSize;
unsigned char* buffer = nullptr; // buffer will be allocated in createTIFFHeader
unsigned char* iptcdata = nullptr;
unsigned int iptclen = 0;
if (iptc && iptc_data_save (iptc, &iptcdata, &iptclen) && iptcdata) {
iptc_data_free_buf (iptc, iptcdata);
iptcdata = nullptr;
}
int size = rtexif::ExifManager::createPNGMarker(exifRoot, *exifChange, width, height, bps, (char*)iptcdata, iptclen, buffer, bufferSize);
if (iptcdata) {
iptc_data_free_buf (iptc, iptcdata);
}
if (buffer && size) {
PNGwriteRawProfile(png, info, "exif", buffer, size);
delete[] buffer;
}
}
int rowlen = width * 3 * bps / 8;
unsigned char *row = new unsigned char [rowlen];
@@ -1117,6 +929,11 @@ int ImageIO::savePNG (const Glib::ustring &fname, int bps) const
delete [] row;
fclose (file);
if (!saveMetadata(fname)) {
g_remove(fname.c_str());
return IMIO_CANNOTWRITEFILE;
}
if (pl) {
pl->setProgressStr ("PROGRESSBAR_READY");
pl->setProgress (1.0);
@@ -1216,49 +1033,6 @@ int ImageIO::saveJPEG (const Glib::ustring &fname, int quality, int subSamp) con
jpeg_start_compress(&cinfo, TRUE);
// buffer for exif and iptc markers
unsigned char* buffer = new unsigned char[165535]; //FIXME: no buffer size check so it can be overflowed in createJPEGMarker() for large tags, and then software will crash
unsigned int size;
// assemble and write exif marker
if (exifRoot) {
int size = rtexif::ExifManager::createJPEGMarker (exifRoot, *exifChange, cinfo.image_width, cinfo.image_height, buffer);
if (size > 0 && size < 65530) {
jpeg_write_marker(&cinfo, JPEG_APP0 + 1, buffer, size);
}
}
// assemble and write iptc marker
if (iptc) {
unsigned char* iptcdata;
bool error = false;
if (iptc_data_save (iptc, &iptcdata, &size)) {
if (iptcdata) {
iptc_data_free_buf (iptc, iptcdata);
}
error = true;
}
int bytes = 0;
if (!error && (bytes = iptc_jpeg_ps3_save_iptc (nullptr, 0, iptcdata, size, buffer, 65532)) < 0) {
error = true;
}
if (iptcdata) {
iptc_data_free_buf (iptc, iptcdata);
}
if (!error) {
jpeg_write_marker(&cinfo, JPEG_APP0 + 13, buffer, bytes);
}
}
delete [] buffer;
// write icc profile to the output
if (profileData) {
write_icc_profile (&cinfo, (JOCTET*)profileData, profileLength);
@@ -1310,6 +1084,11 @@ int ImageIO::saveJPEG (const Glib::ustring &fname, int quality, int subSamp) con
fclose (file);
if (!saveMetadata(fname)) {
g_remove(fname.c_str());
return IMIO_CANNOTWRITEFILE;
}
if (pl) {
pl->setProgressStr ("PROGRESSBAR_READY");
pl->setProgress (1.0);
@@ -1336,7 +1115,7 @@ int ImageIO::saveTIFF (const Glib::ustring &fname, int bps, bool isFloat, bool u
unsigned char* linebuffer = new unsigned char[lineWidth];
// little hack to get libTiff to use proper byte order (see TIFFClienOpen()):
const char *mode = !exifRoot ? "w" : (exifRoot->getOrder() == rtexif::INTEL ? "wl" : "wb");
const char *mode = "w";
#ifdef WIN32
FILE *file = g_fopen_withBinaryAndLock (fname);
int fileno = _fileno(file);
@@ -1344,7 +1123,7 @@ int ImageIO::saveTIFF (const Glib::ustring &fname, int bps, bool isFloat, bool u
TIFF* out = TIFFFdOpen (osfileno, fname.c_str(), mode);
#else
TIFF* out = TIFFOpen(fname.c_str(), mode);
int fileno = TIFFFileno (out);
// int fileno = TIFFFileno (out);
#endif
if (!out) {
@@ -1357,113 +1136,7 @@ int ImageIO::saveTIFF (const Glib::ustring &fname, int bps, bool isFloat, bool u
pl->setProgress (0.0);
}
bool applyExifPatch = false;
if (exifRoot) {
rtexif::TagDirectory* cl = (const_cast<rtexif::TagDirectory*> (exifRoot))->clone (nullptr);
// ------------------ remove some unknown top level tags which produce warnings when opening a tiff (might be useless) -----------------
rtexif::Tag *removeTag = cl->getTag (0x9003);
if (removeTag) {
removeTag->setKeep (false);
}
removeTag = cl->getTag (0x9211);
if (removeTag) {
removeTag->setKeep (false);
}
// ------------------ Apply list of change -----------------
for (auto currExifChange : *exifChange) {
cl->applyChange (currExifChange.first, currExifChange.second);
}
rtexif::Tag *tag = cl->getTag (TIFFTAG_EXIFIFD);
if (tag && tag->isDirectory()) {
rtexif::TagDirectory *exif = tag->getDirectory();
if (exif) {
int exif_size = exif->calculateSize();
unsigned char *buffer = new unsigned char[exif_size + 8];
// TIFFOpen writes out the header and sets file pointer at position 8
exif->write (8, buffer);
write (fileno, buffer + 8, exif_size);
delete [] buffer;
// let libtiff know that scanlines or any other following stuff should go
// at a different offset:
TIFFSetWriteOffset (out, exif_size + 8);
TIFFSetField (out, TIFFTAG_EXIFIFD, 8);
applyExifPatch = true;
}
}
//TODO Even though we are saving EXIF IFD - MakerNote still comes out screwed.
if ((tag = cl->getTag (TIFFTAG_MODEL)) != nullptr) {
TIFFSetField (out, TIFFTAG_MODEL, tag->getValue());
}
if ((tag = cl->getTag (TIFFTAG_MAKE)) != nullptr) {
TIFFSetField (out, TIFFTAG_MAKE, tag->getValue());
}
if ((tag = cl->getTag (TIFFTAG_DATETIME)) != nullptr) {
TIFFSetField (out, TIFFTAG_DATETIME, tag->getValue());
}
if ((tag = cl->getTag (TIFFTAG_ARTIST)) != nullptr) {
TIFFSetField (out, TIFFTAG_ARTIST, tag->getValue());
}
if ((tag = cl->getTag (TIFFTAG_COPYRIGHT)) != nullptr) {
TIFFSetField (out, TIFFTAG_COPYRIGHT, tag->getValue());
}
delete cl;
}
unsigned char* iptcdata = nullptr;
unsigned int iptclen = 0;
if (iptc && iptc_data_save (iptc, &iptcdata, &iptclen)) {
if (iptcdata) {
iptc_data_free_buf (iptc, iptcdata);
iptcdata = nullptr;
}
}
#if __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__
bool needsReverse = exifRoot && exifRoot->getOrder() == rtexif::MOTOROLA;
#else
bool needsReverse = exifRoot && exifRoot->getOrder() == rtexif::INTEL;
#endif
if (iptcdata) {
rtexif::Tag iptcTag(nullptr, rtexif::lookupAttrib (rtexif::ifdAttribs, "IPTCData"));
iptcTag.initLongArray((char*)iptcdata, iptclen);
if (needsReverse) {
unsigned char *ptr = iptcTag.getValue();
for (int a = 0; a < iptcTag.getCount(); ++a) {
unsigned char cc;
cc = ptr[3];
ptr[3] = ptr[0];
ptr[0] = cc;
cc = ptr[2];
ptr[2] = ptr[1];
ptr[1] = cc;
ptr += 4;
}
}
TIFFSetField (out, TIFFTAG_RICHTIFFIPTC, iptcTag.getCount(), (long*)iptcTag.getValue());
iptc_data_free_buf (iptc, iptcdata);
}
bool needsReverse = false;
TIFFSetField (out, TIFFTAG_SOFTWARE, "RawTherapee " RTVERSION);
TIFFSetField (out, TIFFTAG_IMAGEWIDTH, width);
@@ -1523,38 +1196,6 @@ int ImageIO::saveTIFF (const Glib::ustring &fname, int bps, bool isFloat, bool u
writeOk = false;
}
/************************************************************************************************************
*
* Hombre: This is a dirty hack to update the Exif tag data type to 0x0004 so that Windows can understand it.
* libtiff will set this data type to 0x000d and doesn't provide any mechanism to update it before
* dumping to the file.
*
*/
if (applyExifPatch) {
unsigned char b[10];
uint16 tagCount = 0;
lseek(fileno, 4, SEEK_SET);
read(fileno, b, 4);
uint32 ifd0Offset = rtexif::sget4(b, exifRoot->getOrder());
lseek(fileno, ifd0Offset, SEEK_SET);
read(fileno, b, 2);
tagCount = rtexif::sget2(b, exifRoot->getOrder());
for (size_t i = 0; i < tagCount ; ++i) {
uint16 tagID = 0;
read(fileno, b, 2);
tagID = rtexif::sget2(b, exifRoot->getOrder());
if (tagID == 0x8769) {
rtexif::sset2(4, b, exifRoot->getOrder());
write(fileno, b, 2);
break;
} else {
read(fileno, b, 10);
}
}
}
/************************************************************************************************************/
TIFFClose (out);
#ifdef WIN32
fclose (file);
@@ -1562,6 +1203,10 @@ int ImageIO::saveTIFF (const Glib::ustring &fname, int bps, bool isFloat, bool u
delete [] linebuffer;
if (!saveMetadata(fname)) {
writeOk = false;
}
if (pl) {
pl->setProgressStr ("PROGRESSBAR_READY");
pl->setProgress (1.0);
@@ -1692,3 +1337,42 @@ void ImageIO::deleteLoadedProfileData( )
loadedProfileData = nullptr;
}
bool ImageIO::saveMetadata(const Glib::ustring &fname) const
{
if (metadataInfo.filename().empty()) {
return true;
}
try {
auto src = open_exiv2(metadataInfo.filename());
auto dst = open_exiv2(fname);
src->readMetadata();
dst->setMetadata(*src);
dst->exifData()["Exif.Image.Software"] = "RawTherapee " RTVERSION;
for (auto &p : metadataInfo.exif()) {
try {
dst->exifData()[p.first] = p.second;
} catch (Exiv2::AnyError &exc) {}
}
for (auto &p : metadataInfo.iptc()) {
try {
auto &v = p.second;
if (v.size() >= 1) {
dst->iptcData()[p.first] = v[0];
for (size_t j = 1; j < v.size(); ++j) {
Exiv2::Iptcdatum d(Exiv2::IptcKey(p.first));
d.setValue(v[j]);
dst->iptcData().add(d);
}
}
} catch (Exiv2::AnyError &exc) {}
}
dst->writeMetadata();
return true;
} catch (Exiv2::AnyError &exc) {
std::cout << "EXIF ERROR: " << exc.what() << std::endl;
return false;
}
}