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