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