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