std::shared_ptr
[libdcp.git] / src / smpte_subtitle_asset.cc
1 /*
2     Copyright (C) 2012-2019 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 "subtitle_image.h"
48 #include <asdcp/AS_DCP.h>
49 #include <asdcp/KM_util.h>
50 #include <asdcp/KM_log.h>
51 #include <libxml++/libxml++.h>
52 #include <boost/foreach.hpp>
53 #include <boost/algorithm/string.hpp>
54
55 using std::string;
56 using std::list;
57 using std::vector;
58 using std::map;
59 using std::shared_ptr;
60 using boost::split;
61 using boost::is_any_of;
62 using boost::shared_array;
63 using std::dynamic_pointer_cast;
64 using boost::optional;
65 using boost::starts_with;
66 using namespace dcp;
67
68 static string const subtitle_smpte_ns = "http://www.smpte-ra.org/schemas/428-7/2010/DCST";
69
70 SMPTESubtitleAsset::SMPTESubtitleAsset ()
71         : MXF (SMPTE)
72         , _intrinsic_duration (0)
73         , _edit_rate (24, 1)
74         , _time_code_rate (24)
75         , _xml_id (make_uuid ())
76 {
77
78 }
79
80 /** Construct a SMPTESubtitleAsset by reading an MXF or XML file.
81  *  @param file Filename.
82  */
83 SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file)
84         : SubtitleAsset (file)
85 {
86         shared_ptr<cxml::Document> xml (new cxml::Document ("SubtitleReel"));
87
88         shared_ptr<ASDCP::TimedText::MXFReader> reader (new ASDCP::TimedText::MXFReader ());
89         Kumu::Result_t r = Kumu::RESULT_OK;
90         {
91                 ASDCPErrorSuspender sus;
92                 r = reader->OpenRead (_file->string().c_str ());
93         }
94         if (!ASDCP_FAILURE (r)) {
95                 /* MXF-wrapped */
96                 ASDCP::WriterInfo info;
97                 reader->FillWriterInfo (info);
98                 _id = read_writer_info (info);
99                 if (!_key_id) {
100                         /* Not encrypted; read it in now */
101                         reader->ReadTimedTextResource (_raw_xml);
102                         xml->read_string (_raw_xml);
103                         parse_xml (xml);
104                         read_mxf_descriptor (reader, shared_ptr<DecryptionContext> (new DecryptionContext (optional<Key>(), SMPTE)));
105                 }
106         } else {
107                 /* Plain XML */
108                 try {
109                         _raw_xml = dcp::file_to_string (file);
110                         xml.reset (new cxml::Document ("SubtitleReel"));
111                         xml->read_file (file);
112                         parse_xml (xml);
113                         _id = _xml_id = remove_urn_uuid (xml->string_child ("Id"));
114                 } catch (cxml::Error& e) {
115                         boost::throw_exception (
116                                 ReadError (
117                                         String::compose (
118                                                 "Failed to read subtitle file %1; MXF failed with %2, XML failed with %3",
119                                                 file, static_cast<int> (r), e.what ()
120                                                 )
121                                         )
122                                 );
123                 }
124
125                 /* Try to read PNG files from the same folder that the XML is in; the wisdom of this is
126                    debatable, at best...
127                 */
128                 BOOST_FOREACH (shared_ptr<Subtitle> i, _subtitles) {
129                         shared_ptr<SubtitleImage> im = dynamic_pointer_cast<SubtitleImage>(i);
130                         if (im && im->png_image().size() == 0) {
131                                 /* Even more dubious; allow <id>.png or urn:uuid:<id>.png */
132                                 boost::filesystem::path p = file.parent_path() / String::compose("%1.png", im->id());
133                                 if (boost::filesystem::is_regular_file(p)) {
134                                         im->read_png_file (p);
135                                 } else if (starts_with (im->id(), "urn:uuid:")) {
136                                         p = file.parent_path() / String::compose("%1.png", remove_urn_uuid(im->id()));
137                                         if (boost::filesystem::is_regular_file(p)) {
138                                                 im->read_png_file (p);
139                                         }
140                                 }
141                         }
142                 }
143         }
144
145         /* Check that all required image data have been found */
146         BOOST_FOREACH (shared_ptr<Subtitle> i, _subtitles) {
147                 shared_ptr<SubtitleImage> im = dynamic_pointer_cast<SubtitleImage>(i);
148                 if (im && im->png_image().size() == 0) {
149                         throw MissingSubtitleImageError (im->id());
150                 }
151         }
152 }
153
154 void
155 SMPTESubtitleAsset::parse_xml (shared_ptr<cxml::Document> xml)
156 {
157         _xml_id = remove_urn_uuid(xml->string_child("Id"));
158         _load_font_nodes = type_children<dcp::SMPTELoadFontNode> (xml, "LoadFont");
159
160         _content_title_text = xml->string_child ("ContentTitleText");
161         _annotation_text = xml->optional_string_child ("AnnotationText");
162         _issue_date = LocalTime (xml->string_child ("IssueDate"));
163         _reel_number = xml->optional_number_child<int> ("ReelNumber");
164         _language = xml->optional_string_child ("Language");
165
166         /* This is supposed to be two numbers, but a single number has been seen in the wild */
167         string const er = xml->string_child ("EditRate");
168         vector<string> er_parts;
169         split (er_parts, er, is_any_of (" "));
170         if (er_parts.size() == 1) {
171                 _edit_rate = Fraction (raw_convert<int> (er_parts[0]), 1);
172         } else if (er_parts.size() == 2) {
173                 _edit_rate = Fraction (raw_convert<int> (er_parts[0]), raw_convert<int> (er_parts[1]));
174         } else {
175                 throw XMLError ("malformed EditRate " + er);
176         }
177
178         _time_code_rate = xml->number_child<int> ("TimeCodeRate");
179         if (xml->optional_string_child ("StartTime")) {
180                 _start_time = Time (xml->string_child ("StartTime"), _time_code_rate);
181         }
182
183         /* Now we need to drop down to xmlpp */
184
185         list<ParseState> ps;
186         xmlpp::Node::NodeList c = xml->node()->get_children ();
187         for (xmlpp::Node::NodeList::const_iterator i = c.begin(); i != c.end(); ++i) {
188                 xmlpp::Element const * e = dynamic_cast<xmlpp::Element const *> (*i);
189                 if (e && e->get_name() == "SubtitleList") {
190                         parse_subtitles (e, ps, _time_code_rate, SMPTE);
191                 }
192         }
193
194         /* Guess intrinsic duration */
195         _intrinsic_duration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator);
196 }
197
198 void
199 SMPTESubtitleAsset::read_mxf_descriptor (shared_ptr<ASDCP::TimedText::MXFReader> reader, shared_ptr<DecryptionContext> dec)
200 {
201         ASDCP::TimedText::TimedTextDescriptor descriptor;
202         reader->FillTimedTextDescriptor (descriptor);
203
204         /* Load fonts and images */
205
206         for (
207                 ASDCP::TimedText::ResourceList_t::const_iterator i = descriptor.ResourceList.begin();
208                 i != descriptor.ResourceList.end();
209                 ++i) {
210
211                 ASDCP::TimedText::FrameBuffer buffer;
212                 buffer.Capacity (10 * 1024 * 1024);
213                 reader->ReadAncillaryResource (i->ResourceID, buffer, dec->context(), dec->hmac());
214
215                 char id[64];
216                 Kumu::bin2UUIDhex (i->ResourceID, ASDCP::UUIDlen, id, sizeof (id));
217
218                 shared_array<uint8_t> data (new uint8_t[buffer.Size()]);
219                 memcpy (data.get(), buffer.RoData(), buffer.Size());
220
221                 switch (i->Type) {
222                 case ASDCP::TimedText::MT_OPENTYPE:
223                 {
224                         list<shared_ptr<SMPTELoadFontNode> >::const_iterator j = _load_font_nodes.begin ();
225                         while (j != _load_font_nodes.end() && (*j)->urn != id) {
226                                 ++j;
227                         }
228
229                         if (j != _load_font_nodes.end ()) {
230                                 _fonts.push_back (Font ((*j)->id, (*j)->urn, ArrayData (data, buffer.Size ())));
231                         }
232                         break;
233                 }
234                 case ASDCP::TimedText::MT_PNG:
235                 {
236                         list<shared_ptr<Subtitle> >::const_iterator j = _subtitles.begin ();
237                         while (j != _subtitles.end() && ((!dynamic_pointer_cast<SubtitleImage>(*j)) || dynamic_pointer_cast<SubtitleImage>(*j)->id() != id)) {
238                                 ++j;
239                         }
240
241                         if (j != _subtitles.end()) {
242                                 dynamic_pointer_cast<SubtitleImage>(*j)->set_png_image (ArrayData(data, buffer.Size()));
243                         }
244                         break;
245                 }
246                 default:
247                         break;
248                 }
249         }
250
251         /* Get intrinsic duration */
252         _intrinsic_duration = descriptor.ContainerDuration;
253 }
254
255 void
256 SMPTESubtitleAsset::set_key (Key key)
257 {
258         /* See if we already have a key; if we do, and we have a file, we'll already
259            have read that file.
260         */
261         bool const had_key = static_cast<bool> (_key);
262
263         MXF::set_key (key);
264
265         if (!_key_id || !_file || had_key) {
266                 /* Either we don't have any data to read, it wasn't
267                    encrypted, or we've already read it, so we don't
268                    need to do anything else.
269                 */
270                 return;
271         }
272
273         /* Our data was encrypted; now we can decrypt it */
274
275         shared_ptr<ASDCP::TimedText::MXFReader> reader (new ASDCP::TimedText::MXFReader ());
276         Kumu::Result_t r = reader->OpenRead (_file->string().c_str ());
277         if (ASDCP_FAILURE (r)) {
278                 boost::throw_exception (
279                         ReadError (
280                                 String::compose ("Could not read encrypted subtitle MXF (%1)", static_cast<int> (r))
281                                 )
282                         );
283         }
284
285         shared_ptr<DecryptionContext> dec (new DecryptionContext (key, SMPTE));
286         reader->ReadTimedTextResource (_raw_xml, dec->context(), dec->hmac());
287         shared_ptr<cxml::Document> xml (new cxml::Document ("SubtitleReel"));
288         xml->read_string (_raw_xml);
289         parse_xml (xml);
290         read_mxf_descriptor (reader, dec);
291 }
292
293 list<shared_ptr<LoadFontNode> >
294 SMPTESubtitleAsset::load_font_nodes () const
295 {
296         list<shared_ptr<LoadFontNode> > lf;
297         copy (_load_font_nodes.begin(), _load_font_nodes.end(), back_inserter (lf));
298         return lf;
299 }
300
301 bool
302 SMPTESubtitleAsset::valid_mxf (boost::filesystem::path file)
303 {
304         ASDCP::TimedText::MXFReader reader;
305         Kumu::DefaultLogSink().UnsetFilterFlag(Kumu::LOG_ALLOW_ALL);
306         Kumu::Result_t r = reader.OpenRead (file.string().c_str ());
307         Kumu::DefaultLogSink().SetFilterFlag(Kumu::LOG_ALLOW_ALL);
308         return !ASDCP_FAILURE (r);
309 }
310
311 string
312 SMPTESubtitleAsset::xml_as_string () const
313 {
314         xmlpp::Document doc;
315         xmlpp::Element* root = doc.create_root_node ("dcst:SubtitleReel");
316         root->set_namespace_declaration (subtitle_smpte_ns, "dcst");
317         root->set_namespace_declaration ("http://www.w3.org/2001/XMLSchema", "xs");
318
319         root->add_child("Id", "dcst")->add_child_text ("urn:uuid:" + _xml_id);
320         root->add_child("ContentTitleText", "dcst")->add_child_text (_content_title_text);
321         if (_annotation_text) {
322                 root->add_child("AnnotationText", "dcst")->add_child_text (_annotation_text.get ());
323         }
324         root->add_child("IssueDate", "dcst")->add_child_text (_issue_date.as_string (true));
325         if (_reel_number) {
326                 root->add_child("ReelNumber", "dcst")->add_child_text (raw_convert<string> (_reel_number.get ()));
327         }
328         if (_language) {
329                 root->add_child("Language", "dcst")->add_child_text (_language.get ());
330         }
331         root->add_child("EditRate", "dcst")->add_child_text (_edit_rate.as_string ());
332         root->add_child("TimeCodeRate", "dcst")->add_child_text (raw_convert<string> (_time_code_rate));
333         if (_start_time) {
334                 root->add_child("StartTime", "dcst")->add_child_text (_start_time.get().as_string (SMPTE));
335         }
336
337         BOOST_FOREACH (shared_ptr<SMPTELoadFontNode> i, _load_font_nodes) {
338                 xmlpp::Element* load_font = root->add_child("LoadFont", "dcst");
339                 load_font->add_child_text ("urn:uuid:" + i->urn);
340                 load_font->set_attribute ("ID", i->id);
341         }
342
343         subtitles_as_xml (root->add_child ("SubtitleList", "dcst"), _time_code_rate, SMPTE);
344
345         return doc.write_to_string ("UTF-8");
346 }
347
348 /** Write this content to a MXF file */
349 void
350 SMPTESubtitleAsset::write (boost::filesystem::path p) const
351 {
352         EncryptionContext enc (key(), SMPTE);
353
354         ASDCP::WriterInfo writer_info;
355         fill_writer_info (&writer_info, _id);
356
357         ASDCP::TimedText::TimedTextDescriptor descriptor;
358         descriptor.EditRate = ASDCP::Rational (_edit_rate.numerator, _edit_rate.denominator);
359         descriptor.EncodingName = "UTF-8";
360
361         /* Font references */
362
363         BOOST_FOREACH (shared_ptr<dcp::SMPTELoadFontNode> i, _load_font_nodes) {
364                 list<Font>::const_iterator j = _fonts.begin ();
365                 while (j != _fonts.end() && j->load_id != i->id) {
366                         ++j;
367                 }
368                 if (j != _fonts.end ()) {
369                         ASDCP::TimedText::TimedTextResourceDescriptor res;
370                         unsigned int c;
371                         Kumu::hex2bin (i->urn.c_str(), res.ResourceID, Kumu::UUID_Length, &c);
372                         DCP_ASSERT (c == Kumu::UUID_Length);
373                         res.Type = ASDCP::TimedText::MT_OPENTYPE;
374                         descriptor.ResourceList.push_back (res);
375                 }
376         }
377
378         /* Image subtitle references */
379
380         BOOST_FOREACH (shared_ptr<Subtitle> i, _subtitles) {
381                 shared_ptr<SubtitleImage> si = dynamic_pointer_cast<SubtitleImage>(i);
382                 if (si) {
383                         ASDCP::TimedText::TimedTextResourceDescriptor res;
384                         unsigned int c;
385                         Kumu::hex2bin (si->id().c_str(), res.ResourceID, Kumu::UUID_Length, &c);
386                         DCP_ASSERT (c == Kumu::UUID_Length);
387                         res.Type = ASDCP::TimedText::MT_PNG;
388                         descriptor.ResourceList.push_back (res);
389                 }
390         }
391
392         descriptor.NamespaceName = subtitle_smpte_ns;
393         unsigned int c;
394         Kumu::hex2bin (_xml_id.c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c);
395         DCP_ASSERT (c == Kumu::UUID_Length);
396         descriptor.ContainerDuration = _intrinsic_duration;
397
398         ASDCP::TimedText::MXFWriter writer;
399         /* This header size is a guess.  Empirically it seems that each subtitle reference is 90 bytes, and we need some extra.
400            The default size is not enough for some feature-length PNG sub projects (see DCP-o-matic #1561).
401         */
402         ASDCP::Result_t r = writer.OpenWrite (p.string().c_str(), writer_info, descriptor, _subtitles.size() * 90 + 16384);
403         if (ASDCP_FAILURE (r)) {
404                 boost::throw_exception (FileError ("could not open subtitle MXF for writing", p.string(), r));
405         }
406
407         r = writer.WriteTimedTextResource (xml_as_string (), enc.context(), enc.hmac());
408         if (ASDCP_FAILURE (r)) {
409                 boost::throw_exception (MXFFileError ("could not write XML to timed text resource", p.string(), r));
410         }
411
412         /* Font payload */
413
414         BOOST_FOREACH (shared_ptr<dcp::SMPTELoadFontNode> i, _load_font_nodes) {
415                 list<Font>::const_iterator j = _fonts.begin ();
416                 while (j != _fonts.end() && j->load_id != i->id) {
417                         ++j;
418                 }
419                 if (j != _fonts.end ()) {
420                         ASDCP::TimedText::FrameBuffer buffer;
421                         ArrayData data_copy(j->data);
422                         buffer.SetData (data_copy.data(), data_copy.size());
423                         buffer.Size (j->data.size());
424                         r = writer.WriteAncillaryResource (buffer, enc.context(), enc.hmac());
425                         if (ASDCP_FAILURE (r)) {
426                                 boost::throw_exception (MXFFileError ("could not write font to timed text resource", p.string(), r));
427                         }
428                 }
429         }
430
431         /* Image subtitle payload */
432
433         BOOST_FOREACH (shared_ptr<Subtitle> i, _subtitles) {
434                 shared_ptr<SubtitleImage> si = dynamic_pointer_cast<SubtitleImage>(i);
435                 if (si) {
436                         ASDCP::TimedText::FrameBuffer buffer;
437                         buffer.SetData (si->png_image().data(), si->png_image().size());
438                         buffer.Size (si->png_image().size());
439                         r = writer.WriteAncillaryResource (buffer, enc.context(), enc.hmac());
440                         if (ASDCP_FAILURE(r)) {
441                                 boost::throw_exception (MXFFileError ("could not write PNG data to timed text resource", p.string(), r));
442                         }
443                 }
444         }
445
446         writer.Finalize ();
447
448         _file = p;
449 }
450
451 bool
452 SMPTESubtitleAsset::equals (shared_ptr<const Asset> other_asset, EqualityOptions options, NoteHandler note) const
453 {
454         if (!SubtitleAsset::equals (other_asset, options, note)) {
455                 return false;
456         }
457
458         shared_ptr<const SMPTESubtitleAsset> other = dynamic_pointer_cast<const SMPTESubtitleAsset> (other_asset);
459         if (!other) {
460                 note (DCP_ERROR, "Subtitles are in different standards");
461                 return false;
462         }
463
464         list<shared_ptr<SMPTELoadFontNode> >::const_iterator i = _load_font_nodes.begin ();
465         list<shared_ptr<SMPTELoadFontNode> >::const_iterator j = other->_load_font_nodes.begin ();
466
467         while (i != _load_font_nodes.end ()) {
468                 if (j == other->_load_font_nodes.end ()) {
469                         note (DCP_ERROR, "<LoadFont> nodes differ");
470                         return false;
471                 }
472
473                 if ((*i)->id != (*j)->id) {
474                         note (DCP_ERROR, "<LoadFont> nodes differ");
475                         return false;
476                 }
477
478                 ++i;
479                 ++j;
480         }
481
482         if (_content_title_text != other->_content_title_text) {
483                 note (DCP_ERROR, "Subtitle content title texts differ");
484                 return false;
485         }
486
487         if (_language != other->_language) {
488                 note (DCP_ERROR, String::compose("Subtitle languages differ (`%1' vs `%2')", _language.get_value_or("[none]"), other->_language.get_value_or("[none]")));
489                 return false;
490         }
491
492         if (_annotation_text != other->_annotation_text) {
493                 note (DCP_ERROR, "Subtitle annotation texts differ");
494                 return false;
495         }
496
497         if (_issue_date != other->_issue_date) {
498                 if (options.issue_dates_can_differ) {
499                         note (DCP_NOTE, "Subtitle issue dates differ");
500                 } else {
501                         note (DCP_ERROR, "Subtitle issue dates differ");
502                         return false;
503                 }
504         }
505
506         if (_reel_number != other->_reel_number) {
507                 note (DCP_ERROR, "Subtitle reel numbers differ");
508                 return false;
509         }
510
511         if (_edit_rate != other->_edit_rate) {
512                 note (DCP_ERROR, "Subtitle edit rates differ");
513                 return false;
514         }
515
516         if (_time_code_rate != other->_time_code_rate) {
517                 note (DCP_ERROR, "Subtitle time code rates differ");
518                 return false;
519         }
520
521         if (_start_time != other->_start_time) {
522                 note (DCP_ERROR, "Subtitle start times differ");
523                 return false;
524         }
525
526         return true;
527 }
528
529 void
530 SMPTESubtitleAsset::add_font (string load_id, dcp::ArrayData data)
531 {
532         string const uuid = make_uuid ();
533         _fonts.push_back (Font(load_id, uuid, data));
534         _load_font_nodes.push_back (shared_ptr<SMPTELoadFontNode> (new SMPTELoadFontNode (load_id, uuid)));
535 }
536
537 void
538 SMPTESubtitleAsset::add (shared_ptr<Subtitle> s)
539 {
540         SubtitleAsset::add (s);
541         _intrinsic_duration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator);
542 }