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