Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Batch edit of IPTC tags #6851

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions rtengine/procparams.h
Original file line number Diff line number Diff line change
Expand Up @@ -2083,6 +2083,11 @@ class IPTCPairs final
{
pairs.clear();
}

void insert(Glib::ustring key, Glib::ustring value)
{
pairs.insert({key, std::vector<Glib::ustring>(1, value)});
}

std::vector<Glib::ustring>& operator[](const Glib::ustring& key)
{
Expand Down
250 changes: 245 additions & 5 deletions rtgui/batchtoolpanelcoord.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "procparamchangers.h"
#include "addsetids.h"
#include "thumbnail.h"
#include "iptcpanel.h"

using namespace rtengine::procparams;

Expand Down Expand Up @@ -74,7 +75,7 @@ void BatchToolPanelCoordinator::closeSession (bool save)
// read new values from the gui
for (size_t i = 0; i < toolPanels.size(); i++) {
toolPanels[i]->write (&pparams, &pparamsEdited);
}
}

// combine with initial parameters and set
ProcParams newParams;
Expand Down Expand Up @@ -562,11 +563,250 @@ void BatchToolPanelCoordinator::panelChanged(const rtengine::ProcEvent& event, c
}
}

ProcParams newParams, originalParams;
Glib::ustring captionChanged, captionWriterChanged, headlineChanged, instructionsChanged,
keywordAdded, keywordDeleted,
categoryChanged, suppCategoryAdded, suppCategoryDeleted,
creatorChanged, creatorJobTitleChanged, creditChanged, sourceChanged, copyrightChanged,
cityChanged, provinceChanged, countryChanged,
titleChanged, dateCreatedChanged, transReferenceChanged;
// flags are needed because just checking for an empty string would prevent replacing a tag with an empty string, i.e. clearing it
bool captionChangedFlag = false, captionWriterChangedFlag = false, headlineChangedFlag = false, instructionsChangedFlag = false,
keywordAddedFlag = false, keywordDeletedFlag = false,
categoryChangedFlag = false, suppCategoryAddedFlag = false, suppCategoryDeletedFlag = false,
creatorChangedFlag = false, creatorJobTitleChangedFlag = false,
creditChangedFlag = false, sourceChangedFlag = false, copyrightChangedFlag = false,
cityChangedFlag = false, provinceChangedFlag = false, countryChangedFlag = false,
titleChangedFlag = false, dateCreatedChangedFlag = false, transReferenceChangedFlag = false,
tagFound;

