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:
Simone Gotti 2024-03-12 18:12:49 +01:00
parent 68fd35d881
commit 1098966a8b
9 changed files with 152 additions and 17 deletions

View File

@ -2033,6 +2033,7 @@ PREFERENCES_THUMBNAIL_INSPECTOR_JPEG;Embedded JPEG preview
PREFERENCES_THUMBNAIL_INSPECTOR_MODE;Image to show PREFERENCES_THUMBNAIL_INSPECTOR_MODE;Image to show
PREFERENCES_THUMBNAIL_INSPECTOR_RAW;Neutral raw rendering PREFERENCES_THUMBNAIL_INSPECTOR_RAW;Neutral raw rendering
PREFERENCES_THUMBNAIL_INSPECTOR_RAW_IF_NO_JPEG_FULLSIZE;Embedded JPEG if fullsize, neutral raw otherwise PREFERENCES_THUMBNAIL_INSPECTOR_RAW_IF_NO_JPEG_FULLSIZE;Embedded JPEG if fullsize, neutral raw otherwise
PREFERENCES_THUMBNAIL_RANK_COLOR_MODE;Load/Save thumbnail rank and color from/to XMP sidecars
PREFERENCES_TOOLPANEL_AVAILABLETOOLS;Available Tools PREFERENCES_TOOLPANEL_AVAILABLETOOLS;Available Tools
PREFERENCES_TOOLPANEL_CLONE_FAVORITES;Keep favorite tools in original locations PREFERENCES_TOOLPANEL_CLONE_FAVORITES;Keep favorite tools in original locations
PREFERENCES_TOOLPANEL_CLONE_FAVORITES_TOOLTIP;If set, favorite tools will appear in both the favorites tab and their original tabs.\n\nNote: Enabling this option may result in a slight delay when switching tabs. PREFERENCES_TOOLPANEL_CLONE_FAVORITES_TOOLTIP;If set, favorite tools will appear in both the favorites tab and their original tabs.\n\nNote: Enabling this option may result in a slight delay when switching tabs.

View File

@ -106,19 +106,6 @@ void clear_metadata_key(Data &data, const Key &key)
} }
} }
template <typename Iterator, typename Integer = std::size_t>
auto to_long(const Iterator &iter, Integer n = Integer{0}) -> decltype(
#if EXIV2_TEST_VERSION(0,28,0)
iter->toInt64()
) {
return iter->toInt64(n);
#else
iter->toLong()
) {
return iter->toLong(n);
#endif
}
} // namespace } // namespace

View File

@ -97,4 +97,18 @@ private:
static std::unique_ptr<ImageCache> cache_; static std::unique_ptr<ImageCache> cache_;
}; };
template <typename Iterator, typename Integer = std::size_t>
auto to_long(const Iterator &iter, Integer n = Integer{0}) -> decltype(
#if EXIV2_TEST_VERSION(0,28,0)
iter->toInt64()
) {
return iter->toInt64(n);
#else
iter->toLong()
) {
return iter->toLong(n);
#endif
}
} // namespace rtengine } // namespace rtengine

View File

