CaptureMoment 0.1.0
Free and open-source non-destructive photo editor
Loading...
Searching...
No Matches
file_serializer_reader.cpp
Go to the documentation of this file.
1
11
12#include <spdlog/spdlog.h>
13#include <exiv2/exiv2.hpp>
14#include <stdexcept>
15#include <variant>
16#include <magic_enum/magic_enum.hpp>
17#include <unordered_map>
18
20
22 std::unique_ptr<IXmpProvider> xmp_provider,
23 std::unique_ptr<IXmpPathStrategy> xmp_path_strategy)
24 : m_xmp_provider(std::move(xmp_provider))
25 , m_xmp_path_strategy(std::move(xmp_path_strategy))
26{
27 if (!m_xmp_provider || !m_xmp_path_strategy) {
28 spdlog::error("FileSerializerReader: Constructor received a null IXmpProvider or IXmpPathStrategy.");
29 throw std::invalid_argument("FileSerializerReader: IXmpProvider and IXmpPathStrategy cannot be null.");
30 }
31 spdlog::debug("FileSerializerReader: Constructed with IXmpProvider and IXmpPathStrategy.");
32}
33
34std::vector<Operations::OperationDescriptor> FileSerializerReader::loadFromFile(std::string_view source_image_path) const
35{
36 if (source_image_path.empty()) {
37 spdlog::error("FileSerializerReader::loadFromFile: Source image path is empty.");
38 return {}; // Return an empty vector
39 }
40 spdlog::debug("FileSerializerReader::loadFromFile: Attempting to load operations for image: {}", source_image_path);
41
42 // Ensure Exiv2 is initialized before any operations
44
45 // Step 0: Determine the XMP file path using the injected strategy
46 std::string xmp_file_path { m_xmp_path_strategy->getXmpPathForImage(source_image_path) };
47 spdlog::debug("FileSerializerReader::loadFromFile: Determined XMP file path: {}", xmp_file_path);
48
49 // Step 1: Use the XMP provider to read the packet from the determined XMP file
50 std::string xmp_packet { m_xmp_provider->readXmp(xmp_file_path) };
51 if (xmp_packet.empty()) {
52 spdlog::warn("FileSerializerReader::loadFromFile: IXmpProvider returned an empty XMP packet for file: {} (associated with image: {}). Assuming no operations to load.", xmp_file_path, source_image_path);
53 return {}; // Return an empty vector if no packet is found or in case of error
54 }
55
56 // Step 2: Parse the XMP packet string into OperationDescriptors
57 std::string source_path_from_xmp; // To check potential correspondence
58 std::vector<Operations::OperationDescriptor> operations { parseXmpPacket(xmp_packet, source_path_from_xmp) };
59
60 if (operations.empty()) {
61 spdlog::warn("FileSerializerReader::loadFromFile: Parsing XMP packet from file: {} (associated with image: {}) resulted in an empty list of operations.", xmp_file_path, source_image_path);
62 } else {
63 spdlog::info("FileSerializerReader::loadFromFile: Successfully loaded {} operations from XMP file: {} for image: {}", operations.size(), xmp_file_path, source_image_path);
64 }
65
66 return operations;
67}
68
69std::vector<Operations::OperationDescriptor> FileSerializerReader::parseXmpPacket(const std::string& xmp_packet, std::string& source_image_path_from_xmp) const
70{
71 spdlog::debug("FileSerializerReader::parseXmpPacket: Parsing XMP packet (size {}).", xmp_packet.size());
72
73 Exiv2::XmpData xmp_data;
74
75 try {
76 // Decode the XMP packet string into the XmpData container
77 if (Exiv2::XmpParser::decode(xmp_data, xmp_packet.c_str()) != 0) {
78 spdlog::error("FileSerializerReader::parseXmpPacket: Failed to decode XMP packet.");
79 return {}; // Return an empty vector in case of decoding failure
80 }
81
82 // Check if it's our format
83 std::string serialized_by { xmp_data["Xmp.cm.serializedBy"].toString() };
84 if (serialized_by != "CaptureMoment") {
85 spdlog::warn("FileSerializerReader::parseXmpPacket: XMP packet is not marked as serialized by CaptureMoment (found '{}'). Skipping.", serialized_by);
86 return {};
87 }
88
89 // Read the source image path stored in the XMP
90 source_image_path_from_xmp = xmp_data["Xmp.cm.sourceImagePath"].toString(); // Returns an empty string if the key does not exist
91 spdlog::debug("FileSerializerReader::parseXmpPacket: Found source image path in XMP: '{}'", source_image_path_from_xmp);
92
93 std::vector<Operations::OperationDescriptor> operations;
94 // Iterate through potential operations in the XMP data
95 // This assumes operations are stored in an array-like structure Xmp.cm.operation[1], Xmp.cm.operation[2], etc.
96 size_t index {1};
97 while (true) {
98 std::string index_str { std::to_string(index) };
99 std::string type_key_str { "Xmp.cm.operation[" + index_str + "].type"};
100 std::string name_key_str { "Xmp.cm.operation[" + index_str + "].name"};
101 std::string enabled_key_str { "Xmp.cm.operation[" + index_str + "].enabled"};
102
103 // Create Exiv2::XmpKey objects
104 Exiv2::XmpKey type_key(type_key_str);
105 Exiv2::XmpKey name_key(name_key_str);
106 Exiv2::XmpKey enabled_key(enabled_key_str);
107
108 // Use the XmpKey objects with findKey
109 auto type_kv { xmp_data.findKey(type_key) };
110 auto name_kv { xmp_data.findKey(name_key) };
111 auto enabled_kv { xmp_data.findKey(enabled_key) };
112
113 if (type_kv == xmp_data.end()) {
114 // No more operations found with this index
115 break;
116 }
117
118 Operations::OperationDescriptor op_desc;
119
120 // Read Type (using magic_enum)
121 std::string type_str { type_kv->toString() };
122 auto type_opt = magic_enum::enum_cast<Operations::OperationType>(type_str);
123 if (!type_opt.has_value()) {
124 spdlog::warn("FileSerializerReader::parseXmpPacket: Unknown OperationType '{}' found for operation index {}. Skipping operation.", type_str, index);
125 index++;
126 continue; // Move to the next operation
127 }
128 op_desc.type = type_opt.value();
129
130 // Read Name
131 if (name_kv != xmp_data.end()) {
132 op_desc.name = name_kv->toString();
133 } else {
134 // Generate a default name if not found ?
135 op_desc.name = type_str + " (from XMP)";
136 }
137
138 // Read Enabled flag
139 if (enabled_kv != xmp_data.end())
140 {
141 std::string enabled_str { enabled_kv->toString() };
142 // Handle various boolean representations
143 if (enabled_str == "true" || enabled_str == "True" || enabled_str == "TRUE" || enabled_str == "1") {
144 op_desc.enabled = true;
145 } else if (enabled_str == "false" || enabled_str == "False" || enabled_str == "FALSE" || enabled_str == "0") {
146 op_desc.enabled = false;
147 } else {
148 spdlog::warn("FileSerializerReader::parseXmpPacket: Invalid boolean value '{}' for enabled flag of operation index {}. Using default (true).", enabled_str, index);
149 op_desc.enabled = true;
150 }
151 } else {
152 op_desc.enabled = true; // Default value
153 }
154
155 // Read Parameters
156 // Iterate through keys that start with "Xmp.cm.operation[<index>].param."
157 std::string param_prefix { "Xmp.cm.operation[" + index_str + "].param." };
158 for (const auto& kv : xmp_data) {
159 std::string key = kv.key();
160 if (key.starts_with(param_prefix))
161 {
162 std::string param_name { key.substr(param_prefix.length()) };
163 std::string param_value_str { kv.toString() };
164
165 // Deserialize the value using the robust typed approach via OperationSerialization
166 auto parsed_value = Serializer::deserializeParameter(param_value_str);
167
168 // Store the value directly. No need to check has_value() as variant always holds a value.
169 op_desc.params[param_name] = parsed_value;
170
171 spdlog::debug("FileSerializerReader::parseXmpPacket: Parsed parameter '{}' for operation '{}'.", param_name, op_desc.name);
172 }
173 }
174
175 operations.push_back(op_desc);
176 spdlog::debug("FileSerializerReader::parseXmpPacket: Parsed operation index {}: type={}, name={}, enabled={}", index, magic_enum::enum_name(op_desc.type), op_desc.name, op_desc.enabled);
177 index++;
178 }
179
180 spdlog::debug("FileSerializerReader::parseXmpPacket: Successfully parsed {} operations from XMP packet.", operations.size());
181 return operations;
182
183 } catch (const Exiv2::Error& e) {
184 spdlog::error("FileSerializerReader::parseXmpPacket: Exiv2 error during parsing: {}", e.what());
185 return {}; // Return an empty vector in case of Exiv2 error
186 } catch (const std::exception& e) {
187 spdlog::error("FileSerializerReader::parseXmpPacket: General error during parsing: {}", e.what());
188 return {}; // Return an empty vector in case of general error
189 }
190}
191
192} // namespace CaptureMoment::Core::Serializer
static void initialize()
Initializes the Exiv2 XMP parser if not already done. This function is thread-safe.
std::vector< Operations::OperationDescriptor > loadFromFile(std::string_view source_image_path) const override
Loads a list of operations from an XMP file determined by the path strategy.
FileSerializerReader(std::unique_ptr< IXmpProvider > xmp_provider, std::unique_ptr< IXmpPathStrategy > xmp_path_strategy)
Constructs a FileSerializerReader.
Declaration of Exiv2Initializer class.
Declaration of FileSerializerReader class.
Operations::OperationValue deserializeParameter(std::string_view value_str)
Deserializes a string representation back into an OperationValue. This function attempts to infer the...
Declaration of OperationSerialization interface.
CaptureMoment — footer