xref: /aosp_15_r20/external/dynamic_depth/internal/xmpmeta/xmp_writer.cc (revision a62be0856e8e1158f43b03e41bbad10f4d005fde)
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, &sections)) {
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