@ -694,6 +694,7 @@ void Options::setDefaults()
lastICCProfCreatorDir = ""; lastICCProfCreatorDir = "";
gimpPluginShowInfoDialog = true; gimpPluginShowInfoDialog = true;
maxRecentFolders = 15; maxRecentFolders = 15;
thumbnailRankColorMode = Options::ThumbnailPropertyMode::PROCPARAMS;
sortMethod = SORT_BY_NAME; sortMethod = SORT_BY_NAME;
sortDescending = false; sortDescending = false;
rtSettings.lensfunDbDirectory = ""; // set also in main.cc and main-cli.cc rtSettings.lensfunDbDirectory = ""; // set also in main.cc and main-cli.cc
@ -1366,6 +1367,15 @@ void Options::readFromFile(Glib::ustring fname)
if (keyFile.has_key("File Browser", "BrowseRecursiveFollowLinks")) { if (keyFile.has_key("File Browser", "BrowseRecursiveFollowLinks")) {
browseRecursiveFollowLinks = keyFile.get_boolean("File Browser", "BrowseRecursiveFollowLinks"); browseRecursiveFollowLinks = keyFile.get_boolean("File Browser", "BrowseRecursiveFollowLinks");
} }
if (keyFile.has_key("File Browser", "ThumbnailRankColorMode")) {
std::string val = keyFile.get_string("File Browser", "ThumbnailRankColorMode");
if (val == "xmp") {
thumbnailRankColorMode = ThumbnailPropertyMode::XMP;
} else {
thumbnailRankColorMode = ThumbnailPropertyMode::PROCPARAMS;
}
}
} }
if (keyFile.has_group("Clipping Indication")) { if (keyFile.has_group("Clipping Indication")) {
@ -2462,6 +2472,14 @@ void Options::saveToFile(Glib::ustring fname)
keyFile.set_string_list("File Browser", "RecentFolders", temp); keyFile.set_string_list("File Browser", "RecentFolders", temp);
} }
switch (thumbnailRankColorMode) {
case ThumbnailPropertyMode::XMP:
keyFile.set_string("File Browser", "ThumbnailRankColorMode", "xmp");
break;
default: // ThumbnailPropertyMode::PROCPARAMS
keyFile.set_string("File Browser", "ThumbnailRankColorMode", "procparams");
break;
}
keyFile.set_integer("File Browser", "SortMethod", sortMethod); keyFile.set_integer("File Browser", "SortMethod", sortMethod);
keyFile.set_boolean("File Browser", "SortDescending", sortDescending); keyFile.set_boolean("File Browser", "SortDescending", sortDescending);
keyFile.set_boolean("File Browser", "BrowseRecursive", browseRecursive); keyFile.set_boolean("File Browser", "BrowseRecursive", browseRecursive);

View File

