diff --git a/rtengine/CMakeLists.txt b/rtengine/CMakeLists.txt index c50abb88c..01424543b 100644 --- a/rtengine/CMakeLists.txt +++ b/rtengine/CMakeLists.txt @@ -145,6 +145,7 @@ set(RTENGINESOURCEFILES lj92.c lmmse_demosaic.cc loadinitial.cc + metadata.cc munselllch.cc myfile.cc panasonic_decoders.cc diff --git a/rtengine/imagedata.cc b/rtengine/imagedata.cc index 1977061db..e7cf2b4c2 100644 --- a/rtengine/imagedata.cc +++ b/rtengine/imagedata.cc @@ -22,11 +22,11 @@ #include #include #include -#include #include "imagedata.h" #include "imagesource.h" #include "rt_math.h" +#include "metadata.h" #include "utils.h" #pragma GCC diagnostic warning "-Wextra" @@ -52,24 +52,6 @@ namespace rtengine { extern const Settings *settings; -Exiv2::Image::AutoPtr open_exiv2(const Glib::ustring& fname) -{ -#ifdef EXV_UNICODE_PATH - glong ws_size = 0; - gunichar2* const ws = g_utf8_to_utf16(fname.c_str(), -1, nullptr, &ws_size, nullptr); - std::wstring wfname; - wfname.reserve(ws_size); - for (glong i = 0; i < ws_size; ++i) { - wfname.push_back(ws[i]); - } - g_free(ws); - auto image = Exiv2::ImageFactory::open(wfname); -#else - auto image = Exiv2::ImageFactory::open(fname); -#endif - return image; -} - } // namespace rtengine FramesMetaData* FramesMetaData::fromFile(const Glib::ustring& fname) @@ -106,9 +88,9 @@ FramesData::FramesData(const Glib::ustring &fname) : lens.clear(); try { - auto image = open_exiv2(fname); - image->readMetadata(); - const auto& exif = image->exifData(); + Exiv2Metadata meta(fname); + meta.load(); + const auto& exif = meta.exifData(); ok_ = true; // taken and adapted from darktable (src/common/exif.cc) @@ -189,7 +171,7 @@ FramesData::FramesData(const Glib::ustring &fname) : /* TODO: Implement ratings in exiv2 situations. See PR #5325 - + // Look for Rating metadata in the following order: // 1. EXIF // 2. XMP diff --git a/rtengine/imagedata.h b/rtengine/imagedata.h index f692d4267..b1a29dc39 100644 --- a/rtengine/imagedata.h +++ b/rtengine/imagedata.h @@ -22,8 +22,6 @@ #include #include -#include - #include "imageio.h" namespace Glib @@ -36,8 +34,6 @@ class ustring; namespace rtengine { -Exiv2::Image::AutoPtr open_exiv2(const Glib::ustring &fname); // TODO: Global function? - class FramesData final : public FramesMetaData { diff --git a/rtengine/imageio.cc b/rtengine/imageio.cc index 26ea87a44..475792e83 100644 --- a/rtengine/imageio.cc +++ b/rtengine/imageio.cc @@ -79,39 +79,7 @@ FILE* g_fopen_withBinaryAndLock(const Glib::ustring& fname) } -MetadataInfo::MetadataInfo(const Glib::ustring& src) : - src_(src), - exif_(new rtengine::procparams::ExifPairs), - iptc_(new rtengine::procparams::IPTCPairs) -{ -} - -const Glib::ustring& MetadataInfo::filename() const -{ - return src_; -} - -const rtengine::procparams::ExifPairs& MetadataInfo::exif() const -{ - return *exif_; -} - -const rtengine::procparams::IPTCPairs& MetadataInfo::iptc() const -{ - return *iptc_; -} - -void MetadataInfo::setExif(const rtengine::procparams::ExifPairs &exif) -{ - *exif_ = exif; -} - -void MetadataInfo::setIptc(const rtengine::procparams::IPTCPairs &iptc) -{ - *iptc_ = iptc; -} - -void ImageIO::setMetadata(MetadataInfo info) +void ImageIO::setMetadata(Exiv2Metadata info) { metadataInfo = std::move(info); } @@ -1181,9 +1149,9 @@ int ImageIO::saveTIFF (const Glib::ustring &fname, int bps, bool isFloat, bool u TIFFSetField (out, TIFFTAG_SAMPLEFORMAT, (bps == 16 || bps == 32) && isFloat ? SAMPLEFORMAT_IEEEFP : SAMPLEFORMAT_UINT); /* - + TODO: Re-apply fix from #5787 - + [out]() { const std::vector default_tags = rtexif::ExifManager::getDefaultTIFFTags(nullptr); @@ -1388,32 +1356,34 @@ bool ImageIO::saveMetadata(const Glib::ustring &fname) const } 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 (const auto& p : metadataInfo.exif()) { - try { - dst->exifData()[p.first] = p.second; - } catch (const Exiv2::AnyError& exc) { - } - } - for (const auto& p : metadataInfo.iptc()) { - try { - auto& v = p.second; - if (!v.empty()) { - 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 (const Exiv2::AnyError& exc) { - } - } - dst->writeMetadata(); + metadataInfo.load(); + metadataInfo.saveToImage(fname); + // auto src = open_exiv2(metadataInfo.filename()); + // auto dst = open_exiv2(fname); + // src->readMetadata(); + // dst->setMetadata(*src); + // dst->exifData()["Exif.Image.Software"] = "RawTherapee " RTVERSION; + // for (const auto& p : metadataInfo.exif()) { + // try { + // dst->exifData()[p.first] = p.second; + // } catch (const Exiv2::AnyError& exc) { + // } + // } + // for (const auto& p : metadataInfo.iptc()) { + // try { + // auto& v = p.second; + // if (!v.empty()) { + // 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 (const Exiv2::AnyError& exc) { + // } + // } + // dst->writeMetadata(); return true; } catch (const Exiv2::AnyError& exc) { std::cout << "EXIF ERROR: " << exc.what() << std::endl; diff --git a/rtengine/imageio.h b/rtengine/imageio.h index 3283d5816..26e7164e6 100644 --- a/rtengine/imageio.h +++ b/rtengine/imageio.h @@ -24,6 +24,7 @@ #include "iimage.h" #include "imagedimensions.h" #include "imageformat.h" +#include "metadata.h" #include "rtengine.h" enum { @@ -52,25 +53,6 @@ class ColorTemp; class ProgressListener; class Imagefloat; -class MetadataInfo final -{ -public: - explicit MetadataInfo(const Glib::ustring& src = {}); - - const Glib::ustring& filename() const; - - const rtengine::procparams::ExifPairs& exif() const; - const rtengine::procparams::IPTCPairs& iptc() const; - - void setExif(const rtengine::procparams::ExifPairs &exif); - void setIptc(const rtengine::procparams::IPTCPairs &iptc); - -private: - Glib::ustring src_; - std::unique_ptr exif_; - std::unique_ptr iptc_; -}; - class ImageIO : virtual public ImageDatas { @@ -84,7 +66,7 @@ protected: MyMutex imutex; IIOSampleFormat sampleFormat; IIOSampleArrangement sampleArrangement; - MetadataInfo metadataInfo; + Exiv2Metadata metadataInfo; private: void deleteLoadedProfileData( ); @@ -124,7 +106,7 @@ public: cmsHPROFILE getEmbeddedProfile () const; void getEmbeddedProfileData (int& length, unsigned char*& pdata) const; - void setMetadata(MetadataInfo info); + void setMetadata(Exiv2Metadata info); void setOutputProfile(const std::string& pdata); bool saveMetadata(const Glib::ustring &fname) const; diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index 804e81ac0..b74a137ff 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -31,6 +31,7 @@ #include "image8.h" #include "imagefloat.h" #include "improcfun.h" +#include "metadata.h" #include "labimage.h" #include "lcp.h" #include "procparams.h" @@ -320,7 +321,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) RAWParams rp = params->raw; ColorManagementParams cmp = params->icm; LCurveParams lcur = params->labCurve; - + if (!highDetailNeeded) { // if below 100% magnification, take a fast path if (rp.bayersensor.method != RAWParams::BayerSensor::getMethodString(RAWParams::BayerSensor::Method::NONE) && rp.bayersensor.method != RAWParams::BayerSensor::getMethodString(RAWParams::BayerSensor::Method::MONO)) { @@ -528,8 +529,8 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) lastAwbauto = ""; autoWB.useDefaults(params->wb.equal); } - - + + } currWB = autoWB; @@ -542,11 +543,11 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) if (autowb && awbListener && params->wb.method == "autitcgreen") { awbListener->WBChanged(params->wb.temperature, params->wb.green, studgood); - } + } if (autowb && awbListener && params->wb.method == "autold") { awbListener->WBChanged(params->wb.temperature, params->wb.green, -1.f); - } + } /* GammaValues g_a; @@ -748,7 +749,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) whiteev = new float[sizespot]; bool *Autogr = nullptr; Autogr = new bool[sizespot]; - + float *locx = nullptr; locx = new float[sizespot]; float *locy = nullptr; @@ -761,7 +762,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) centx = new float[sizespot]; float *centy = nullptr; centy = new float[sizespot]; - + for (int sp = 0; sp < sizespot; sp++) { log[sp] = params->locallab.spots.at(sp).explog; autocomput[sp] = params->locallab.spots.at(sp).autocompute; @@ -865,7 +866,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) // if ((todo & (M_LUMINANCE + M_COLOR)) || (todo & M_AUTOEXP)) { // if (todo & M_RGBCURVE) { if (((todo & (M_AUTOEXP | M_RGBCURVE)) || (todo & M_CROP)) && params->locallab.enabled && !params->locallab.spots.empty()) { - + ipf.rgb2lab(*oprevi, *oprevl, params->icm.workingProfile); nprevl->CopyFrom(oprevl); @@ -964,7 +965,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) const bool llmaslogutili = locllmaslogCurve.Set(params->locallab.spots.at(sp).LLmaskcurveL); const bool lcmaslogutili = locccmaslogCurve.Set(params->locallab.spots.at(sp).CCmaskcurveL); const bool lhmaslogutili = lochhmaslogCurve.Set(params->locallab.spots.at(sp).HHmaskcurveL); - + const bool lcmas_utili = locccmas_Curve.Set(params->locallab.spots.at(sp).CCmask_curve); const bool llmas_utili = locllmas_Curve.Set(params->locallab.spots.at(sp).LLmask_curve); const bool lhmas_utili = lochhmas_Curve.Set(params->locallab.spots.at(sp).HHmask_curve); @@ -1053,7 +1054,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) int xxe = xend * ww; int yys = ysta * hh; int yye = yend * hh; - + if(istm) { //calculate mean and sigma on full image for RT-spot use by normalize_mean_dt ipf.mean_sig (nprevl->L, meantme, stdtme, xxs, xxe, yys, yye); } @@ -1073,7 +1074,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) float stdtm = stdtms[sp] = stdtme; float meanreti = meanretis[sp] = meanretie; float stdreti = stdretis[sp] = stdretie; - + CurveFactory::complexCurvelocal(ecomp, black / 65535., hlcompr, hlcomprthresh, shcompr, br, cont, lumar, hltonecurveloc, shtonecurveloc, tonecurveloc, lightCurveloc, avg, sca); @@ -1125,7 +1126,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) locccmasblCurve, lcmasblutili, locllmasblCurve, llmasblutili, lochhmasblCurve, lhmasblutili, locccmaslcCurve, lcmaslcutili, locllmaslcCurve, llmaslcutili, lochhmaslcCurve, lhmaslcutili, locccmaslogCurve, lcmaslogutili, locllmaslogCurve, llmaslogutili, lochhmaslogCurve, lhmaslogutili, - + locccmas_Curve, lcmas_utili, locllmas_Curve, llmas_utili, lochhmas_Curve, lhmas_utili, lochhhmas_Curve, lhhmas_utili, loclmasCurveblwav, lmasutiliblwav, @@ -1145,13 +1146,13 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) meantm, stdtm, meanreti, stdreti); - + if(istm) { //calculate mean and sigma on full image for use by normalize_mean_dt float meanf = 0.f; float stdf = 0.f; ipf.mean_sig (savenormtm.get()->L, meanf, stdf, xxs, xxe, yys, yye); - - //using 2 unused variables noiselumc and softradiustm + + //using 2 unused variables noiselumc and softradiustm params->locallab.spots.at(sp).noiselumc = (int) meanf; params->locallab.spots.at(sp).softradiustm = stdf ; } @@ -1160,7 +1161,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) float meanf = 0.f; float stdf = 0.f; ipf.mean_sig (savenormreti.get()->L, meanf, stdf,xxs, xxe, yys, yye ); - //using 2 unused variables sensihs and sensiv + //using 2 unused variables sensihs and sensiv params->locallab.spots.at(sp).sensihs = (int) meanf; params->locallab.spots.at(sp).sensiv = (int) stdf; } @@ -1207,9 +1208,9 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) //************************************************************* // end locallab //************************************************************* - + } - + if ((todo & M_RGBCURVE) || (todo & M_CROP)) { //complexCurve also calculated pre-curves histogram depending on crop CurveFactory::complexCurve(params->toneCurve.expcomp, params->toneCurve.black / 65535.0, @@ -1412,7 +1413,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) bool proedge = WaveParams.expedge; bool profin = WaveParams.expfinal; bool proton = WaveParams.exptoning; - bool pronois = WaveParams.expnoise; + bool pronois = WaveParams.expnoise; if(WaveParams.showmask) { // WaveParams.showmask = false; @@ -1436,7 +1437,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) WaveParams.expedge = false; WaveParams.expfinal = false; WaveParams.exptoning = false; - WaveParams.expnoise = false; + WaveParams.expnoise = false; } ipf.ip_wavelet(nprevl, nprevl, kall, WaveParams, wavCLVCurve, wavdenoise, wavdenoiseh, wavblcurve, waOpacityCurveRG, waOpacityCurveSH, waOpacityCurveBY, waOpacityCurveW, waOpacityCurveWL, wavclCurve, scale); @@ -1449,7 +1450,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) WaveParams.expfinal = profin; WaveParams.exptoning = proton; WaveParams.expnoise = pronois; - + if (WaveParams.softrad > 0.f) { array2D ble(pW, pH); @@ -1479,7 +1480,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) tmpImage->b(ir, jr) = Z; ble[ir][jr] = Y / 32768.f; } - + double epsilmax = 0.0001; double epsilmin = 0.00001; double aepsil = (epsilmax - epsilmin) / 100.f; @@ -1505,11 +1506,11 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) Color::XYZ2Lab(X, Y, Z, L, a, b); nprevl->L[ir][jr] = L; } - + delete tmpImage; } - + } if ((WaveParams.ushamethod == "sharp" || WaveParams.ushamethod == "clari") && WaveParams.expclari && WaveParams.CLmethod != "all") { @@ -1518,7 +1519,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) float mL0; float mC0; float background = 0.f; - int show = 0; + int show = 0; @@ -1560,8 +1561,8 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) delete unshar; unshar = NULL; - - + + /* if (WaveParams.softrad > 0.f) { array2D ble(pW, pH); @@ -1606,7 +1607,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) } - + } ipf.softLight(nprevl, params->softlight); @@ -1689,7 +1690,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) // acListener->wbCamChanged(params->wb.temperature, params->wb.green); //real temp and tint // acListener->wbCamChanged(params->wb.temperature, 1.f); //real temp and tint = 1. // } - + } else { // CIECAM is disabled, we free up its image buffer to save some space if (ncie) { @@ -2127,7 +2128,7 @@ bool ImProcCoordinator::getAutoWB(double& temp, double& green, double equal, dou // Issue 2500 MyMutex::MyLock lock(minit); // Also used in crop window double rm, gm, bm; params->wb.method = "autold";//same result as before muliple Auto WB - + // imgsrc->getAutoWBMultipliers(rm, gm, bm); double tempitc = 5000.; double greenitc = 1.; @@ -2374,7 +2375,7 @@ void ImProcCoordinator::saveInputICCReference(const Glib::ustring& fname, bool a im = tempImage; } - im->setMetadata(MetadataInfo(imgsrc->getFileName())); + im->setMetadata(Exiv2Metadata(imgsrc->getFileName(), false)); im->saveTIFF(fname, 16, false, true); delete im; diff --git a/rtengine/metadata.cc b/rtengine/metadata.cc new file mode 100644 index 000000000..15ab7db44 --- /dev/null +++ b/rtengine/metadata.cc @@ -0,0 +1,291 @@ +/* -*- C++ -*- + * + * This file is part of RawTherapee. + * + * Copyright (c) 2019 Alberto Griggio + * + * RawTherapee is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RawTherapee is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RawTherapee. If not, see . + */ + +#include +#include +#include "metadata.h" +#include "settings.h" +#include "../rtgui/version.h" +#include "../rtgui/pathutils.h" + + +namespace rtengine { + +extern const Settings *settings; + + +namespace { + +Exiv2::Image::AutoPtr open_exiv2(const Glib::ustring& fname) +{ +#ifdef EXV_UNICODE_PATH + glong ws_size = 0; + gunichar2* const ws = g_utf8_to_utf16(fname.c_str(), -1, nullptr, &ws_size, nullptr); + std::wstring wfname; + wfname.reserve(ws_size); + for (glong i = 0; i < ws_size; ++i) { + wfname.push_back(ws[i]); + } + g_free(ws); + auto image = Exiv2::ImageFactory::open(wfname); +#else + auto image = Exiv2::ImageFactory::open(Glib::filename_from_utf8(fname)); +#endif + return image; +} + +} // namespace + + +Exiv2Metadata::Exiv2Metadata(): + src_(""), + merge_xmp_(false), + image_(nullptr), + exif_(new rtengine::procparams::ExifPairs), + iptc_(new rtengine::procparams::IPTCPairs) +{ +} + + +Exiv2Metadata::Exiv2Metadata(const Glib::ustring &path): + src_(""), + merge_xmp_(settings->metadata_xmp_sync != Settings::MetadataXmpSync::NONE), + image_(nullptr), + exif_(new rtengine::procparams::ExifPairs), + iptc_(new rtengine::procparams::IPTCPairs) +{ +} + + +Exiv2Metadata::Exiv2Metadata(const Glib::ustring &path, bool merge_xmp_sidecar): + src_(path), + merge_xmp_(merge_xmp_sidecar), + image_(nullptr), + exif_(new rtengine::procparams::ExifPairs), + iptc_(new rtengine::procparams::IPTCPairs) +{ +} + + +void Exiv2Metadata::load() const +{ + if (!src_.empty() && !image_.get()) { + auto img = open_exiv2(src_); + image_.reset(img.release()); + image_->readMetadata(); + + if (merge_xmp_) { + do_merge_xmp(image_.get()); + } + } +} + +Exiv2::ExifData& Exiv2Metadata::exifData() +{ + return image_.get() ? image_->exifData() : exif_data_; +} + +const Exiv2::ExifData& Exiv2Metadata::exifData() const +{ + return const_cast(this)->exifData(); +} + +Exiv2::IptcData& Exiv2Metadata::iptcData() +{ + return image_.get() ? image_->iptcData() : iptc_data_; +} + +const Exiv2::IptcData& Exiv2Metadata::iptcData() const +{ + return const_cast(this)->iptcData(); +} + +Exiv2::XmpData& Exiv2Metadata::xmpData() +{ + return image_.get() ? image_->xmpData() : xmp_data_; +} + +const Exiv2::XmpData& Exiv2Metadata::xmpData() const +{ + return const_cast(this)->xmpData(); +} + +const Glib::ustring& Exiv2Metadata::filename() const +{ + return src_; +} + +const rtengine::procparams::ExifPairs& Exiv2Metadata::exif() const +{ + return *exif_; +} + +const rtengine::procparams::IPTCPairs& Exiv2Metadata::iptc() const +{ + return *iptc_; +} + +void Exiv2Metadata::setExif(const rtengine::procparams::ExifPairs &exif) +{ + *exif_ = exif; +} + +void Exiv2Metadata::setIptc(const rtengine::procparams::IPTCPairs &iptc) +{ + *iptc_ = iptc; +} + +void Exiv2Metadata::do_merge_xmp(Exiv2::Image *dst) const +{ + auto xmp = getXmpSidecar(src_); + Exiv2::ExifData exif; + Exiv2::IptcData iptc; + Exiv2::moveXmpToExif(xmp, exif); + Exiv2::moveXmpToIptc(xmp, iptc); + + for (auto &datum : exif) { + dst->exifData()[datum.key()] = datum; + } + for (auto &datum : iptc) { + dst->iptcData()[datum.key()] = datum; + } + for (auto &datum : xmp) { + dst->xmpData()[datum.key()] = datum; + } +} + + +void Exiv2Metadata::saveToImage(const Glib::ustring &path) const +{ + auto dst = open_exiv2(path); + dst->readMetadata(); + if (image_.get()) { + dst->setMetadata(*image_); + if (merge_xmp_) { + do_merge_xmp(dst.get()); + } + } else { + dst->setExifData(exif_data_); + dst->setIptcData(iptc_data_); + dst->setXmpData(xmp_data_); + } + + dst->exifData()["Exif.Image.Software"] = "RawTherapee " RTVERSION; + import_exif_pairs(dst->exifData()); + import_iptc_pairs(dst->iptcData()); + dst->writeMetadata(); +} + + +void Exiv2Metadata::import_exif_pairs(Exiv2::ExifData &out) const +{ + for (auto &p : *exif_) { + try { + out[p.first] = p.second; + } catch (Exiv2::AnyError &exc) {} + } +} + + +void Exiv2Metadata::import_iptc_pairs(Exiv2::IptcData &out) const +{ + for (auto &p : *iptc_) { + try { + auto &v = p.second; + if (v.size() >= 1) { + out[p.first] = v[0]; + for (size_t j = 1; j < v.size(); ++j) { + Exiv2::Iptcdatum d(Exiv2::IptcKey(p.first)); + d.setValue(v[j]); + out.add(d); + } + } + } catch (Exiv2::AnyError &exc) {} + } +} + + +void Exiv2Metadata::saveToXmp(const Glib::ustring &path) const +{ + Exiv2::XmpData xmp; + Exiv2::copyExifToXmp(exifData(), xmp); + Exiv2::copyIptcToXmp(iptcData(), xmp); + for (auto &datum : xmpData()) { + xmp[datum.key()] = datum; + } + Exiv2::ExifData exif; + Exiv2::IptcData iptc; + import_exif_pairs(exif); + import_iptc_pairs(iptc); + Exiv2::copyExifToXmp(exif, xmp); + Exiv2::copyIptcToXmp(iptc, xmp); + + std::string data; + bool err = false; + if (Exiv2::XmpParser::encode(data, xmp, Exiv2::XmpParser::omitPacketWrapper|Exiv2::XmpParser::useCompactFormat) != 0) { + err = true; + } else { + FILE *out = g_fopen(path.c_str(), "wb"); + if (!out || fputs(data.c_str(), out) == EOF) { + err = true; + } + if (out) { + fclose(out); + } + } + + class Error: public Exiv2::AnyError { + public: + Error(const std::string &msg): msg_(msg) {} + const char *what() const throw() { return msg_.c_str(); } + int code() const throw() { return 0; } + + private: + std::string msg_; + }; + if (err) { + throw Error("error saving XMP sidecar " + path); + } +} + + +Glib::ustring Exiv2Metadata::xmpSidecarPath(const Glib::ustring &path) +{ + Glib::ustring fn = path; + if (settings->xmp_sidecar_style == Settings::XmpSidecarStyle::STD) { + fn = removeExtension(fn); + } + return fn + ".xmp"; +} + + +Exiv2::XmpData Exiv2Metadata::getXmpSidecar(const Glib::ustring &path) +{ + Exiv2::XmpData ret; + auto fname = xmpSidecarPath(path); + if (Glib::file_test(fname, Glib::FILE_TEST_EXISTS)) { + auto image = open_exiv2(fname); + image->readMetadata(); + ret = image->xmpData(); + } + return ret; +} + +} // namespace rtengine diff --git a/rtengine/metadata.h b/rtengine/metadata.h new file mode 100644 index 000000000..8de384479 --- /dev/null +++ b/rtengine/metadata.h @@ -0,0 +1,80 @@ +/* -*- C++ -*- + * + * This file is part of RawTherapee. + * + * Copyright (c) 2019 Alberto Griggio + * + * RawTherapee is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RawTherapee is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RawTherapee. If not, see . + */ + +#pragma once + +#include +#include +#include +#include "procparams.h" + +namespace rtengine { + +class Exiv2Metadata final +{ +public: + Exiv2Metadata(); + explicit Exiv2Metadata(const Glib::ustring& path); + Exiv2Metadata(const Glib::ustring& path, bool merge_xmp_sidecar); + + void load() const; + + Exiv2::ExifData& exifData(); + const Exiv2::ExifData& exifData() const; + + Exiv2::IptcData& iptcData(); + const Exiv2::IptcData& iptcData() const; + + Exiv2::XmpData& xmpData(); + const Exiv2::XmpData& xmpData() const; + + const Glib::ustring& filename() const; + const rtengine::procparams::ExifPairs& exif() const; + const rtengine::procparams::IPTCPairs& iptc() const; + void setExif(const rtengine::procparams::ExifPairs& exif); + void setIptc(const rtengine::procparams::IPTCPairs& iptc); + + void saveToImage(const Glib::ustring& path) const; + void saveToXmp(const Glib::ustring& path) const; + + static Glib::ustring xmpSidecarPath(const Glib::ustring& path); + static Exiv2::XmpData getXmpSidecar(const Glib::ustring& path); + +private: + void do_merge_xmp(Exiv2::Image* dst) const; + void import_exif_pairs(Exiv2::ExifData& out) const; + void import_iptc_pairs(Exiv2::IptcData& out) const; + + Glib::ustring src_; + bool merge_xmp_; + mutable std::shared_ptr image_; + std::unique_ptr exif_; + std::unique_ptr iptc_; + Exiv2::ExifData exif_data_; + Exiv2::IptcData iptc_data_; + Exiv2::XmpData xmp_data_; +}; + +// Glib::ustring get_xmp_sidecar_path(const Glib::ustring &path); +// Exiv2::Image::AutoPtr open_exiv2(const Glib::ustring &fname, +// bool merge_xmp_sidecar); +// Exiv2::XmpData read_exiv2_xmp(const Glib::ustring &fname); + +} // namespace rtengine diff --git a/rtengine/procparams.h b/rtengine/procparams.h index f72d12f81..4a1eb47e3 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -1773,6 +1773,11 @@ public: return pairs.erase(key); } + bool empty() const + { + return pairs.empty(); + } + Glib::ustring& operator[](const Glib::ustring& key) { return pairs[key]; diff --git a/rtengine/settings.h b/rtengine/settings.h index 0fb4996df..f9ba55257 100644 --- a/rtengine/settings.h +++ b/rtengine/settings.h @@ -1,4 +1,5 @@ -/* +/* -*- C++ -*- + * * This file is part of RawTherapee. * * Copyright (c) 2004-2010 Gabor Horvath @@ -112,6 +113,19 @@ public: }; ThumbnailInspectorMode thumbnail_inspector_mode; + enum class XmpSidecarStyle { + STD, // FILENAME.xmp for FILENAME.ext + EXT // FILENAME.ext.xmp for FILENAME.ext + }; + XmpSidecarStyle xmp_sidecar_style; + + enum class MetadataXmpSync { + NONE, + READ, + READ_WRITE + }; + MetadataXmpSync metadata_xmp_sync; + /** Creates a new instance of Settings. * @return a pointer to the new Settings instance. */ static Settings* create(); diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index 6ae805d94..0b6406b62 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -37,6 +37,7 @@ #include "mytime.h" #include "guidedfilter.h" #include "color.h" +#include "metadata.h" #undef THREAD_PRIORITY_NORMAL @@ -945,10 +946,10 @@ private: if (params.locallab.enabled && params.locallab.spots.size() > 0) { ipf.rgb2lab(*baseImg, *labView, params.icm.workingProfile); - + MyTime t1, t2; t1.set(); - + const std::unique_ptr reservView(new LabImage(*labView, true)); const std::unique_ptr lastorigView(new LabImage(*labView, true)); std::unique_ptr savenormtmView; @@ -994,7 +995,7 @@ private: LocLLmaskCurve locllmas_Curve; LocHHmaskCurve lochhmas_Curve; LocHHmaskCurve lochhhmas_Curve; - + LocwavCurve loclmasCurveblwav; LocwavCurve loclmasCurvecolwav; LocwavCurve loclmasCurve_wav; @@ -1073,7 +1074,7 @@ private: const bool lcmaslogutili = locccmaslogCurve.Set(params.locallab.spots.at(sp).CCmaskcurveL); const bool llmaslogutili = locllmaslogCurve.Set(params.locallab.spots.at(sp).LLmaskcurveL); const bool lhmaslogutili = lochhmaslogCurve.Set(params.locallab.spots.at(sp).HHmaskcurveL); - + const bool lcmas_utili = locccmas_Curve.Set(params.locallab.spots.at(sp).CCmask_curve); const bool llmas_utili = locllmas_Curve.Set(params.locallab.spots.at(sp).LLmask_curve); const bool lhmas_utili = lochhmas_Curve.Set(params.locallab.spots.at(sp).HHmask_curve); @@ -1129,7 +1130,7 @@ private: float stdtme; float meanretie; float stdretie; - + if (params.locallab.spots.at(sp).spotMethod == "exc") { ipf.calc_ref(sp, reservView.get(), reservView.get(), 0, 0, fw, fh, 1, huerefblu, chromarefblu, lumarefblu, huere, chromare, lumare, sobelre, avge, locwavCurveden, locwavdenutili); } else { @@ -1148,8 +1149,8 @@ private: float Tmax; // No Locallab mask is shown in exported picture - ipf.Lab_Local(2, sp, shbuffer, labView, labView, reservView.get(), savenormtmView.get(), savenormretiView.get(), lastorigView.get(), 0, 0, fw, fh, 1, locRETgainCurve, locRETtransCurve, - lllocalcurve, locallutili, + ipf.Lab_Local(2, sp, shbuffer, labView, labView, reservView.get(), savenormtmView.get(), savenormretiView.get(), lastorigView.get(), 0, 0, fw, fh, 1, locRETgainCurve, locRETtransCurve, + lllocalcurve, locallutili, cllocalcurve, localclutili, lclocalcurve, locallcutili, loclhCurve, lochhCurve, locchCurve, @@ -1164,7 +1165,7 @@ private: lmasklclocalcurve, localmasklcutili, lmaskloglocalcurve, localmasklogutili, lmasklocal_curve, localmask_utili, - + locccmasCurve, lcmasutili, locllmasCurve, llmasutili, lochhmasCurve, lhmasutili, lochhhmasCurve, lhhmasutili, locccmasexpCurve, lcmasexputili, locllmasexpCurve, llmasexputili, lochhmasexpCurve, lhmasexputili, locccmasSHCurve, lcmasSHutili, locllmasSHCurve, llmasSHutili, lochhmasSHCurve, lhmasSHutili, locccmasvibCurve, lcmasvibutili, locllmasvibCurve, llmasvibutili, lochhmasvibCurve, lhmasvibutili, @@ -1428,8 +1429,8 @@ private: bool proedge = WaveParams.expedge; bool profin = WaveParams.expfinal; bool proton = WaveParams.exptoning; - bool pronois = WaveParams.expnoise; - + bool pronois = WaveParams.expnoise; + /* if(WaveParams.showmask) { WaveParams.showmask = false; @@ -1456,7 +1457,7 @@ private: WaveParams.expedge = false; WaveParams.expfinal = false; WaveParams.exptoning = false; - WaveParams.expnoise = false; + WaveParams.expnoise = false; } ipf.ip_wavelet(labView, labView, 2, WaveParams, wavCLVCurve, wavdenoise, wavdenoiseh, wavblcurve, waOpacityCurveRG, waOpacityCurveSH, waOpacityCurveBY, waOpacityCurveW, waOpacityCurveWL, wavclCurve, 1); @@ -1468,7 +1469,7 @@ private: WaveParams.expfinal = profin; WaveParams.exptoning = proton; WaveParams.expnoise = pronois; - + if (WaveParams.softrad > 0.f) { array2D ble(fw, fh); array2D guid(fw, fh); @@ -1523,7 +1524,7 @@ private: } delete tmpImage; } - + } if ((WaveParams.ushamethod == "sharp" || WaveParams.ushamethod == "clari") && WaveParams.expclari && WaveParams.CLmethod != "all") { @@ -1730,7 +1731,7 @@ private: readyImg = tempImage; } - MetadataInfo info(imgsrc->getFileName()); + Exiv2Metadata info(imgsrc->getFileName()); switch (params.metadata.mode) { case MetaDataParams::TUNNEL: readyImg->setMetadata(std::move(info)); diff --git a/rtgui/exifpanel.cc b/rtgui/exifpanel.cc index f452c2491..7438240e9 100644 --- a/rtgui/exifpanel.cc +++ b/rtgui/exifpanel.cc @@ -25,6 +25,7 @@ #include "options.h" #include "../rtengine/imagedata.h" +#include "../rtengine/metadata.h" #include "../rtengine/procparams.h" using namespace rtengine; @@ -225,9 +226,9 @@ void ExifPanel::refreshTags() } try { - auto img = open_exiv2(fn); - img->readMetadata(); - auto& exif = img->exifData(); + rtengine::Exiv2Metadata meta(fn); + meta.load(); + auto& exif = meta.exifData(); for (const auto& p : *changeList) { try { diff --git a/rtgui/iptcpanel.cc b/rtgui/iptcpanel.cc index 0f8471b92..724b54aa1 100644 --- a/rtgui/iptcpanel.cc +++ b/rtgui/iptcpanel.cc @@ -24,6 +24,7 @@ #include "rtimage.h" #include "../rtengine/imagedata.h" +#include "../rtengine/metadata.h" #include "../rtengine/procparams.h" using namespace rtengine; @@ -493,9 +494,9 @@ void IPTCPanel::setImageData (const FramesMetaData* id) embeddedData->clear(); if (id) { try { - auto img = open_exiv2(id->getFileName()); - img->readMetadata(); - auto& iptc = img->iptcData(); + rtengine::Exiv2Metadata meta(id->getFileName()); + meta.load(); + auto& iptc = meta.iptcData(); for (const auto& tag : iptc) { if (iptc_keys.find(tag.key()) != iptc_keys.end()) { (*embeddedData)[tag.key()].push_back(tag.toString()); diff --git a/rtgui/options.cc b/rtgui/options.cc index ce03db434..cf2cbaad3 100644 --- a/rtgui/options.cc +++ b/rtgui/options.cc @@ -626,7 +626,7 @@ void Options::setDefaults() rtSettings.edghi = 3.0;//1.1 and 5. rtSettings.edglo = 0.5;//0.1 and 0.95 rtSettings.limrad = 20.;//1 and 60 - + rtSettings.protectred = 60; rtSettings.protectredh = 0.3; @@ -682,6 +682,9 @@ void Options::setDefaults() cropAutoFit = false; rtSettings.thumbnail_inspector_mode = rtengine::Settings::ThumbnailInspectorMode::JPEG; + + rtSettings.xmp_sidecar_style = rtengine::Settings::XmpSidecarStyle::STD; + rtSettings.metadata_xmp_sync = rtengine::Settings::MetadataXmpSync::NONE; } Options* Options::copyFrom(Options* other) @@ -1488,11 +1491,11 @@ void Options::readFromFile(Glib::ustring fname) if (keyFile.has_key("GUI", "CurveBBoxPosition")) { curvebboxpos = keyFile.get_integer("GUI", "CurveBBoxPosition"); } - + if (keyFile.has_key("GUI", "Complexity")) { complexity = keyFile.get_integer("GUI", "Complexity"); } - + if (keyFile.has_key("GUI", "InspectorWindow")) { inspectorWindow = keyFile.get_boolean("GUI", "InspectorWindow"); } @@ -1755,8 +1758,8 @@ void Options::readFromFile(Glib::ustring fname) } } - - + + if (keyFile.has_group("ICC Profile Creator")) { if (keyFile.has_key("ICC Profile Creator", "PimariesPreset")) { ICCPC_primariesPreset = keyFile.get_string("ICC Profile Creator", "PimariesPreset"); @@ -2030,6 +2033,27 @@ void Options::readFromFile(Glib::ustring fname) } } + if (keyFile.has_group("Metadata")) { + if (keyFile.has_key("Metadata", "XMPSidecarStyle")) { + std::string val = keyFile.get_string("Metadata", "XMPSidecarStyle"); + if (val == "ext") { + rtSettings.xmp_sidecar_style = rtengine::Settings::XmpSidecarStyle::EXT; + } else { + rtSettings.xmp_sidecar_style = rtengine::Settings::XmpSidecarStyle::STD; + } + } + if (keyFile.has_key("Metadata", "XMPSynchronization")) { + std::string val = keyFile.get_string("Metadata", "XMPSynchronization"); + if (val == "read") { + rtSettings.metadata_xmp_sync = rtengine::Settings::MetadataXmpSync::READ; + } else if (val == "readwrite") { + rtSettings.metadata_xmp_sync = rtengine::Settings::MetadataXmpSync::READ_WRITE; + } else { + rtSettings.metadata_xmp_sync = rtengine::Settings::MetadataXmpSync::NONE; + } + } + } + // -------------------------------------------------------------------------------------------------------- filterOutParsedExtensions(); @@ -2448,6 +2472,25 @@ void Options::saveToFile(Glib::ustring fname) keyFile.set_string("Lensfun", "DBDirectory", rtSettings.lensfunDbDirectory); + switch (rtSettings.xmp_sidecar_style) { + case rtengine::Settings::XmpSidecarStyle::EXT: + keyFile.set_string("Metadata", "XMPSidecarStyle", "ext"); + break; + default: + keyFile.set_string("Metadata", "XMPSidecarStyle", "std"); + } + + switch (rtSettings.metadata_xmp_sync) { + case rtengine::Settings::MetadataXmpSync::READ: + keyFile.set_string("Metadata", "XMPSynchronization", "read"); + break; + case rtengine::Settings::MetadataXmpSync::READ_WRITE: + keyFile.set_string("Metadata", "XMPSynchronization", "readwrite"); + break; + default: + keyFile.set_string("Metadata", "XMPSynchronization", "none"); + } + keyData = keyFile.to_data(); } catch (Glib::KeyFileError &e) { diff --git a/rtgui/preferences.cc b/rtgui/preferences.cc index 9d9603297..62c243802 100644 --- a/rtgui/preferences.cc +++ b/rtgui/preferences.cc @@ -527,7 +527,7 @@ Gtk::Widget* Preferences::getImageProcessingPanel () iprofiles->set_size_request(50, -1); setExpandAlignProperties(iprofiles, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL); ipconn = iprofiles->signal_changed().connect(sigc::mem_fun(*this, &Preferences::forImageComboChanged)); - + Gtk::Grid* defpt = Gtk::manage(new Gtk::Grid()); defpt->set_row_spacing(2); defpt->attach(*drlab, 0, 0, 1, 1); @@ -535,7 +535,7 @@ Gtk::Widget* Preferences::getImageProcessingPanel () defpt->attach(*drimg, 0, 1, 1, 1); defpt->attach(*iprofiles, 1, 1, 1, 1); vbpp->pack_start(*defpt, Gtk::PACK_SHRINK, 4); - + useBundledProfiles = Gtk::manage(new Gtk::CheckButton(M("PREFERENCES_USEBUNDLEDPROFILES"))); bpconn = useBundledProfiles->signal_clicked().connect(sigc::mem_fun(*this, &Preferences::bundledProfilesChanged)); vbpp->pack_start(*useBundledProfiles, Gtk::PACK_SHRINK, 4); @@ -581,9 +581,36 @@ Gtk::Widget* Preferences::getImageProcessingPanel () fdp->add(*vbdp); vbImageProcessing->pack_start (*fdp, Gtk::PACK_SHRINK, 4); -// Gtk::Frame* fdf = Gtk::manage (new Gtk::Frame (M ("PREFERENCES_DARKFRAME")) ); -// Gtk::Box* hb42 = Gtk::manage (new Gtk::Box ()); -// darkFrameDir = Gtk::manage (new Gtk::FileChooserButton (M ("PREFERENCES_DIRDARKFRAMES"), Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER)); + // Metadata + Gtk::Frame *mf = Gtk::manage(new Gtk::Frame(M("PREFERENCES_METADATA"))); + Gtk::Grid *mtbl = Gtk::manage(new Gtk::Grid()); + setExpandAlignProperties(mtbl, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); + + metadataSyncCombo = Gtk::manage(new Gtk::ComboBoxText()); + metadataSyncCombo->set_active(0); + metadataSyncCombo->append(M("PREFERENCES_METADATA_SYNC_NONE")); + metadataSyncCombo->append(M("PREFERENCES_METADATA_SYNC_READ")); + metadataSyncCombo->append(M("PREFERENCES_METADATA_SYNC_READWRITE")); + Gtk::Label *mlbl = Gtk::manage(new Gtk::Label(M("PREFERENCES_METADATA_SYNC") + ": ")); + mtbl->attach(*mlbl, 0, 0, 1, 1); + mtbl->attach_next_to(*metadataSyncCombo, *mlbl, Gtk::POS_RIGHT, 1, 1); + setExpandAlignProperties(mlbl, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); + setExpandAlignProperties(metadataSyncCombo, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); + + xmpSidecarCombo = Gtk::manage(new Gtk::ComboBoxText()); + xmpSidecarCombo->set_active(0); + xmpSidecarCombo->append(M("PREFERENCES_XMP_SIDECAR_MODE_STD")); + xmpSidecarCombo->append(M("PREFERENCES_XMP_SIDECAR_MODE_EXT")); + + mlbl = Gtk::manage(new Gtk::Label(M("PREFERENCES_XMP_SIDECAR_MODE") + ": ")); + mtbl->attach(*mlbl, 0, 2, 1, 1); + mtbl->attach_next_to(*xmpSidecarCombo, *mlbl, Gtk::POS_RIGHT, 1, 1); + setExpandAlignProperties(mlbl, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); + setExpandAlignProperties(xmpSidecarCombo, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); + + mf->add(*mtbl); + vbImageProcessing->pack_start(*mf, Gtk::PACK_SHRINK, 4); + // Directories Gtk::Frame* cdf = Gtk::manage(new Gtk::Frame(M("PREFERENCES_DIRECTORIES"))); Gtk::Grid* dirgrid = Gtk::manage(new Gtk::Grid()); @@ -1104,7 +1131,7 @@ Gtk::Widget* Preferences::getGeneralPanel() setExpandAlignProperties(pseudoHiDPI, false, false, Gtk::ALIGN_START, Gtk::ALIGN_BASELINE); Gtk::Separator *vSep = Gtk::manage(new Gtk::Separator(Gtk::ORIENTATION_VERTICAL)); - + appearanceGrid->attach(*themeLbl, 0, 0, 1, 1); appearanceGrid->attach(*themeCBT, 1, 0, 1, 1); @@ -1870,6 +1897,9 @@ void Preferences::storePreferences() moptions.cropGuides = Options::CropGuidesMode(cropGuidesCombo->get_active_row_number()); moptions.cropAutoFit = cropAutoFitCB->get_active(); + + moptions.rtSettings.metadata_xmp_sync = rtengine::Settings::MetadataXmpSync(metadataSyncCombo->get_active_row_number()); + moptions.rtSettings.xmp_sidecar_style = rtengine::Settings::XmpSidecarStyle(xmpSidecarCombo->get_active_row_number()); } void Preferences::fillPreferences() @@ -2053,7 +2083,7 @@ void Preferences::fillPreferences() } curveBBoxPosC->set_active(moptions.curvebboxpos); - complexitylocal->set_active(moptions.complexity); + complexitylocal->set_active(moptions.complexity); inspectorWindowCB->set_active(moptions.inspectorWindow); zoomOnScrollCB->set_active(moptions.zoomOnScroll); @@ -2120,6 +2150,9 @@ void Preferences::fillPreferences() txtSndLngEditProcDone->set_text(moptions.sndLngEditProcDone); spbSndLngEditProcDoneSecs->set_value(moptions.sndLngEditProcDoneSecs); #endif + + metadataSyncCombo->set_active(int(moptions.rtSettings.metadata_xmp_sync)); + xmpSidecarCombo->set_active(int(moptions.rtSettings.xmp_sidecar_style)); } /* diff --git a/rtgui/preferences.h b/rtgui/preferences.h index df4e3327a..86e81424d 100644 --- a/rtgui/preferences.h +++ b/rtgui/preferences.h @@ -223,6 +223,9 @@ class Preferences final : Gtk::ComboBoxText *cropGuidesCombo; Gtk::CheckButton *cropAutoFitCB; + Gtk::ComboBoxText *metadataSyncCombo; + Gtk::ComboBoxText *xmpSidecarCombo; + Glib::ustring storedValueRaw; Glib::ustring storedValueImg; diff --git a/rtgui/thumbnail.cc b/rtgui/thumbnail.cc index e50d7ac77..31ca8bc1f 100644 --- a/rtgui/thumbnail.cc +++ b/rtgui/thumbnail.cc @@ -33,6 +33,7 @@ #include #include "../rtengine/dynamicprofile.h" +#include "../rtengine/metadata.h" #include "../rtengine/profilestore.h" #include "../rtengine/settings.h" #include "guiutils.h" @@ -1026,6 +1027,10 @@ void Thumbnail::updateCache (bool updatePParams, bool updateCacheImageData) if (updateCacheImageData) { cfs.save (getCacheFileName ("data", ".txt")); } + + if (updatePParams && pparamsValid) { + saveMetadata(); + } } Thumbnail::~Thumbnail () @@ -1204,6 +1209,33 @@ void Thumbnail::getCamWB(double& temp, double& green) const } } +void Thumbnail::saveMetadata() +{ + if (options.rtSettings.metadata_xmp_sync != rtengine::Settings::MetadataXmpSync::READ_WRITE) { + return; + } + + if (pparams->exif.empty() && pparams->iptc.empty()) { + return; + } + + auto fn = rtengine::Exiv2Metadata::xmpSidecarPath(fname); + try { + auto xmp = rtengine::Exiv2Metadata::getXmpSidecar(fname); + rtengine::Exiv2Metadata meta; + meta.xmpData() = std::move(xmp); + meta.setExif(pparams->exif); + meta.setIptc(pparams->iptc); + meta.saveToXmp(fn); + if (options.rtSettings.verbose) { + std::cout << "saved edited metadata for " << fname << " to " + << fn << std::endl; + } + } catch (Exiv2::AnyError &exc) { + std::cerr << "ERROR saving metadata for " << fname << " to " << fn + << ": " << exc.what() << std::endl; + } +} void Thumbnail::getSpotWB(int x, int y, int rect, double& temp, double& green) { if (tpp) { diff --git a/rtgui/thumbnail.h b/rtgui/thumbnail.h index 93d1deb93..85701142d 100644 --- a/rtgui/thumbnail.h +++ b/rtgui/thumbnail.h @@ -88,6 +88,8 @@ class Thumbnail Glib::ustring getCacheFileName (const Glib::ustring& subdir, const Glib::ustring& fext) const; + void saveMetadata(); + public: Thumbnail (CacheManager* cm, const Glib::ustring& fname, CacheImageData* cf); Thumbnail (CacheManager* cm, const Glib::ustring& fname, const std::string& md5);