File indexing completed on 2024-05-12 15:59:16
0001 /* 0002 * SPDX-FileCopyrightText: 2008 Cyrille Berger <cberger@cberger.net> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.1-or-later 0005 */ 0006 0007 #include "kis_meta_data_merge_strategy_p.h" 0008 #include <klocalizedstring.h> 0009 0010 #include "kis_debug.h" 0011 0012 #include "kis_meta_data_entry.h" 0013 #include "kis_meta_data_schema.h" 0014 #include "kis_meta_data_schema_registry.h" 0015 #include "kis_meta_data_store.h" 0016 #include "kis_meta_data_value.h" 0017 0018 using namespace KisMetaData; 0019 0020 //-------------------------------------------// 0021 //------------ DropMergeStrategy ------------// 0022 //-------------------------------------------// 0023 0024 DropMergeStrategy::DropMergeStrategy() 0025 { 0026 } 0027 0028 DropMergeStrategy::~DropMergeStrategy() 0029 { 0030 } 0031 0032 QString DropMergeStrategy::id() const 0033 { 0034 return "Drop"; 0035 } 0036 QString DropMergeStrategy::name() const 0037 { 0038 return i18n("Drop"); 0039 } 0040 0041 QString DropMergeStrategy::description() const 0042 { 0043 return i18n("Drop all meta data"); 0044 } 0045 0046 void DropMergeStrategy::merge(Store* dst, QList<const Store*> srcs, QList<double> score) const 0047 { 0048 Q_UNUSED(dst); 0049 Q_UNUSED(srcs); 0050 Q_UNUSED(score); 0051 dbgMetaData << "Drop meta data"; 0052 } 0053 0054 //---------------------------------------// 0055 //---------- DropMergeStrategy ----------// 0056 //---------------------------------------// 0057 0058 PriorityToFirstMergeStrategy::PriorityToFirstMergeStrategy() 0059 { 0060 } 0061 0062 PriorityToFirstMergeStrategy::~PriorityToFirstMergeStrategy() 0063 { 0064 } 0065 0066 QString PriorityToFirstMergeStrategy::id() const 0067 { 0068 return "PriorityToFirst"; 0069 } 0070 QString PriorityToFirstMergeStrategy::name() const 0071 { 0072 return i18n("Priority to first meta data"); 0073 } 0074 0075 QString PriorityToFirstMergeStrategy::description() const 0076 { 0077 return i18n("Use in priority the meta data from the layers at the bottom of the stack."); 0078 } 0079 0080 void PriorityToFirstMergeStrategy::merge(Store* dst, QList<const Store*> srcs, QList<double> score) const 0081 { 0082 Q_UNUSED(score); 0083 dbgMetaData << "Priority to first meta data"; 0084 0085 Q_FOREACH (const Store* store, srcs) { 0086 QList<QString> keys = store->keys(); 0087 Q_FOREACH (const QString & key, keys) { 0088 if (!dst->containsEntry(key)) { 0089 dst->addEntry(store->getEntry(key)); 0090 } 0091 } 0092 } 0093 } 0094 //-------------------------------------------// 0095 //------ OnlyIdenticalMergeStrategy ---------// 0096 //-------------------------------------------// 0097 0098 OnlyIdenticalMergeStrategy::OnlyIdenticalMergeStrategy() 0099 { 0100 } 0101 0102 OnlyIdenticalMergeStrategy::~OnlyIdenticalMergeStrategy() 0103 { 0104 } 0105 0106 QString OnlyIdenticalMergeStrategy::id() const 0107 { 0108 return "OnlyIdentical"; 0109 } 0110 QString OnlyIdenticalMergeStrategy::name() const 0111 { 0112 return i18n("Only identical"); 0113 } 0114 0115 QString OnlyIdenticalMergeStrategy::description() const 0116 { 0117 return i18n("Keep only meta data that are identical"); 0118 } 0119 0120 void OnlyIdenticalMergeStrategy::merge(Store* dst, QList<const Store*> srcs, QList<double> score) const 0121 { 0122 Q_UNUSED(score); 0123 dbgMetaData << "OnlyIdenticalMergeStrategy"; 0124 dbgMetaData << "Priority to first meta data"; 0125 0126 Q_ASSERT(srcs.size() > 0); 0127 QList<QString> keys = srcs[0]->keys(); 0128 Q_FOREACH (const QString & key, keys) { 0129 bool keep = true; 0130 const Entry& e = srcs[0]->getEntry(key); 0131 const Value& v = e.value(); 0132 Q_FOREACH (const Store* store, srcs) { 0133 if (!(store->containsEntry(key) && e.value() == v)) { 0134 keep = false; 0135 break; 0136 } 0137 } 0138 if (keep) { 0139 dst->addEntry(e); 0140 } 0141 } 0142 } 0143 0144 //-------------------------------------------// 0145 //------------ SmartMergeStrategy -----------// 0146 //-------------------------------------------// 0147 0148 SmartMergeStrategy::SmartMergeStrategy() 0149 { 0150 } 0151 0152 SmartMergeStrategy::~SmartMergeStrategy() 0153 { 0154 } 0155 0156 QString SmartMergeStrategy::id() const 0157 { 0158 return "Smart"; 0159 } 0160 QString SmartMergeStrategy::name() const 0161 { 0162 return i18n("Smart"); 0163 } 0164 0165 QString SmartMergeStrategy::description() const 0166 { 0167 return i18n("This merge strategy attempts to find the best solution for merging, " 0168 "for instance by merging the list of authors together, or keeping " 0169 "identical photographic information."); 0170 } 0171 0172 struct ScoreValue { 0173 double score; 0174 Value value; 0175 }; 0176 0177 Value SmartMergeStrategy::election(QList<const Store*> srcs, QList<double> scores, const QString & key) const 0178 { 0179 QList<ScoreValue> scoreValues; 0180 for (int i = 0; i < srcs.size(); i++) { 0181 if (srcs[i]->containsEntry(key)) { 0182 const Value& nv = srcs[i]->getEntry(key).value(); 0183 if (nv.type() != Value::Invalid) { 0184 bool found = false; 0185 for (int j = 0; j < scoreValues.size(); j++) { 0186 ScoreValue& sv = scoreValues[j]; 0187 if (sv.value == nv) { 0188 found = true; 0189 sv.score += scores[i]; 0190 break; 0191 } 0192 } 0193 if (!found) { 0194 ScoreValue sv; 0195 sv.score = scores[i]; 0196 sv.value = nv; 0197 scoreValues.append(sv); 0198 } 0199 } 0200 } 0201 } 0202 if (scoreValues.size() < 1) { 0203 warnMetaData << "SmartMergeStrategy::election returned less than 1 score value"; 0204 return Value(); 0205 } 0206 const ScoreValue* bestSv = 0; 0207 double bestScore = -1.0; 0208 Q_FOREACH (const ScoreValue& sv, scoreValues) { 0209 if (sv.score > bestScore) { 0210 bestScore = sv.score; 0211 bestSv = &sv; 0212 } 0213 } 0214 if (bestSv) { 0215 return bestSv->value; 0216 } 0217 else { 0218 return Value(); 0219 } 0220 } 0221 0222 void SmartMergeStrategy::mergeEntry(Store* dst, QList<const Store*> srcs, const KisMetaData::Schema* schema, const QString & identifier) const 0223 { 0224 bool foundOnce = false; 0225 Value v(QList<Value>(), Value::OrderedArray); 0226 Q_FOREACH (const Store* store, srcs) { 0227 if (store->containsEntry(schema, identifier)) { 0228 v += store->getEntry(schema, identifier).value(); 0229 foundOnce = true; 0230 } 0231 } 0232 if (foundOnce) { 0233 dst->getEntry(schema, identifier).value() = v; 0234 } 0235 } 0236 0237 void SmartMergeStrategy::merge(Store* dst, QList<const Store*> srcs, QList<double> scores) const 0238 { 0239 dbgMetaData << "Smart merging of meta data"; 0240 Q_ASSERT(srcs.size() == scores.size()); 0241 Q_ASSERT(srcs.size() > 0); 0242 if (srcs.size() == 1) { 0243 dst->copyFrom(srcs[0]); 0244 return; 0245 } 0246 // Initialize some schema 0247 const KisMetaData::Schema* dcSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::DublinCoreSchemaUri); 0248 // const KisMetaData::Schema* psSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::PhotoshopSchemaUri); 0249 const KisMetaData::Schema* XMPRightsSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::XMPRightsSchemaUri); 0250 const KisMetaData::Schema* XMPSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::XMPSchemaUri); 0251 // Sort the stores and scores 0252 { 0253 QMultiMap<double, const Store*> scores2srcs; 0254 for (int i = 0; i < scores.size(); ++i) { 0255 scores2srcs.insert(scores[i], srcs[i]); 0256 } 0257 srcs = scores2srcs.values(); 0258 scores = scores2srcs.keys(); 0259 } 0260 0261 // First attempt to see if one of the store has a higher score than the others 0262 if (scores[0] > 2 * scores[1]) { // One of the store has a higher importance than the other ones 0263 dst->copyFrom(srcs[0]); 0264 } else { 0265 // Merge exif info 0266 0267 0268 // Election 0269 Q_FOREACH (const Store* store, srcs) { 0270 QList<QString> keys = store->keys(); 0271 Q_FOREACH (const QString & key, keys) { 0272 if (!dst->containsEntry(key)) { 0273 Value v = election(srcs, scores, key); 0274 if (v.type() != Value::Invalid) { 0275 dst->getEntry(key).value() = v; 0276 } 0277 } 0278 } 0279 } 0280 0281 // Compute rating 0282 double rating = 0.0; 0283 double norm = 0.0; 0284 for (int i = 0; i < srcs.size(); i++) { 0285 const Store* store = srcs[i]; 0286 if (store->containsEntry(XMPSchema, "Rating")) { 0287 double score = scores[i]; 0288 rating += score * store->getEntry(XMPSchema, "Rating").value().asVariant().toDouble(); 0289 norm += score; 0290 } 0291 } 0292 if (norm > 0.01) { 0293 dst->getEntry(XMPSchema, "Rating").value() = QVariant((int)(rating / norm)); 0294 } 0295 } 0296 // Merge the list of authors and keywords and other stuff 0297 mergeEntry(dst, srcs, dcSchema, "contributor"); 0298 mergeEntry(dst, srcs, dcSchema, "creator"); 0299 mergeEntry(dst, srcs, dcSchema, "publisher"); 0300 mergeEntry(dst, srcs, dcSchema, "subject"); 0301 mergeEntry(dst, srcs, XMPRightsSchema, "Owner"); 0302 mergeEntry(dst, srcs, XMPSchema, "Identifier"); 0303 }