if (event == rtengine::EvIPTC) {
// The following is necessary because it is not assured that initialPP[i].metadata.iptc is always initialized.
// There may be a better way to do this (existing functionality?) but I haven't found out how.
// Also, it might be considered to do this initialization in a more "general" place, e.g. procparams::PartialProfile constructor?
for (size_t i = 0; i < selected.size(); i++) {
if (initialPP[i].metadata.iptc.empty()) {
initialPP[i].metadata.iptc.insert(CAPTION, "");
initialPP[i].metadata.iptc.insert(CAPTION_WRITER, "");
initialPP[i].metadata.iptc.insert(CATEGORY, "");
initialPP[i].metadata.iptc.insert(CITY, "");
initialPP[i].metadata.iptc.insert(COPYRIGHT, "");
initialPP[i].metadata.iptc.insert(COUNTRY, "");
initialPP[i].metadata.iptc.insert(CREATOR, "");
initialPP[i].metadata.iptc.insert(CREATOR_JOB_TITLE, "");
initialPP[i].metadata.iptc.insert(CREDIT, "");
initialPP[i].metadata.iptc.insert(DATE_CREATED, "");
initialPP[i].metadata.iptc.insert(HEADLINE, "");
initialPP[i].metadata.iptc.insert(INSTRUCTIONS, "");
initialPP[i].metadata.iptc.insert(KEYWORDS, "");
initialPP[i].metadata.iptc[KEYWORDS].clear();
initialPP[i].metadata.iptc.insert(PROVINCE, "");
initialPP[i].metadata.iptc.insert(SOURCE, "");
initialPP[i].metadata.iptc.insert(SUPPLEMENTAL_CATEGORIES, "");
initialPP[i].metadata.iptc[SUPPLEMENTAL_CATEGORIES].clear();
initialPP[i].metadata.iptc.insert(TITLE, "");
initialPP[i].metadata.iptc.insert(TRANS_REFERENCE, "");
}
}

// determine changes to IPTC tags of first selected picture
originalParams = initialPP[0];

if (pparams.metadata.iptc[CAPTION].at(0).compare(originalParams.metadata.iptc[CAPTION].at(0)) != 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if-else-if chain belongs in IPTCPanel::write. The booleans like captionChangedFlag can be placed in ParamsEdited. The Glib::ustrings like captionChanged are not needed since they can be found in pparams. The exceptions are the added/deleted keyword/category, which can also go in pparams.

captionChanged = pparams.metadata.iptc[CAPTION].at(0);
captionChangedFlag = true; }
else if (pparams.metadata.iptc[CAPTION_WRITER].at(0).compare(originalParams.metadata.iptc[CAPTION_WRITER].at(0)) != 0) {
captionWriterChanged = pparams.metadata.iptc[CAPTION_WRITER].at(0);
captionWriterChangedFlag = true; }
else if (pparams.metadata.iptc[HEADLINE].at(0).compare(originalParams.metadata.iptc[HEADLINE].at(0)) != 0) {
headlineChanged = pparams.metadata.iptc[HEADLINE].at(0);
headlineChangedFlag = true; }
else if (pparams.metadata.iptc[INSTRUCTIONS].at(0).compare(originalParams.metadata.iptc[INSTRUCTIONS].at(0)) != 0) {
instructionsChanged = pparams.metadata.iptc[INSTRUCTIONS].at(0);
instructionsChangedFlag = true; }
else if (originalParams.metadata.iptc[KEYWORDS].size() < pparams.metadata.iptc[KEYWORDS].size()) {
// get added keyword
// apparently, I can't rely on a specific order of keywords, so have to check them all
for (unsigned int j = 0; j < pparams.metadata.iptc[KEYWORDS].size(); j++) {
tagFound = false;
for (unsigned int k = 0; k < originalParams.metadata.iptc[KEYWORDS].size(); k++) {
if (pparams.metadata.iptc[KEYWORDS].at(j).compare(originalParams.metadata.iptc[KEYWORDS].at(k)) == 0) {
tagFound = true;
break;
}
}
if (!tagFound) {
keywordAdded = pparams.metadata.iptc[KEYWORDS].at(j);
keywordAddedFlag = true;
break;
}
}
}
else if (originalParams.metadata.iptc[KEYWORDS].size() > pparams.metadata.iptc[KEYWORDS].size()) {
// get deleted keyword
// apparently, I can't rely on a specific order of keywords, so have to check them all
for (unsigned int k = 0; k < originalParams.metadata.iptc[KEYWORDS].size(); k++) {
tagFound = false;
for (unsigned int j = 0; j < pparams.metadata.iptc[KEYWORDS].size(); j++) {
if (pparams.metadata.iptc[KEYWORDS].at(j).compare(originalParams.metadata.iptc[KEYWORDS].at(k)) == 0) {
tagFound = true;
break;
}
}
if (!tagFound) {
keywordDeleted = originalParams.metadata.iptc[KEYWORDS].at(k);
keywordDeletedFlag = true;
break;
}
}
}
else if (pparams.metadata.iptc[CATEGORY].at(0).compare(originalParams.metadata.iptc[CATEGORY].at(0)) != 0) {
categoryChanged = pparams.metadata.iptc[CATEGORY].at(0);
categoryChangedFlag = true; }
else if (originalParams.metadata.iptc[SUPPLEMENTAL_CATEGORIES].size() < pparams.metadata.iptc[SUPPLEMENTAL_CATEGORIES].size()) {
// get added supplemental category
// apparently, I can't rely on a specific order, so have to check them all
for (unsigned int j = 0; j < pparams.metadata.iptc[SUPPLEMENTAL_CATEGORIES].size(); j++) {
tagFound = false;
for (unsigned int k = 0; k < originalParams.metadata.iptc[SUPPLEMENTAL_CATEGORIES].size(); k++) {
if (pparams.metadata.iptc[SUPPLEMENTAL_CATEGORIES].at(j).compare(originalParams.metadata.iptc[SUPPLEMENTAL_CATEGORIES].at(k)) == 0) {
tagFound = true;
break;
}
}
if (!tagFound) {
suppCategoryAdded = pparams.metadata.iptc[SUPPLEMENTAL_CATEGORIES].at(j);
suppCategoryAddedFlag = true;
break;
}
}
}
else if (originalParams.metadata.iptc[SUPPLEMENTAL_CATEGORIES].size() > pparams.metadata.iptc[SUPPLEMENTAL_CATEGORIES].size()) {
// get deleted supplemental category
// apparently, I can't rely on a specific order, so have to check them all
for (unsigned int k = 0; k < originalParams.metadata.iptc[SUPPLEMENTAL_CATEGORIES].size(); k++) {
tagFound = false;
for (unsigned int j = 0; j < pparams.metadata.iptc[SUPPLEMENTAL_CATEGORIES].size(); j++) {
if (pparams.metadata.iptc[SUPPLEMENTAL_CATEGORIES].at(j).compare(originalParams.metadata.iptc[SUPPLEMENTAL_CATEGORIES].at(k)) == 0) {
tagFound = true;
break;
}
}
if (!tagFound) {
suppCategoryDeleted = originalParams.metadata.iptc[SUPPLEMENTAL_CATEGORIES].at(k);
suppCategoryDeletedFlag = true;
break;
}
}
}
else if (pparams.metadata.iptc[CREATOR].at(0).compare(originalParams.metadata.iptc[CREATOR].at(0)) != 0) {
creatorChanged = pparams.metadata.iptc[CREATOR].at(0);
creatorChangedFlag = true; }
else if (pparams.metadata.iptc[CREATOR_JOB_TITLE].at(0).compare(originalParams.metadata.iptc[CREATOR_JOB_TITLE].at(0)) != 0) {
creatorJobTitleChanged = pparams.metadata.iptc[CREATOR_JOB_TITLE].at(0);
creatorJobTitleChangedFlag = true; }
else if (pparams.metadata.iptc[CREDIT].at(0).compare(originalParams.metadata.iptc[CREDIT].at(0)) != 0) {
creditChanged = pparams.metadata.iptc[CREDIT].at(0);
creditChangedFlag = true; }
else if (pparams.metadata.iptc[SOURCE].at(0).compare(originalParams.metadata.iptc[SOURCE].at(0)) != 0) {
sourceChanged = pparams.metadata.iptc[SOURCE].at(0);
sourceChangedFlag = true; }
else if (pparams.metadata.iptc[COPYRIGHT].at(0).compare(originalParams.metadata.iptc[COPYRIGHT].at(0)) != 0) {
copyrightChanged = pparams.metadata.iptc[COPYRIGHT].at(0);
copyrightChangedFlag = true; }
else if (pparams.metadata.iptc[CITY].at(0).compare(originalParams.metadata.iptc[CITY].at(0)) != 0) {
cityChanged = pparams.metadata.iptc[CITY].at(0);
cityChangedFlag = true; }
else if (pparams.metadata.iptc[PROVINCE].at(0).compare(originalParams.metadata.iptc[PROVINCE].at(0)) != 0) {
provinceChanged = pparams.metadata.iptc[PROVINCE].at(0);
provinceChangedFlag = true; }
else if (pparams.metadata.iptc[COUNTRY].at(0).compare(originalParams.metadata.iptc[COUNTRY].at(0)) != 0) {
countryChanged = pparams.metadata.iptc[COUNTRY].at(0);
countryChangedFlag = true; }
else if (pparams.metadata.iptc[TITLE].at(0).compare(originalParams.metadata.iptc[TITLE].at(0)) != 0) {
titleChanged = pparams.metadata.iptc[TITLE].at(0);
titleChangedFlag = true; }
else if (pparams.metadata.iptc[DATE_CREATED].at(0).compare(originalParams.metadata.iptc[DATE_CREATED].at(0)) != 0) {
dateCreatedChanged = pparams.metadata.iptc[DATE_CREATED].at(0);
dateCreatedChangedFlag = true; }
else if (pparams.metadata.iptc[TRANS_REFERENCE].at(0).compare(originalParams.metadata.iptc[TRANS_REFERENCE].at(0)) != 0) {
transReferenceChanged = pparams.metadata.iptc[TRANS_REFERENCE].at(0);
transReferenceChangedFlag = true; }
}

