Debug.
[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 boost::shared_ptr;
60 using boost::split;
61 using boost::is_any_of;
62 using boost::shared_array;
63 using boost::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, Data (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 (Data(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                         buffer.SetData (j->data.data().get(), j->data.size());
422                         buffer.Size (j->data.size());
423                         r = writer.WriteAncillaryResource (buffer, enc.context(), enc.hmac());
424                         if (ASDCP_FAILURE (r)) {
425                                 boost::throw_exception (MXFFileError ("could not write font to timed text resource", p.string(), r));
426                         }
427                 }
428         }
429
430         /* Image subtitle payload */
431
432         BOOST_FOREACH (shared_ptr<Subtitle> i, _subtitles) {
433                 shared_ptr<SubtitleImage> si = dynamic_pointer_cast<SubtitleImage>(i);
434                 if (si) {
435                         ASDCP::TimedText::FrameBuffer buffer;
436                         buffer.SetData (si->png_image().data().get(), si->png_image().size());
437                         buffer.Size (si->png_image().size());
438                         r = writer.WriteAncillaryResource (buffer, enc.context(), enc.hmac());
439                         if (ASDCP_FAILURE(r)) {
440                                 boost::throw_exception (MXFFileError ("could not write PNG data to timed text resource", p.string(), r));
441                         }
442                 }
443         }
444
445         writer.Finalize ();
446
447         _file = p;
448 }
449
450 bool
451 SMPTESubtitleAsset::equals (shared_ptr<const Asset> other_asset, EqualityOptions options, NoteHandler note) const
452 {
453         if (!SubtitleAsset::equals (other_asset, options, note)) {
454                 return false;
455         }
456
457         shared_ptr<const SMPTESubtitleAsset> other = dynamic_pointer_cast<const SMPTESubtitleAsset> (other_asset);
458         if (!other) {
459                 note (DCP_ERROR, "Subtitles are in different standards");
460                 return false;
461         }
462
463         list<shared_ptr<SMPTELoadFontNode> >::const_iterator i = _load_font_nodes.begin ();
464         list<shared_ptr<SMPTELoadFontNode> >::const_iterator j = other->_load_font_nodes.begin ();
465
466         while (i != _load_font_nodes.end ()) {
467                 if (j == other->_load_font_nodes.end ()) {
468                         note (DCP_ERROR, "<LoadFont> nodes differ");
469                         return false;
470                 }
471
472                 if ((*i)->id != (*j)->id) {
473                         note (DCP_ERROR, "<LoadFont> nodes differ");
474                         return false;
475                 }
476
477                 ++i;
478                 ++j;
479         }
480
481         if (_content_title_text != other->_content_title_text) {
482                 note (DCP_ERROR, "Subtitle content title texts differ");
483                 return false;
484         }
485
486         if (_language != other->_language) {
487                 note (DCP_ERROR, "Subtitle languages differ");
488                 return false;
489         }
490
491         if (_annotation_text != other->_annotation_text) {
492                 note (DCP_ERROR, "Subtitle annotation texts differ");
493                 return false;
494         }
495
496         if (_issue_date != other->_issue_date) {
497                 if (options.issue_dates_can_differ) {
498                         note (DCP_NOTE, "Subtitle issue dates differ");
499                 } else {
500                         note (DCP_ERROR, "Subtitle issue dates differ");
501                         return false;
502                 }
503         }
504
505         if (_reel_number != other->_reel_number) {
506                 note (DCP_ERROR, "Subtitle reel numbers differ");
507                 return false;
508         }
509
510         if (_edit_rate != other->_edit_rate) {
511                 note (DCP_ERROR, "Subtitle edit rates differ");
512                 return false;
513         }
514
515         if (_time_code_rate != other->_time_code_rate) {
516                 note (DCP_ERROR, "Subtitle time code rates differ");
517                 return false;
518         }
519
520         if (_start_time != other->_start_time) {
521                 note (DCP_ERROR, "Subtitle start times differ");
522                 return false;
523         }
524
525         return true;
526 }
527
528 void
529 SMPTESubtitleAsset::add_font (string load_id, boost::filesystem::path file)
530 {
531         string const uuid = make_uuid ();
532         _fonts.push_back (Font (load_id, uuid, file));
533         _load_font_nodes.push_back (shared_ptr<SMPTELoadFontNode> (new SMPTELoadFontNode (load_id, uuid)));
534 }
535
536 void
537 SMPTESubtitleAsset::add (shared_ptr<Subtitle> s)
538 {
539         SubtitleAsset::add (s);
540         _intrinsic_duration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator);
541 }