1 #include "xmpmeta/xmp_writer.h"
2
3 #include <libxml/tree.h>
4 #include <libxml/xmlIO.h>
5 #include <libxml/xmlstring.h>
6
7 #include <fstream>
8 #include <sstream>
9 #include <string>
10 #include <vector>
11
12 #include "android-base/logging.h"
13 #include "xmpmeta/jpeg_io.h"
14 #include "xmpmeta/md5.h"
15 #include "xmpmeta/xml/const.h"
16 #include "xmpmeta/xml/utils.h"
17 #include "xmpmeta/xmp_const.h"
18 #include "xmpmeta/xmp_data.h"
19 #include "xmpmeta/xmp_parser.h"
20
21 using ::dynamic_depth::xmpmeta::xml::FromXmlChar;
22 using ::dynamic_depth::xmpmeta::xml::GetFirstDescriptionElement;
23 using ::dynamic_depth::xmpmeta::xml::ToXmlChar;
24 using ::dynamic_depth::xmpmeta::xml::XmlConst;
25
26 namespace dynamic_depth {
27 namespace xmpmeta {
28 namespace {
29
30 const char kXmlStartTag = '<';
31
32 const char kCEmptyString[] = "\x00";
33 const int kXmlDumpFormat = 1;
34 const int kInvalidIndex = -1;
35
36 // True if 's' starts with substring 'x'.
StartsWith(const string & s,const string & x)37 bool StartsWith(const string& s, const string& x) {
38 return s.size() >= x.size() && !s.compare(0, x.size(), x);
39 }
40 // True if 's' ends with substring 'x'.
EndsWith(const string & s,const string & x)41 bool EndsWith(const string& s, const string& x) {
42 return s.size() >= x.size() && !s.compare(s.size() - x.size(), x.size(), x);
43 }
44
45 // Creates the outer rdf:RDF node for XMP.
CreateXmpRdfNode()46 xmlNodePtr CreateXmpRdfNode() {
47 xmlNodePtr rdf_node = xmlNewNode(nullptr, ToXmlChar(XmlConst::RdfNodeName()));
48 xmlNsPtr rdf_ns = xmlNewNs(rdf_node, ToXmlChar(XmlConst::RdfNodeNs()),
49 ToXmlChar(XmlConst::RdfPrefix()));
50 xmlSetNs(rdf_node, rdf_ns);
51 return rdf_node;
52 }
53
54 // Creates the root node for XMP.
CreateXmpRootNode()55 xmlNodePtr CreateXmpRootNode() {
56 xmlNodePtr root_node = xmlNewNode(nullptr, ToXmlChar(XmpConst::NodeName()));
57 xmlNsPtr root_ns = xmlNewNs(root_node, ToXmlChar(XmpConst::Namespace()),
58 ToXmlChar(XmpConst::NamespacePrefix()));
59 xmlSetNs(root_node, root_ns);
60 xmlSetNsProp(root_node, root_ns, ToXmlChar(XmpConst::AdobePropName()),
61 ToXmlChar(XmpConst::AdobePropValue()));
62 return root_node;
63 }
64
65 // Creates a new XMP metadata section, with an x:xmpmeta element wrapping
66 // rdf:RDF and rdf:Description child elements. This is the equivalent of
67 // createXMPMeta in geo/lightfield/metadata/XmpUtils.java
CreateXmpSection()68 xmlDocPtr CreateXmpSection() {
69 xmlDocPtr xmp_meta = xmlNewDoc(ToXmlChar(XmlConst::Version()));
70
71 xmlNodePtr root_node = CreateXmpRootNode();
72 xmlNodePtr rdf_node = CreateXmpRdfNode();
73 xmlNodePtr description_node =
74 xmlNewNode(nullptr, ToXmlChar(XmlConst::RdfDescription()));
75 xmlNsPtr rdf_prefix_ns =
76 xmlNewNs(description_node, nullptr, ToXmlChar(XmlConst::RdfPrefix()));
77 xmlSetNs(description_node, rdf_prefix_ns);
78
79 // rdf:about is mandatory.
80 xmlSetNsProp(description_node, rdf_node->ns, ToXmlChar(XmlConst::RdfAbout()),
81 ToXmlChar(""));
82
83 // Align nodes into the proper hierarchy.
84 xmlAddChild(rdf_node, description_node);
85 xmlAddChild(root_node, rdf_node);
86 xmlDocSetRootElement(xmp_meta, root_node);
87
88 return xmp_meta;
89 }
90
WriteIntTo4Bytes(int integer,std::ostream * output_stream)91 void WriteIntTo4Bytes(int integer, std::ostream* output_stream) {
92 output_stream->put((integer >> 24) & 0xff);
93 output_stream->put((integer >> 16) & 0xff);
94 output_stream->put((integer >> 8) & 0xff);
95 output_stream->put(integer & 0xff);
96 }
97
98 // Serializes an XML document to a string.
SerializeMeta(const xmlDocPtr parent,string * serialized_value)99 void SerializeMeta(const xmlDocPtr parent, string* serialized_value) {
100 if (parent == nullptr || parent->children == nullptr) {
101 LOG(WARNING) << "Nothing to serialize, either XML doc is null or it has "
102 << "no elements";
103 return;
104 }
105
106 std::ostringstream serialized_stream;
107 xmlChar* xml_doc_contents;
108 int doc_size = 0;
109 xmlDocDumpFormatMemoryEnc(parent, &xml_doc_contents, &doc_size,
110 XmlConst::EncodingStr(), kXmlDumpFormat);
111 const char* xml_doc_string = FromXmlChar(xml_doc_contents);
112
113 // Find the index of the second "<" so we can discard the first element,
114 // which is <?xml version...>, so start searching after the first "<". XMP
115 // starts directly afterwards.
116 const int xmp_start_idx =
117 static_cast<int>(strchr(&xml_doc_string[2], kXmlStartTag) -
118 xml_doc_string) -
119 1;
120 serialized_stream.write(&xml_doc_string[xmp_start_idx],
121 doc_size - xmp_start_idx);
122 xmlFree(xml_doc_contents);
123 *serialized_value = serialized_stream.str();
124 }
125
126 // TODO(miraleung): Switch to different library for Android if needed.
GetGUID(const string & to_hash)127 const string GetGUID(const string& to_hash) { return MD5Hash(to_hash); }
128
129 // Creates the standard XMP section.
CreateStandardSectionXmpString(const string & buffer,string * value)130 void CreateStandardSectionXmpString(const string& buffer, string* value) {
131 std::ostringstream data_stream;
132 data_stream.write(XmpConst::Header(), strlen(XmpConst::Header()));
133 data_stream.write(kCEmptyString, 1);
134 data_stream.write(buffer.c_str(), buffer.length());
135 *value = data_stream.str();
136 }
137
138 // Creates the extended XMP section.
CreateExtendedSections(const string & buffer,std::vector<Section> * extended_sections)139 void CreateExtendedSections(const string& buffer,
140 std::vector<Section>* extended_sections) {
141 string guid = GetGUID(buffer);
142 // Increment by 1 for the null byte in the middle.
143 const int header_length =
144 static_cast<int>(strlen(XmpConst::ExtensionHeader()) + 1 + guid.length());
145 const int buffer_length = static_cast<int>(buffer.length());
146 const int overhead = header_length + XmpConst::ExtensionHeaderOffset();
147 const int num_sections =
148 buffer_length / (XmpConst::ExtendedMaxBufferSize() - overhead) + 1;
149 for (int i = 0, position = 0; i < num_sections; ++i) {
150 const int section_size =
151 std::min(static_cast<int>(buffer_length - position + overhead),
152 XmpConst::ExtendedMaxBufferSize());
153 const int bytes_from_buffer = section_size - overhead;
154
155 // Header and GUID.
156 std::ostringstream data_stream;
157 data_stream.write(XmpConst::ExtensionHeader(),
158 strlen(XmpConst::ExtensionHeader()));
159 data_stream.write(kCEmptyString, 1);
160 data_stream.write(guid.c_str(), guid.length());
161
162 // Total buffer length.
163 WriteIntTo4Bytes(buffer_length, &data_stream);
164 // Current position.
165 WriteIntTo4Bytes(position, &data_stream);
166 // Data
167 data_stream.write(&buffer[position], bytes_from_buffer);
168 position += bytes_from_buffer;
169
170 extended_sections->push_back(Section(data_stream.str()));
171 }
172 }
173
InsertStandardXMPSection(const string & buffer,std::vector<Section> * sections)174 int InsertStandardXMPSection(const string& buffer,
175 std::vector<Section>* sections) {
176 if (buffer.length() > XmpConst::MaxBufferSize()) {
177 LOG(WARNING) << "The standard XMP section (at size " << buffer.length()
178 << ") cannot have a size larger than "
179 << XmpConst::MaxBufferSize() << " bytes";
180 return kInvalidIndex;
181 }
182 string value;
183 CreateStandardSectionXmpString(buffer, &value);
184 Section xmp_section(value);
185 // If we can find the old XMP section, replace it with the new one
186 for (int index = 0; index < sections->size(); ++index) {
187 if (sections->at(index).IsMarkerApp1() &&
188 StartsWith(sections->at(index).data, XmpConst::Header())) {
189 // Replace with the new XMP data.
190 sections->at(index) = xmp_section;
191 return index;
192 }
193 }
194 // If the first section is EXIF, insert XMP data after it.
195 // Otherwise, make XMP data the first section.
196 const int position =
197 (!sections->empty() && sections->at(0).IsMarkerApp1()) ? 1 : 0;
198 sections->emplace(sections->begin() + position, xmp_section);
199 return position;
200 }
201
202 // Position is the index in the Section vector where the extended sections
203 // will be inserted.
InsertExtendedXMPSections(const string & buffer,int position,std::vector<Section> * sections)204 void InsertExtendedXMPSections(const string& buffer, int position,
205 std::vector<Section>* sections) {
206 std::vector<Section> extended_sections;
207 CreateExtendedSections(buffer, &extended_sections);
208 sections->insert(sections->begin() + position, extended_sections.begin(),
209 extended_sections.end());
210 }
211
212 // Returns true if the respective sections in xmp_data and their serialized
213 // counterparts are (correspondingly) not null and not empty.
XmpSectionsAndSerializedDataValid(const XmpData & xmp_data,const string & main_buffer,const string & extended_buffer)214 bool XmpSectionsAndSerializedDataValid(const XmpData& xmp_data,
215 const string& main_buffer,
216 const string& extended_buffer) {
217 // Standard section and its serialized counterpart cannot be null/empty.
218 // Extended section can be null XOR the extended buffer can be empty.
219 const bool extended_is_consistent =
220 ((xmp_data.ExtendedSection() == nullptr) == extended_buffer.empty());
221 const bool is_valid = (xmp_data.StandardSection() != nullptr) &&
222 !main_buffer.empty() && extended_is_consistent;
223 if (!is_valid) {
224 LOG(ERROR) << "XMP sections Xor their serialized counterparts are empty";
225 }
226 return is_valid;
227 }
228
229 // Updates a list of JPEG sections with serialized XMP data.
UpdateSections(const string & main_buffer,const string & extended_buffer,std::vector<Section> * sections)230 bool UpdateSections(const string& main_buffer, const string& extended_buffer,
231 std::vector<Section>* sections) {
232 if (main_buffer.empty()) {
233 LOG(WARNING) << "Main section was empty";
234 return false;
235 }
236
237 // Update the list of sections with the new standard XMP section.
238 const int main_index = InsertStandardXMPSection(main_buffer, sections);
239 if (main_index < 0) {
240 LOG(WARNING) << "Could not find a valid index for inserting the "
241 << "standard sections";
242 return false;
243 }
244
245 // Insert the extended section right after the main section.
246 if (!extended_buffer.empty()) {
247 InsertExtendedXMPSections(extended_buffer, main_index + 1, sections);
248 }
249 return true;
250 }
251
LinkXmpStandardAndExtendedSections(const string & extended_buffer,xmlDocPtr standard_section)252 void LinkXmpStandardAndExtendedSections(const string& extended_buffer,
253 xmlDocPtr standard_section) {
254 xmlNodePtr description_node = GetFirstDescriptionElement(standard_section);
255 xmlNsPtr xmp_note_ns_ptr =
256 xmlNewNs(description_node, ToXmlChar(XmpConst::NoteNamespace()),
257 ToXmlChar(XmpConst::HasExtensionPrefix()));
258 const string extended_id = GetGUID(extended_buffer);
259 xmlSetNsProp(description_node, xmp_note_ns_ptr,
260 ToXmlChar(XmpConst::HasExtension()),
261 ToXmlChar(extended_id.c_str()));
262 xmlUnsetProp(description_node, ToXmlChar(XmpConst::HasExtension()));
263 }
264
265 } // namespace
266
CreateXmpData(bool create_extended)267 std::unique_ptr<XmpData> CreateXmpData(bool create_extended) {
268 std::unique_ptr<XmpData> xmp_data(new XmpData());
269 *xmp_data->MutableStandardSection() = CreateXmpSection();
270 if (create_extended) {
271 *xmp_data->MutableExtendedSection() = CreateXmpSection();
272 }
273 return xmp_data;
274 }
275
WriteLeftEyeAndXmpMeta(const string & left_data,const string & filename,const XmpData & xmp_data)276 bool WriteLeftEyeAndXmpMeta(const string& left_data, const string& filename,
277 const XmpData& xmp_data) {
278 std::istringstream input_jpeg_stream(left_data);
279 std::ofstream output_jpeg_stream;
280 output_jpeg_stream.open(filename, std::ostream::out);
281 bool success =
282 WriteLeftEyeAndXmpMeta(xmp_data, &input_jpeg_stream, &output_jpeg_stream);
283 output_jpeg_stream.close();
284 return success;
285 }
286
WriteLeftEyeAndXmpMeta(const XmpData & xmp_data,std::istream * input_jpeg_stream,std::ostream * output_jpeg_stream)287 bool WriteLeftEyeAndXmpMeta(const XmpData& xmp_data,
288 std::istream* input_jpeg_stream,
289 std::ostream* output_jpeg_stream) {
290 if (input_jpeg_stream == nullptr || output_jpeg_stream == nullptr) {
291 LOG(ERROR) << "Input and output streams must both be non-null";
292 return false;
293 }
294
295 // Get a list of sections from the input stream.
296 ParseOptions parse_options;
297 std::vector<Section> sections = Parse(parse_options, input_jpeg_stream);
298
299 string extended_buffer;
300 if (xmp_data.ExtendedSection() != nullptr) {
301 SerializeMeta(xmp_data.ExtendedSection(), &extended_buffer);
302 LinkXmpStandardAndExtendedSections(extended_buffer,
303 xmp_data.StandardSection());
304 }
305 string main_buffer;
306 SerializeMeta(xmp_data.StandardSection(), &main_buffer);
307
308 // Update the input sections with the XMP data.
309 if (!XmpSectionsAndSerializedDataValid(xmp_data, main_buffer,
310 extended_buffer) ||
311 !UpdateSections(main_buffer, extended_buffer, §ions)) {
312 return false;
313 }
314
315 WriteSections(sections, output_jpeg_stream);
316 return true;
317 }
318
319 } // namespace xmpmeta
320 } // namespace dynamic_depth
321