• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, &sections)) {
312     return false;
313   }
314 
315   WriteSections(sections, output_jpeg_stream);
316   return true;
317 }
318 
319 }  // namespace xmpmeta
320 }  // namespace dynamic_depth
321