Merge EncryptionContext with DecryptionContext and use HMAC when decrypting.
[libdcp.git] / src / smpte_subtitle_asset.cc
1 /*
2     Copyright (C) 2012-2018 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
6     libdcp is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     libdcp is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
18
19     In addition, as a special exception, the copyright holders give
20     permission to link the code of portions of this program with the
21     OpenSSL library under certain conditions as described in each
22     individual source file, and distribute linked combinations
23     including the two.
24
25     You must obey the GNU General Public License in all respects
26     for all of the code used other than OpenSSL.  If you modify
27     file(s) with this exception, you may extend this exception to your
28     version of the file(s), but you are not obligated to do so.  If you
29     do not wish to do so, delete this exception statement from your
30     version.  If you delete this exception statement from all source
31     files in the program, then also delete it here.
32 */
33
34 /** @file  src/smpte_subtitle_asset.cc
35  *  @brief SMPTESubtitleAsset class.
36  */
37
38 #include "smpte_subtitle_asset.h"
39 #include "smpte_load_font_node.h"
40 #include "exceptions.h"
41 #include "xml.h"
42 #include "raw_convert.h"
43 #include "dcp_assert.h"
44 #include "util.h"
45 #include "compose.hpp"
46 #include "crypto_context.h"
47 #include "crypto_context.h"
48 #include <asdcp/AS_DCP.h>
49 #include <asdcp/KM_util.h>
50 #include <libxml++/libxml++.h>
51 #include <boost/foreach.hpp>
52 #include <boost/algorithm/string.hpp>
53
54 using std::string;
55 using std::list;
56 using std::vector;
57 using std::map;
58 using boost::shared_ptr;
59 using boost::split;
60 using boost::is_any_of;
61 using boost::shared_array;
62 using boost::dynamic_pointer_cast;
63 using boost::optional;
64 using namespace dcp;
65
66 SMPTESubtitleAsset::SMPTESubtitleAsset ()
67         : MXF (SMPTE)
68         , _intrinsic_duration (0)
69         , _edit_rate (24, 1)
70         , _time_code_rate (24)
71         , _xml_id (make_uuid ())
72 {
73
74 }
75
76 /** Construct a SMPTESubtitleAsset by reading an MXF or XML file.
77  *  @param file Filename.
78  */
79 SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file)
80         : SubtitleAsset (file)
81 {
82         shared_ptr<cxml::Document> xml (new cxml::Document ("SubtitleReel"));
83
84         shared_ptr<ASDCP::TimedText::MXFReader> reader (new ASDCP::TimedText::MXFReader ());
85         Kumu::Result_t r = reader->OpenRead (_file->string().c_str ());
86         if (!ASDCP_FAILURE (r)) {
87                 /* MXF-wrapped */
88                 ASDCP::WriterInfo info;
89                 reader->FillWriterInfo (info);
90                 _id = read_writer_info (info);
91                 if (!_key_id) {
92                         /* Not encrypted; read it in now */
93                         string s;
94                         reader->ReadTimedTextResource (s);
95                         xml->read_string (s);
96                         parse_xml (xml);
97                         read_mxf_descriptor (reader, shared_ptr<DecryptionContext> (new DecryptionContext (optional<Key>(), SMPTE)));
98                 }
99         } else {
100                 /* Plain XML */
101                 try {
102                         xml.reset (new cxml::Document ("SubtitleReel"));
103                         xml->read_file (file);
104                         parse_xml (xml);
105                         _id = _xml_id = remove_urn_uuid (xml->string_child ("Id"));
106                 } catch (cxml::Error& e) {
107                         boost::throw_exception (
108                                 DCPReadError (
109                                         String::compose (
110                                                 "Failed to read subtitle file %1; MXF failed with %2, XML failed with %3",
111                                                 file, static_cast<int> (r), e.what ()
112                                                 )
113                                         )
114                                 );
115                 }
116         }
117 }
118
119 void
120 SMPTESubtitleAsset::parse_xml (shared_ptr<cxml::Document> xml)
121 {
122         _xml_id = remove_urn_uuid(xml->string_child("Id"));
123         _load_font_nodes = type_children<dcp::SMPTELoadFontNode> (xml, "LoadFont");
124
125         _content_title_text = xml->string_child ("ContentTitleText");
126         _annotation_text = xml->optional_string_child ("AnnotationText");
127         _issue_date = LocalTime (xml->string_child ("IssueDate"));
128         _reel_number = xml->optional_number_child<int> ("ReelNumber");
129         _language = xml->optional_string_child ("Language");
130
131         /* This is supposed to be two numbers, but a single number has been seen in the wild */
132         string const er = xml->string_child ("EditRate");
133         vector<string> er_parts;
134         split (er_parts, er, is_any_of (" "));
135         if (er_parts.size() == 1) {
136                 _edit_rate = Fraction (raw_convert<int> (er_parts[0]), 1);
137         } else if (er_parts.size() == 2) {
138                 _edit_rate = Fraction (raw_convert<int> (er_parts[0]), raw_convert<int> (er_parts[1]));
139         } else {
140                 throw XMLError ("malformed EditRate " + er);
141         }
142
143         _time_code_rate = xml->number_child<int> ("TimeCodeRate");
144         if (xml->optional_string_child ("StartTime")) {
145                 _start_time = Time (xml->string_child ("StartTime"), _time_code_rate);
146         }
147
148         /* Now we need to drop down to xmlpp */
149
150         list<ParseState> ps;
151         xmlpp::Node::NodeList c = xml->node()->get_children ();
152         for (xmlpp::Node::NodeList::const_iterator i = c.begin(); i != c.end(); ++i) {
153                 xmlpp::Element const * e = dynamic_cast<xmlpp::Element const *> (*i);
154                 if (e && e->get_name() == "SubtitleList") {
155                         parse_subtitles (e, ps, _time_code_rate, SMPTE);
156                 }
157         }
158
159         /* Guess intrinsic duration */
160         _intrinsic_duration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator);
161 }
162
163 void
164 SMPTESubtitleAsset::read_mxf_descriptor (shared_ptr<ASDCP::TimedText::MXFReader> reader, shared_ptr<DecryptionContext> dec)
165 {
166         ASDCP::TimedText::TimedTextDescriptor descriptor;
167         reader->FillTimedTextDescriptor (descriptor);
168
169         /* Load fonts */
170
171         for (
172                 ASDCP::TimedText::ResourceList_t::const_iterator i = descriptor.ResourceList.begin();
173                 i != descriptor.ResourceList.end();
174                 ++i) {
175
176                 if (i->Type == ASDCP::TimedText::MT_OPENTYPE) {
177                         ASDCP::TimedText::FrameBuffer buffer;
178                         buffer.Capacity (10 * 1024 * 1024);
179                         reader->ReadAncillaryResource (i->ResourceID, buffer, dec->context(), dec->hmac());
180
181                         char id[64];
182                         Kumu::bin2UUIDhex (i->ResourceID, ASDCP::UUIDlen, id, sizeof (id));
183
184                         shared_array<uint8_t> data (new uint8_t[buffer.Size()]);
185                         memcpy (data.get(), buffer.RoData(), buffer.Size());
186
187                         list<shared_ptr<SMPTELoadFontNode> >::const_iterator j = _load_font_nodes.begin ();
188                         while (j != _load_font_nodes.end() && (*j)->urn != id) {
189                                 ++j;
190                         }
191
192                         if (j != _load_font_nodes.end ()) {
193                                 _fonts.push_back (Font ((*j)->id, (*j)->urn, Data (data, buffer.Size ())));
194                         }
195                 }
196         }
197
198         /* Get intrinsic duration */
199         _intrinsic_duration = descriptor.ContainerDuration;
200 }
201
202 void
203 SMPTESubtitleAsset::set_key (Key key)
204 {
205         /* See if we already have a key; if we do, and we have a file, we'll already
206            have read that file.
207         */
208         bool const had_key = static_cast<bool> (_key);
209
210         MXF::set_key (key);
211
212         if (!_key_id || !_file || had_key) {
213                 /* Either we don't have any data to read, it wasn't
214                    encrypted, or we've already read it, so we don't
215                    need to do anything else.
216                 */
217                 return;
218         }
219
220         /* Our data was encrypted; now we can decrypt it */
221
222         shared_ptr<ASDCP::TimedText::MXFReader> reader (new ASDCP::TimedText::MXFReader ());
223         Kumu::Result_t r = reader->OpenRead (_file->string().c_str ());
224         if (ASDCP_FAILURE (r)) {
225                 boost::throw_exception (
226                         DCPReadError (
227                                 String::compose ("Could not read encrypted subtitle MXF (%1)", static_cast<int> (r))
228                                 )
229                         );
230         }
231
232         string s;
233         shared_ptr<DecryptionContext> dec (new DecryptionContext (key, SMPTE));
234         reader->ReadTimedTextResource (s, dec->context(), dec->hmac());
235         shared_ptr<cxml::Document> xml (new cxml::Document ("SubtitleReel"));
236         xml->read_string (s);
237         parse_xml (xml);
238         read_mxf_descriptor (reader, dec);
239 }
240
241 list<shared_ptr<LoadFontNode> >
242 SMPTESubtitleAsset::load_font_nodes () const
243 {
244         list<shared_ptr<LoadFontNode> > lf;
245         copy (_load_font_nodes.begin(), _load_font_nodes.end(), back_inserter (lf));
246         return lf;
247 }
248
249 bool
250 SMPTESubtitleAsset::valid_mxf (boost::filesystem::path file)
251 {
252         ASDCP::TimedText::MXFReader reader;
253         Kumu::Result_t r = reader.OpenRead (file.string().c_str ());
254         return !ASDCP_FAILURE (r);
255 }
256
257 string
258 SMPTESubtitleAsset::xml_as_string () const
259 {
260         xmlpp::Document doc;
261         xmlpp::Element* root = doc.create_root_node ("dcst:SubtitleReel");
262         root->set_namespace_declaration ("http://www.smpte-ra.org/schemas/428-7/2010/DCST", "dcst");
263         root->set_namespace_declaration ("http://www.w3.org/2001/XMLSchema", "xs");
264
265         root->add_child("Id", "dcst")->add_child_text ("urn:uuid:" + _xml_id);
266         root->add_child("ContentTitleText", "dcst")->add_child_text (_content_title_text);
267         if (_annotation_text) {
268                 root->add_child("AnnotationText", "dcst")->add_child_text (_annotation_text.get ());
269         }
270         root->add_child("IssueDate", "dcst")->add_child_text (_issue_date.as_string (true));
271         if (_reel_number) {
272                 root->add_child("ReelNumber", "dcst")->add_child_text (raw_convert<string> (_reel_number.get ()));
273         }
274         if (_language) {
275                 root->add_child("Language", "dcst")->add_child_text (_language.get ());
276         }
277         root->add_child("EditRate", "dcst")->add_child_text (_edit_rate.as_string ());
278         root->add_child("TimeCodeRate", "dcst")->add_child_text (raw_convert<string> (_time_code_rate));
279         if (_start_time) {
280                 root->add_child("StartTime", "dcst")->add_child_text (_start_time.get().as_string (SMPTE));
281         }
282
283         BOOST_FOREACH (shared_ptr<SMPTELoadFontNode> i, _load_font_nodes) {
284                 xmlpp::Element* load_font = root->add_child("LoadFont", "dcst");
285                 load_font->add_child_text ("urn:uuid:" + i->urn);
286                 load_font->set_attribute ("ID", i->id);
287         }
288
289         subtitles_as_xml (root->add_child ("SubtitleList", "dcst"), _time_code_rate, SMPTE);
290
291         return doc.write_to_string ("UTF-8");
292 }
293
294 /** Write this content to a MXF file */
295 void
296 SMPTESubtitleAsset::write (boost::filesystem::path p) const
297 {
298         EncryptionContext enc (key(), SMPTE);
299
300         ASDCP::WriterInfo writer_info;
301         fill_writer_info (&writer_info, _id);
302
303         ASDCP::TimedText::TimedTextDescriptor descriptor;
304         descriptor.EditRate = ASDCP::Rational (_edit_rate.numerator, _edit_rate.denominator);
305         descriptor.EncodingName = "UTF-8";
306
307         BOOST_FOREACH (shared_ptr<dcp::SMPTELoadFontNode> i, _load_font_nodes) {
308                 list<Font>::const_iterator j = _fonts.begin ();
309                 while (j != _fonts.end() && j->load_id != i->id) {
310                         ++j;
311                 }
312                 if (j != _fonts.end ()) {
313                         ASDCP::TimedText::TimedTextResourceDescriptor res;
314                         unsigned int c;
315                         Kumu::hex2bin (i->urn.c_str(), res.ResourceID, Kumu::UUID_Length, &c);
316                         DCP_ASSERT (c == Kumu::UUID_Length);
317                         res.Type = ASDCP::TimedText::MT_OPENTYPE;
318                         descriptor.ResourceList.push_back (res);
319                 }
320         }
321
322         descriptor.NamespaceName = "dcst";
323         memcpy (descriptor.AssetID, writer_info.AssetUUID, ASDCP::UUIDlen);
324         descriptor.ContainerDuration = _intrinsic_duration;
325
326         ASDCP::TimedText::MXFWriter writer;
327         ASDCP::Result_t r = writer.OpenWrite (p.string().c_str(), writer_info, descriptor);
328         if (ASDCP_FAILURE (r)) {
329                 boost::throw_exception (FileError ("could not open subtitle MXF for writing", p.string(), r));
330         }
331
332         /* XXX: no encryption */
333         r = writer.WriteTimedTextResource (xml_as_string (), enc.context(), enc.hmac());
334         if (ASDCP_FAILURE (r)) {
335                 boost::throw_exception (MXFFileError ("could not write XML to timed text resource", p.string(), r));
336         }
337
338         BOOST_FOREACH (shared_ptr<dcp::SMPTELoadFontNode> i, _load_font_nodes) {
339                 list<Font>::const_iterator j = _fonts.begin ();
340                 while (j != _fonts.end() && j->load_id != i->id) {
341                         ++j;
342                 }
343                 if (j != _fonts.end ()) {
344                         ASDCP::TimedText::FrameBuffer buffer;
345                         buffer.SetData (j->data.data().get(), j->data.size());
346                         buffer.Size (j->data.size());
347                         r = writer.WriteAncillaryResource (buffer, enc.context(), enc.hmac());
348                         if (ASDCP_FAILURE (r)) {
349                                 boost::throw_exception (MXFFileError ("could not write font to timed text resource", p.string(), r));
350                         }
351                 }
352         }
353
354         writer.Finalize ();
355
356         _file = p;
357 }
358
359 bool
360 SMPTESubtitleAsset::equals (shared_ptr<const Asset> other_asset, EqualityOptions options, NoteHandler note) const
361 {
362         if (!SubtitleAsset::equals (other_asset, options, note)) {
363                 return false;
364         }
365
366         shared_ptr<const SMPTESubtitleAsset> other = dynamic_pointer_cast<const SMPTESubtitleAsset> (other_asset);
367         if (!other) {
368                 note (DCP_ERROR, "Subtitles are in different standards");
369                 return false;
370         }
371
372         list<shared_ptr<SMPTELoadFontNode> >::const_iterator i = _load_font_nodes.begin ();
373         list<shared_ptr<SMPTELoadFontNode> >::const_iterator j = other->_load_font_nodes.begin ();
374
375         while (i != _load_font_nodes.end ()) {
376                 if (j == other->_load_font_nodes.end ()) {
377                         note (DCP_ERROR, "<LoadFont> nodes differ");
378                         return false;
379                 }
380
381                 if ((*i)->id != (*j)->id) {
382                         note (DCP_ERROR, "<LoadFont> nodes differ");
383                         return false;
384                 }
385
386                 ++i;
387                 ++j;
388         }
389
390         if (_content_title_text != other->_content_title_text) {
391                 note (DCP_ERROR, "Subtitle content title texts differ");
392                 return false;
393         }
394
395         if (_language != other->_language) {
396                 note (DCP_ERROR, "Subtitle languages differ");
397                 return false;
398         }
399
400         if (_annotation_text != other->_annotation_text) {
401                 note (DCP_ERROR, "Subtitle annotation texts differ");
402                 return false;
403         }
404
405         if (_issue_date != other->_issue_date) {
406                 if (options.issue_dates_can_differ) {
407                         note (DCP_NOTE, "Subtitle issue dates differ");
408                 } else {
409                         note (DCP_ERROR, "Subtitle issue dates differ");
410                         return false;
411                 }
412         }
413
414         if (_reel_number != other->_reel_number) {
415                 note (DCP_ERROR, "Subtitle reel numbers differ");
416                 return false;
417         }
418
419         if (_edit_rate != other->_edit_rate) {
420                 note (DCP_ERROR, "Subtitle edit rates differ");
421                 return false;
422         }
423
424         if (_time_code_rate != other->_time_code_rate) {
425                 note (DCP_ERROR, "Subtitle time code rates differ");
426                 return false;
427         }
428
429         if (_start_time != other->_start_time) {
430                 note (DCP_ERROR, "Subtitle start times differ");
431                 return false;
432         }
433
434         return true;
435 }
436
437 void
438 SMPTESubtitleAsset::add_font (string load_id, boost::filesystem::path file)
439 {
440         string const uuid = make_uuid ();
441         _fonts.push_back (Font (load_id, uuid, file));
442         _load_font_nodes.push_back (shared_ptr<SMPTELoadFontNode> (new SMPTELoadFontNode (load_id, uuid)));
443 }
444
445 void
446 SMPTESubtitleAsset::add (dcp::SubtitleString s)
447 {
448         SubtitleAsset::add (s);
449         _intrinsic_duration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator);
450 }