@ -492,6 +492,12 @@ public:
size_t maxRecentFolders; // max. number of recent folders stored in options file size_t maxRecentFolders; // max. number of recent folders stored in options file
std::vector<Glib::ustring> recentFolders; // List containing all recent folders std::vector<Glib::ustring> recentFolders; // List containing all recent folders
enum class ThumbnailPropertyMode {
PROCPARAMS, // store rank and color in procparams sidecars
XMP // store rank and color xmp sidecar
};
ThumbnailPropertyMode thumbnailRankColorMode;
enum SortMethod { enum SortMethod {
SORT_BY_NAME, SORT_BY_NAME,
SORT_BY_DATE, SORT_BY_DATE,

View File

@ -1470,6 +1470,9 @@ Gtk::Widget* Preferences::getFileBrowserPanel()
vbro->pack_start(*sameThumbSize, Gtk::PACK_SHRINK, 0); vbro->pack_start(*sameThumbSize, Gtk::PACK_SHRINK, 0);
vbro->pack_start(*ckbInternalThumbIfUntouched, Gtk::PACK_SHRINK, 0); vbro->pack_start(*ckbInternalThumbIfUntouched, Gtk::PACK_SHRINK, 0);
thumbnailRankColorMode = Gtk::manage(new Gtk::CheckButton(M("PREFERENCES_THUMBNAIL_RANK_COLOR_MODE")));
vbro->pack_start(*thumbnailRankColorMode, Gtk::PACK_SHRINK, 0);
Gtk::Box* hbrecent = Gtk::manage(new Gtk::Box()); Gtk::Box* hbrecent = Gtk::manage(new Gtk::Box());
Gtk::Label* labrecent = Gtk::manage (new Gtk::Label (M("PREFERENCES_MAXRECENTFOLDERS") + ":", Gtk::ALIGN_START)); Gtk::Label* labrecent = Gtk::manage (new Gtk::Label (M("PREFERENCES_MAXRECENTFOLDERS") + ":", Gtk::ALIGN_START));
maxRecentFolders = Gtk::manage(new Gtk::SpinButton()); maxRecentFolders = Gtk::manage(new Gtk::SpinButton());
@ -2029,6 +2032,8 @@ void Preferences::storePreferences()
moptions.rtSettings.metadata_xmp_sync = rtengine::Settings::MetadataXmpSync(metadataSyncCombo->get_active_row_number()); 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()); moptions.rtSettings.xmp_sidecar_style = rtengine::Settings::XmpSidecarStyle(xmpSidecarCombo->get_active_row_number());
moptions.thumbnailRankColorMode = thumbnailRankColorMode->get_active() ? Options::ThumbnailPropertyMode::XMP : Options::ThumbnailPropertyMode::PROCPARAMS;
} }
void Preferences::fillPreferences() void Preferences::fillPreferences()
@ -2287,6 +2292,8 @@ void Preferences::fillPreferences()
metadataSyncCombo->set_active(int(moptions.rtSettings.metadata_xmp_sync)); metadataSyncCombo->set_active(int(moptions.rtSettings.metadata_xmp_sync));
xmpSidecarCombo->set_active(int(moptions.rtSettings.xmp_sidecar_style)); xmpSidecarCombo->set_active(int(moptions.rtSettings.xmp_sidecar_style));
thumbnailRankColorMode->set_active(moptions.thumbnailRankColorMode == Options::ThumbnailPropertyMode::XMP);
} }
/* /*

View File

@ -224,6 +224,8 @@ class Preferences final :
Gtk::CheckButton* ckbInternalThumbIfUntouched; Gtk::CheckButton* ckbInternalThumbIfUntouched;
Gtk::CheckButton *thumbnailRankColorMode;
Gtk::Entry* txtCustProfBuilderPath; Gtk::Entry* txtCustProfBuilderPath;
Gtk::ComboBoxText* custProfBuilderLabelType; Gtk::ComboBoxText* custProfBuilderLabelType;

View File

@ -105,6 +105,46 @@ bool CPBDump(
return true; 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 } // namespace
using namespace rtengine::procparams; using namespace rtengine::procparams;
@ -1034,6 +1074,8 @@ void Thumbnail::saveThumbnail ()
*/ */
void Thumbnail::updateCache (bool updatePParams, bool updateCacheImageData) void Thumbnail::updateCache (bool updatePParams, bool updateCacheImageData)
{ {
updateProcParamsProperties();
if (updatePParams && pparamsValid) { if (updatePParams && pparamsValid) {
pparams->save ( pparams->save (
options.saveParamsFile ? fname + paramFileExtension : "", options.saveParamsFile ? fname + paramFileExtension : "",
@ -1049,6 +1091,8 @@ void Thumbnail::updateCache (bool updatePParams, bool updateCacheImageData)
if (updatePParams && pparamsValid) { if (updatePParams && pparamsValid) {
saveMetadata(); saveMetadata();
} }
saveXMPSidecarProperties();
} }
Thumbnail::~Thumbnail () 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 (pparamsValid) {
if (options.thumbnailRankColorMode == Options::ThumbnailPropertyMode::PROCPARAMS) {
if (pparams->rank >= 0) { if (pparams->rank >= 0) {
properties.rank.value = pparams->rank; properties.rank.value = pparams->rank;
} }
properties.color.value = pparams->colorlabel; }
properties.trashed.value = pparams->inTrash; 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; 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) { if (properties.rank.edited && properties.rank != pparams->rank) {
pparams->rank = properties.rank; pparams->rank = properties.rank;
pparamsValid = true; 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() void Thumbnail::saveMetadata()
{ {
if (options.rtSettings.metadata_xmp_sync != rtengine::Settings::MetadataXmpSync::READ_WRITE) { if (options.rtSettings.metadata_xmp_sync != rtengine::Settings::MetadataXmpSync::READ_WRITE) {

View File

@ -116,6 +116,7 @@ class Thumbnail
void saveMetadata(); void saveMetadata();
void loadProperties(); void loadProperties();
void updateProcParamsProperties(); void updateProcParamsProperties();
void saveXMPSidecarProperties();
public: public:
Thumbnail (CacheManager* cm, const Glib::ustring& fname, CacheImageData* cf); Thumbnail (CacheManager* cm, const Glib::ustring& fname, CacheImageData* cf);