// combine with initial parameters and set
ProcParams newParams;

for (size_t i = 0; i < selected.size(); i++) {
newParams = initialPP[i];
newParams = initialPP[i];

if (event == rtengine::EvIPTC) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this block into ParamsEdited::combine.

if (captionChangedFlag)
initialPP[i].metadata.iptc[CAPTION].at(0) = captionChanged;
else if (captionWriterChangedFlag)
initialPP[i].metadata.iptc[CAPTION_WRITER].at(0) = captionWriterChanged;
else if (headlineChangedFlag)
initialPP[i].metadata.iptc[HEADLINE].at(0) = headlineChanged;
else if (instructionsChangedFlag)
initialPP[i].metadata.iptc[INSTRUCTIONS].at(0) = instructionsChanged;
else if (keywordAddedFlag) {
unsigned int j;
for (j = 0; j < newParams.metadata.iptc[KEYWORDS].size(); j++) {
if (newParams.metadata.iptc[KEYWORDS].at(j).compare(keywordAdded) == 0)
break;
}
if (j == newParams.metadata.iptc[KEYWORDS].size())
initialPP[i].metadata.iptc[KEYWORDS].push_back(keywordAdded);
}
else if (keywordDeletedFlag) {
initialPP[i].metadata.iptc[KEYWORDS].clear();
for (unsigned int j = 0; j < newParams.metadata.iptc[KEYWORDS].size(); j++) {
if (newParams.metadata.iptc[KEYWORDS].at(j).compare(keywordDeleted) != 0)
initialPP[i].metadata.iptc[KEYWORDS].push_back(newParams.metadata.iptc[KEYWORDS].at(j));
}
}
else if (categoryChangedFlag)
initialPP[i].metadata.iptc[CATEGORY].at(0) = categoryChanged;
else if (suppCategoryAddedFlag) {
unsigned int j;
for (j = 0; j < newParams.metadata.iptc[SUPPLEMENTAL_CATEGORIES].size(); j++) {
if (newParams.metadata.iptc[SUPPLEMENTAL_CATEGORIES].at(j).compare(suppCategoryAdded) == 0)
break;
}
if (j == newParams.metadata.iptc[SUPPLEMENTAL_CATEGORIES].size())
initialPP[i].metadata.iptc[SUPPLEMENTAL_CATEGORIES].push_back(suppCategoryAdded);
}
else if (suppCategoryDeletedFlag) {
initialPP[i].metadata.iptc[SUPPLEMENTAL_CATEGORIES].clear();
for (unsigned int j = 0; j < newParams.metadata.iptc[SUPPLEMENTAL_CATEGORIES].size(); j++) {
if (newParams.metadata.iptc[SUPPLEMENTAL_CATEGORIES].at(j).compare(suppCategoryDeleted) != 0)
initialPP[i].metadata.iptc[SUPPLEMENTAL_CATEGORIES].push_back(newParams.metadata.iptc[SUPPLEMENTAL_CATEGORIES].at(j));
}
}
else if (creatorChangedFlag)
initialPP[i].metadata.iptc[CREATOR].at(0) = creatorChanged;
else if (creatorJobTitleChangedFlag)
initialPP[i].metadata.iptc[CREATOR_JOB_TITLE].at(0) = creatorJobTitleChanged;
else if (creditChangedFlag)
initialPP[i].metadata.iptc[CREDIT].at(0) = creditChanged;
else if (sourceChangedFlag)
initialPP[i].metadata.iptc[SOURCE].at(0) = sourceChanged;
else if (copyrightChangedFlag)
initialPP[i].metadata.iptc[COPYRIGHT].at(0) = copyrightChanged;
else if (cityChangedFlag)
initialPP[i].metadata.iptc[CITY].at(0) = cityChanged;
else if (provinceChangedFlag)
initialPP[i].metadata.iptc[PROVINCE].at(0) = provinceChanged;
else if (countryChangedFlag)
initialPP[i].metadata.iptc[COUNTRY].at(0) = countryChanged;
else if (titleChangedFlag)
initialPP[i].metadata.iptc[TITLE].at(0) = titleChanged;
else if (dateCreatedChangedFlag)
initialPP[i].metadata.iptc[DATE_CREATED].at(0) = dateCreatedChanged;
else if (transReferenceChangedFlag)
initialPP[i].metadata.iptc[TRANS_REFERENCE].at(0) = transReferenceChanged;
newParams.metadata.iptc = initialPP[i].metadata.iptc;
pparamsEdited.iptc = false;
}
// If only one file is selected, slider's addMode has been set to false, and hence the behave
// like in SET mode like in an editor ; that's why we force the combination to the SET mode too
pparamsEdited.combine (newParams, pparams, selected.size() == 1);
Expand All @@ -577,8 +817,8 @@ void BatchToolPanelCoordinator::panelChanged(const rtengine::ProcEvent& event, c
}

