Add optional image rank/color load/save from/to xmp sidecar
Add optional ability to load/save image rank property from/to xmp sidecar "xmp.Rating" and color property from xmp "xmp.Label" ignoring the ones provided in the processing params file. This behavior is disabled by default and an option under settings -> file browser has been added to enable it. When enabled: * On load: * rank and color are not read from processing params. * rank is mapped from xmp sidecar file rating entry. * color is mapped from xmp sidecar file label entry. * On save: * rank and color are saved to the xmp sidecar * rank and color are also saved to the processing param (pp3) files to keep them in sync Rating mapping: Since rating can be also -1 but rank only goes from 0 to 5, the -1 value is ignored like already done when importing from embedded xmp data. Color mapping: XMP has no color concept, usually programs like digikam uses the label field to write a color name ("Red", "Orange"). The problem is that this isn't standardized and label can be any string. Additionally Rawtherapee has 5 specific colors while other programs can have different colors with different name so they won't be shown if they don't map to the 5 color names supported by rawtherapee. On save only the 5 color supported by rawtherapee wil be saved. Trash is kept only in the profile files for multiple reasons: * There's no trash concept in xmp, there's the rejected concept assigned to a rating == -1. * We could map rejected to trash but in rawtherapee rank and trash are two different values and an image can have both rank >= 0 and trashed set to true. Using an unique value like rating for rank and trash (when -1) will require changing the current rawtherapee logic. * Also digikam only handles ratings from 0 to 5 (no -1) and handles trash in its own internal way without reflecting it in the xmp sidecar.
This commit is contained in:
@@ -105,6 +105,46 @@ bool CPBDump(
|
||||
return true;
|
||||
}
|
||||
|
||||
struct ColorMapper {
|
||||
std::map<int, std::string> indexLabelMap;
|
||||
std::map<std::string, int> labelIndexMap;
|
||||
|
||||
ColorMapper(std::map<int, std::string> colors) {
|
||||
for (const auto& color: colors) {
|
||||
indexLabelMap.insert({color.first, color.second});
|
||||
labelIndexMap.insert({color.second, color.first});
|
||||
}
|
||||
}
|
||||
|
||||
int index(const std::string &label) const
|
||||
{
|
||||
auto it = labelIndexMap.find(label);
|
||||
if (it != labelIndexMap.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string label(int index) const
|
||||
{
|
||||
auto it = indexLabelMap.find(index);
|
||||
if (it != indexLabelMap.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
const std::map<int, std::string> defaultColors = {
|
||||
{1, "Red"},
|
||||
{2, "Yellow"},
|
||||
{3, "Green"},
|
||||
{4, "Blue"},
|
||||
{5, "Purple"}
|
||||
};
|
||||
|
||||
auto defaultColorMapper = ColorMapper(defaultColors);
|
||||
|
||||
} // namespace
|
||||
|
||||
using namespace rtengine::procparams;
|
||||
@@ -1034,6 +1074,8 @@ void Thumbnail::saveThumbnail ()
|
||||
*/
|
||||
void Thumbnail::updateCache (bool updatePParams, bool updateCacheImageData)
|
||||
{
|
||||
updateProcParamsProperties();
|
||||
|
||||
if (updatePParams && pparamsValid) {
|
||||
pparams->save (
|
||||
options.saveParamsFile ? fname + paramFileExtension : "",
|
||||
@@ -1049,6 +1091,8 @@ void Thumbnail::updateCache (bool updatePParams, bool updateCacheImageData)
|
||||
if (updatePParams && pparamsValid) {
|
||||
saveMetadata();
|
||||
}
|
||||
|
||||
saveXMPSidecarProperties();
|
||||
}
|
||||
|
||||
Thumbnail::~Thumbnail ()
|
||||
@@ -1227,13 +1271,37 @@ void Thumbnail::loadProperties()
|
||||
}
|
||||
}
|
||||
|
||||
// update rank and load color, trash from procparams
|
||||
// update rank and color from procparams or xmp sidecar
|
||||
// load trash from procparams
|
||||
if (pparamsValid) {
|
||||
if (pparams->rank >= 0) {
|
||||
properties.rank.value = pparams->rank;
|
||||
if (options.thumbnailRankColorMode == Options::ThumbnailPropertyMode::PROCPARAMS) {
|
||||
if (pparams->rank >= 0) {
|
||||
properties.rank.value = pparams->rank;
|
||||
}
|
||||
}
|
||||
properties.color.value = pparams->colorlabel;
|
||||
|
||||
properties.trashed.value = pparams->inTrash;
|
||||
properties.color.value = pparams->colorlabel;
|
||||
}
|
||||
|
||||
if (options.thumbnailRankColorMode == Options::ThumbnailPropertyMode::XMP) {
|
||||
try {
|
||||
auto xmp = rtengine::Exiv2Metadata::getXmpSidecar(fname);
|
||||
auto pos = xmp.findKey(Exiv2::XmpKey("Xmp.xmp.Rating"));
|
||||
if (pos != xmp.end()) {
|
||||
int r = rtengine::to_long(pos);
|
||||
properties.rank.value = rtengine::LIM(r, 0, 5);
|
||||
}
|
||||
|
||||
pos = xmp.findKey(Exiv2::XmpKey("Xmp.xmp.Label"));
|
||||
if (pos != xmp.end()) {
|
||||
properties.color.value = defaultColorMapper.index(pos->toString());
|
||||
}
|
||||
} catch (std::exception &exc) {
|
||||
std::cerr << "ERROR loading thumbnail properties data from "
|
||||
<< rtengine::Exiv2Metadata::xmpSidecarPath(fname)
|
||||
<< ": " << exc.what() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1248,6 +1316,8 @@ void Thumbnail::updateProcParamsProperties()
|
||||
pparamsValid = true;
|
||||
}
|
||||
|
||||
// save procparams rank and color also when options.thumbnailRankColorMode == Options::ThumbnailPropertyMode::XMP
|
||||
// so they'll be kept in sync
|
||||
if (properties.rank.edited && properties.rank != pparams->rank) {
|
||||
pparams->rank = properties.rank;
|
||||
pparamsValid = true;
|
||||
@@ -1259,6 +1329,35 @@ void Thumbnail::updateProcParamsProperties()
|
||||
}
|
||||
}
|
||||
|
||||
void Thumbnail::saveXMPSidecarProperties()
|
||||
{
|
||||
if (!properties.edited()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.thumbnailRankColorMode != Options::ThumbnailPropertyMode::XMP) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto fn = rtengine::Exiv2Metadata::xmpSidecarPath(fname);
|
||||
try {
|
||||
auto xmp = rtengine::Exiv2Metadata::getXmpSidecar(fname);
|
||||
if (properties.rank.edited) {
|
||||
xmp["Xmp.xmp.Rating"] = std::to_string(properties.rank);
|
||||
}
|
||||
if (properties.color.edited) {
|
||||
xmp["Xmp.xmp.Label"] = defaultColorMapper.label(properties.color);
|
||||
}
|
||||
|
||||
rtengine::Exiv2Metadata meta;
|
||||
meta.xmpData() = std::move(xmp);
|
||||
meta.saveToXmp(fn);
|
||||
} catch (std::exception &exc) {
|
||||
std::cerr << "ERROR saving thumbnail properties data to " << fn
|
||||
<< ": " << exc.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void Thumbnail::saveMetadata()
|
||||
{
|
||||
if (options.rtSettings.metadata_xmp_sync != rtengine::Settings::MetadataXmpSync::READ_WRITE) {
|
||||
|
Reference in New Issue
Block a user