selected[i]->setProcParams (newParams, nullptr, BATCHEDITOR, false);
}

}
for (size_t i = 0; i < paramcListeners.size(); i++) {
paramcListeners[i]->procParamsChanged (&pparams, event, descr, &pparamsEdited);
}
Expand Down
19 changes: 0 additions & 19 deletions rtgui/iptcpanel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,6 @@ using namespace rtengine::procparams;

namespace {

const std::string CAPTION("Iptc.Application2.Caption");
const std::string CAPTION_WRITER("Iptc.Application2.Writer");
const std::string CATEGORY("Iptc.Application2.Category");
const std::string CITY("Iptc.Application2.City");
const std::string COPYRIGHT("Iptc.Application2.Copyright");
const std::string COUNTRY("Iptc.Application2.CountryName");
const std::string CREATOR("Iptc.Application2.Byline");
const std::string CREATOR_JOB_TITLE("Iptc.Application2.BylineTitle");
const std::string CREDIT("Iptc.Application2.Credit");
const std::string DATE_CREATED("Iptc.Application2.DateCreated");
const std::string HEADLINE("Iptc.Application2.Headline");
const std::string INSTRUCTIONS("Iptc.Application2.SpecialInstructions");
const std::string KEYWORDS("Iptc.Application2.Keywords");
const std::string PROVINCE("Iptc.Application2.ProvinceState");
const std::string SOURCE("Iptc.Application2.Source");
const std::string SUPPLEMENTAL_CATEGORIES("Iptc.Application2.SuppCategory");
const std::string TITLE("Iptc.Application2.ObjectName");
const std::string TRANS_REFERENCE("Iptc.Application2.TransmissionReference");

const std::set<std::string> iptc_keys = {
CAPTION,
CAPTION_WRITER,
Expand Down
20 changes: 20 additions & 0 deletions rtgui/iptcpanel.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,23 @@ class IPTCPanel final :
void copyClicked ();
void pasteClicked ();
};

const std::string CAPTION("Iptc.Application2.Caption");
const std::string CAPTION_WRITER("Iptc.Application2.Writer");
const std::string CATEGORY("Iptc.Application2.Category");
const std::string CITY("Iptc.Application2.City");
const std::string COPYRIGHT("Iptc.Application2.Copyright");
const std::string COUNTRY("Iptc.Application2.CountryName");
const std::string CREATOR("Iptc.Application2.Byline");
const std::string CREATOR_JOB_TITLE("Iptc.Application2.BylineTitle");
const std::string CREDIT("Iptc.Application2.Credit");
const std::string DATE_CREATED("Iptc.Application2.DateCreated");
const std::string HEADLINE("Iptc.Application2.Headline");
const std::string INSTRUCTIONS("Iptc.Application2.SpecialInstructions");
const std::string KEYWORDS("Iptc.Application2.Keywords");
const std::string PROVINCE("Iptc.Application2.ProvinceState");
const std::string SOURCE("Iptc.Application2.Source");
const std::string SUPPLEMENTAL_CATEGORIES("Iptc.Application2.SuppCategory");
const std::string TITLE("Iptc.Application2.ObjectName");
const std::string TRANS_REFERENCE("Iptc.Application2.TransmissionReference");

3 changes: 1 addition & 2 deletions rtgui/metadatapanel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ void MetaDataPanel::setBatchMode(bool batchMode)
{
ToolPanel::setBatchMode(batchMode);
metadataMode->append(M("GENERAL_UNCHANGED"));
tagsNotebook->remove_page(-1);
tagsNotebook->remove_page(-1);
tagsNotebook->remove_page(0); // remove EXIF page
}


Expand Down
26 changes: 13 additions & 13 deletions rtgui/toolpanelcoord.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1141,19 +1141,19 @@ void ToolPanelCoordinator::panelChanged(const rtengine::ProcEvent& event, const
// Locallab spot curves are set visible if at least one photo has been loaded (to avoid
// segfault) and locallab panel is active
// When a new photo is loaded, Locallab spot curves need to be set visible again
const auto func =
[this]() -> bool
{
if (photoLoadedOnce && (toolPanelNotebook->get_nth_page(toolPanelNotebook->get_current_page()) == locallabPanelSW)) {
locallab->subscribe();
}

return false;
};

if (event == rtengine::EvPhotoLoaded) {
idle_register.add(func);
}
const auto func =
[this]() -> bool
{
if (photoLoadedOnce && (toolPanelNotebook->get_nth_page(toolPanelNotebook->get_current_page()) == locallabPanelSW)) {
locallab->subscribe();
}

return false;
};

if (event == rtengine::EvPhotoLoaded) {
idle_register.add(func);
}

photoLoadedOnce = true;

Expand Down