X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Fsmpte_subtitle_asset.cc;h=4f611583b36c0db47ac19a3ecc03f12b19bea48d;hb=0d31c86d6dfad9f437f5613d41cace9cc5928474;hp=3b30a0c71a5be7edd1dff883c3cbfe48cb5f1976;hpb=495555716e98a02b571a5dbc3200ef4ce928272c;p=libdcp.git diff --git a/src/smpte_subtitle_asset.cc b/src/smpte_subtitle_asset.cc index 3b30a0c7..4f611583 100644 --- a/src/smpte_subtitle_asset.cc +++ b/src/smpte_subtitle_asset.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2016 Carl Hetherington + Copyright (C) 2012-2021 Carl Hetherington This file is part of libdcp. @@ -31,91 +31,156 @@ files in the program, then also delete it here. */ + /** @file src/smpte_subtitle_asset.cc - * @brief SMPTESubtitleAsset class. + * @brief SMPTESubtitleAsset class */ -#include "smpte_subtitle_asset.h" -#include "smpte_load_font_node.h" + +#include "compose.hpp" +#include "crypto_context.h" +#include "dcp_assert.h" +#include "equality_options.h" #include "exceptions.h" -#include "xml.h" +#include "filesystem.h" #include "raw_convert.h" -#include "dcp_assert.h" +#include "smpte_load_font_node.h" +#include "smpte_subtitle_asset.h" +#include "subtitle_image.h" #include "util.h" -#include "compose.hpp" -#include "encryption_context.h" -#include "decryption_context.h" +#include "warnings.h" +#include "xml.h" +LIBDCP_DISABLE_WARNINGS #include #include +#include #include -#include +LIBDCP_ENABLE_WARNINGS #include + using std::string; using std::list; using std::vector; using std::map; -using boost::shared_ptr; +using std::shared_ptr; +using std::dynamic_pointer_cast; +using std::make_shared; using boost::split; using boost::is_any_of; using boost::shared_array; -using boost::dynamic_pointer_cast; +using boost::optional; +using boost::starts_with; using namespace dcp; -SMPTESubtitleAsset::SMPTESubtitleAsset () - : _intrinsic_duration (0) + +static string const subtitle_smpte_ns_2007 = "http://www.smpte-ra.org/schemas/428-7/2007/DCST"; +static string const subtitle_smpte_ns_2010 = "http://www.smpte-ra.org/schemas/428-7/2010/DCST"; +static string const subtitle_smpte_ns_2014 = "http://www.smpte-ra.org/schemas/428-7/2014/DCST"; + + +SMPTESubtitleAsset::SMPTESubtitleAsset(SubtitleStandard standard) + : MXF(Standard::SMPTE) , _edit_rate (24, 1) , _time_code_rate (24) + , _subtitle_standard(standard) + , _xml_id (make_uuid()) { } -/** Construct a SMPTESubtitleAsset by reading an MXF or XML file. - * @param file Filename. - */ + SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file) : SubtitleAsset (file) { - shared_ptr xml (new cxml::Document ("SubtitleReel")); - - shared_ptr reader (new ASDCP::TimedText::MXFReader ()); - Kumu::Result_t r = reader->OpenRead (_file->string().c_str ()); - if (!ASDCP_FAILURE (r)) { + auto xml = make_shared("SubtitleReel"); + + Kumu::FileReaderFactory factory; + auto reader = make_shared(factory); + auto r = Kumu::RESULT_OK; + { + ASDCPErrorSuspender sus; + r = reader->OpenRead(dcp::filesystem::fix_long_path(*_file).string().c_str()); + } + if (!ASDCP_FAILURE(r)) { /* MXF-wrapped */ ASDCP::WriterInfo info; reader->FillWriterInfo (info); _id = read_writer_info (info); if (!_key_id) { /* Not encrypted; read it in now */ - string s; - reader->ReadTimedTextResource (s); - xml->read_string (s); + string xml_string; + reader->ReadTimedTextResource (xml_string); + _raw_xml = xml_string; + xml->read_string (xml_string); parse_xml (xml); - read_mxf_descriptor (reader, shared_ptr (new DecryptionContext ())); + read_mxf_descriptor (reader); + read_mxf_resources(reader, std::make_shared(optional(), Standard::SMPTE)); + } else { + read_mxf_descriptor (reader); } } else { /* Plain XML */ try { - xml.reset (new cxml::Document ("SubtitleReel")); - xml->read_file (file); + _raw_xml = dcp::file_to_string (file); + xml = make_shared("SubtitleReel"); + xml->read_file(dcp::filesystem::fix_long_path(file)); parse_xml (xml); - _id = remove_urn_uuid (xml->string_child ("Id")); } catch (cxml::Error& e) { boost::throw_exception ( - DCPReadError ( + ReadError ( String::compose ( "Failed to read subtitle file %1; MXF failed with %2, XML failed with %3", - file, static_cast (r), e.what () + file, static_cast(r), e.what() ) ) ); } + + /* Try to read PNG files from the same folder that the XML is in; the wisdom of this is + debatable, at best... + */ + for (auto i: _subtitles) { + auto im = dynamic_pointer_cast(i); + if (im && im->png_image().size() == 0) { + /* Even more dubious; allow .png or urn:uuid:.png */ + auto p = file.parent_path() / String::compose("%1.png", im->id()); + if (filesystem::is_regular_file(p)) { + im->read_png_file (p); + } else if (starts_with (im->id(), "urn:uuid:")) { + p = file.parent_path() / String::compose("%1.png", remove_urn_uuid(im->id())); + if (filesystem::is_regular_file(p)) { + im->read_png_file (p); + } + } + } + } + _standard = Standard::SMPTE; + } + + /* Check that all required image data have been found */ + for (auto i: _subtitles) { + auto im = dynamic_pointer_cast(i); + if (im && im->png_image().size() == 0) { + throw MissingSubtitleImageError (im->id()); + } } } + void SMPTESubtitleAsset::parse_xml (shared_ptr xml) { + if (xml->namespace_uri() == subtitle_smpte_ns_2007) { + _subtitle_standard = SubtitleStandard::SMPTE_2007; + } else if (xml->namespace_uri() == subtitle_smpte_ns_2010) { + _subtitle_standard = SubtitleStandard::SMPTE_2010; + } else if (xml->namespace_uri() == subtitle_smpte_ns_2014) { + _subtitle_standard = SubtitleStandard::SMPTE_2014; + } else { + throw XMLError("Unrecognised subtitle namespace " + xml->namespace_uri()); + } + _xml_id = remove_urn_uuid(xml->string_child("Id")); _load_font_nodes = type_children (xml, "LoadFont"); _content_title_text = xml->string_child ("ContentTitleText"); @@ -125,7 +190,7 @@ SMPTESubtitleAsset::parse_xml (shared_ptr xml) _language = xml->optional_string_child ("Language"); /* This is supposed to be two numbers, but a single number has been seen in the wild */ - string const er = xml->string_child ("EditRate"); + auto const er = xml->string_child ("EditRate"); vector er_parts; split (er_parts, er, is_any_of (" ")); if (er_parts.size() == 1) { @@ -138,164 +203,221 @@ SMPTESubtitleAsset::parse_xml (shared_ptr xml) _time_code_rate = xml->number_child ("TimeCodeRate"); if (xml->optional_string_child ("StartTime")) { - _start_time = Time (xml->string_child ("StartTime"), _time_code_rate); + _start_time = Time (xml->string_child("StartTime"), _time_code_rate); } /* Now we need to drop down to xmlpp */ - list ps; - xmlpp::Node::NodeList c = xml->node()->get_children (); - for (xmlpp::Node::NodeList::const_iterator i = c.begin(); i != c.end(); ++i) { - xmlpp::Element const * e = dynamic_cast (*i); + vector ps; + for (auto i: xml->node()->get_children()) { + auto const e = dynamic_cast(i); if (e && e->get_name() == "SubtitleList") { - parse_subtitles (e, ps, _time_code_rate, SMPTE); + parse_subtitles (e, ps, _time_code_rate, Standard::SMPTE); } } /* Guess intrinsic duration */ - _intrinsic_duration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator); + _intrinsic_duration = latest_subtitle_out().as_editable_units_ceil(_edit_rate.numerator / _edit_rate.denominator); } + void -SMPTESubtitleAsset::read_mxf_descriptor (shared_ptr reader, shared_ptr dec) +SMPTESubtitleAsset::read_mxf_resources (shared_ptr reader, shared_ptr dec) { ASDCP::TimedText::TimedTextDescriptor descriptor; reader->FillTimedTextDescriptor (descriptor); - /* Load fonts */ + /* Load fonts and images */ for ( - ASDCP::TimedText::ResourceList_t::const_iterator i = descriptor.ResourceList.begin(); + auto i = descriptor.ResourceList.begin(); i != descriptor.ResourceList.end(); ++i) { - if (i->Type == ASDCP::TimedText::MT_OPENTYPE) { - ASDCP::TimedText::FrameBuffer buffer; - buffer.Capacity (10 * 1024 * 1024); - reader->ReadAncillaryResource (i->ResourceID, buffer, dec->decryption()); - - char id[64]; - Kumu::bin2UUIDhex (i->ResourceID, ASDCP::UUIDlen, id, sizeof (id)); + ASDCP::TimedText::FrameBuffer buffer; + buffer.Capacity(32 * 1024 * 1024); + auto const result = reader->ReadAncillaryResource(i->ResourceID, buffer, dec->context(), dec->hmac()); + if (ASDCP_FAILURE(result)) { + switch (i->Type) { + case ASDCP::TimedText::MT_OPENTYPE: + throw ReadError(String::compose("Could not read font from MXF file (%1)", static_cast(result))); + case ASDCP::TimedText::MT_PNG: + throw ReadError(String::compose("Could not read subtitle image from MXF file (%1)", static_cast(result))); + default: + throw ReadError(String::compose("Could not read resource from MXF file (%1)", static_cast(result))); + } + } - shared_array data (new uint8_t[buffer.Size()]); - memcpy (data.get(), buffer.RoData(), buffer.Size()); + char id[64]; + Kumu::bin2UUIDhex (i->ResourceID, ASDCP::UUIDlen, id, sizeof(id)); - list >::const_iterator j = _load_font_nodes.begin (); + switch (i->Type) { + case ASDCP::TimedText::MT_OPENTYPE: + { + auto j = _load_font_nodes.begin(); while (j != _load_font_nodes.end() && (*j)->urn != id) { ++j; } if (j != _load_font_nodes.end ()) { - _fonts.push_back (Font ((*j)->id, (*j)->urn, Data (data, buffer.Size ()))); + _fonts.push_back(Font((*j)->id, (*j)->urn, ArrayData(buffer.RoData(), buffer.Size()))); + } + break; + } + case ASDCP::TimedText::MT_PNG: + { + auto j = _subtitles.begin(); + while (j != _subtitles.end() && ((!dynamic_pointer_cast(*j)) || dynamic_pointer_cast(*j)->id() != id)) { + ++j; + } + + if (j != _subtitles.end()) { + dynamic_pointer_cast(*j)->set_png_image(ArrayData(buffer.RoData(), buffer.Size())); } + break; + } + default: + break; } } +} + + +void +SMPTESubtitleAsset::read_mxf_descriptor (shared_ptr reader) +{ + ASDCP::TimedText::TimedTextDescriptor descriptor; + reader->FillTimedTextDescriptor (descriptor); - /* Get intrinsic duration */ _intrinsic_duration = descriptor.ContainerDuration; + /* The thing which is called AssetID in the descriptor is also known as the + * ResourceID of the MXF. We store that, at present just for verification + * purposes. + */ + char id[64]; + Kumu::bin2UUIDhex (descriptor.AssetID, ASDCP::UUIDlen, id, sizeof(id)); + _resource_id = id; } + void SMPTESubtitleAsset::set_key (Key key) { + /* See if we already have a key; if we do, and we have a file, we'll already + have read that file. + */ + auto const had_key = static_cast(_key); + auto const had_key_id = static_cast(_key_id); + MXF::set_key (key); - if (!_key_id || !_file) { - /* Either we don't have any data to read, or it wasn't - encrypted, so we don't need to do anything else. + if (!had_key_id || !_file || had_key) { + /* Either we don't have any data to read, it wasn't + encrypted, or we've already read it, so we don't + need to do anything else. */ return; } /* Our data was encrypted; now we can decrypt it */ - shared_ptr reader (new ASDCP::TimedText::MXFReader ()); - Kumu::Result_t r = reader->OpenRead (_file->string().c_str ()); + Kumu::FileReaderFactory factory; + auto reader = make_shared(factory); + auto r = reader->OpenRead(dcp::filesystem::fix_long_path(*_file).string().c_str()); if (ASDCP_FAILURE (r)) { boost::throw_exception ( - DCPReadError ( + ReadError ( String::compose ("Could not read encrypted subtitle MXF (%1)", static_cast (r)) ) ); } - string s; - shared_ptr dec (new DecryptionContext (key)); - reader->ReadTimedTextResource (s, dec->decryption()); - shared_ptr xml (new cxml::Document ("SubtitleReel")); - xml->read_string (s); + auto dec = make_shared(key, Standard::SMPTE); + string xml_string; + reader->ReadTimedTextResource (xml_string, dec->context(), dec->hmac()); + _raw_xml = xml_string; + auto xml = make_shared("SubtitleReel"); + xml->read_string (xml_string); parse_xml (xml); - read_mxf_descriptor (reader, dec); + read_mxf_descriptor(reader); + read_mxf_resources (reader, dec); } -list > + +vector> SMPTESubtitleAsset::load_font_nodes () const { - list > lf; - copy (_load_font_nodes.begin(), _load_font_nodes.end(), back_inserter (lf)); + vector> lf; + copy (_load_font_nodes.begin(), _load_font_nodes.end(), back_inserter(lf)); return lf; } + bool SMPTESubtitleAsset::valid_mxf (boost::filesystem::path file) { - ASDCP::TimedText::MXFReader reader; - Kumu::Result_t r = reader.OpenRead (file.string().c_str ()); + Kumu::FileReaderFactory factory; + ASDCP::TimedText::MXFReader reader(factory); + Kumu::DefaultLogSink().UnsetFilterFlag(Kumu::LOG_ALLOW_ALL); + auto r = reader.OpenRead(dcp::filesystem::fix_long_path(file).string().c_str()); + Kumu::DefaultLogSink().SetFilterFlag(Kumu::LOG_ALLOW_ALL); return !ASDCP_FAILURE (r); } + string SMPTESubtitleAsset::xml_as_string () const { xmlpp::Document doc; - xmlpp::Element* root = doc.create_root_node ("dcst:SubtitleReel"); - root->set_namespace_declaration ("http://www.smpte-ra.org/schemas/428-7/2010/DCST", "dcst"); - root->set_namespace_declaration ("http://www.w3.org/2001/XMLSchema", "xs"); + auto root = doc.create_root_node ("SubtitleReel"); - root->add_child("Id", "dcst")->add_child_text ("urn:uuid:" + _id); - root->add_child("ContentTitleText", "dcst")->add_child_text (_content_title_text); + DCP_ASSERT (_xml_id); + cxml::add_text_child(root, "Id", "urn:uuid:" + *_xml_id); + cxml::add_text_child(root, "ContentTitleText", _content_title_text); if (_annotation_text) { - root->add_child("AnnotationText", "dcst")->add_child_text (_annotation_text.get ()); + cxml::add_text_child(root, "AnnotationText", _annotation_text.get()); } - root->add_child("IssueDate", "dcst")->add_child_text (_issue_date.as_string (true)); + cxml::add_text_child(root, "IssueDate", _issue_date.as_string(false, false)); if (_reel_number) { - root->add_child("ReelNumber", "dcst")->add_child_text (raw_convert (_reel_number.get ())); + cxml::add_text_child(root, "ReelNumber", raw_convert(_reel_number.get())); } if (_language) { - root->add_child("Language", "dcst")->add_child_text (_language.get ()); + cxml::add_text_child(root, "Language", _language.get()); } - root->add_child("EditRate", "dcst")->add_child_text (_edit_rate.as_string ()); - root->add_child("TimeCodeRate", "dcst")->add_child_text (raw_convert (_time_code_rate)); + cxml::add_text_child(root, "EditRate", _edit_rate.as_string()); + cxml::add_text_child(root, "TimeCodeRate", raw_convert(_time_code_rate)); if (_start_time) { - root->add_child("StartTime", "dcst")->add_child_text (_start_time.get().as_string (SMPTE)); + cxml::add_text_child(root, "StartTime", _start_time.get().as_string(Standard::SMPTE)); } - BOOST_FOREACH (shared_ptr i, _load_font_nodes) { - xmlpp::Element* load_font = root->add_child("LoadFont", "dcst"); + for (auto i: _load_font_nodes) { + auto load_font = cxml::add_child(root, "LoadFont"); load_font->add_child_text ("urn:uuid:" + i->urn); load_font->set_attribute ("ID", i->id); } - subtitles_as_xml (root->add_child ("SubtitleList", "dcst"), _time_code_rate, SMPTE); + subtitles_as_xml(cxml::add_child(root, "SubtitleList"), _time_code_rate, Standard::SMPTE); - return doc.write_to_string_formatted ("UTF-8"); + return format_xml(doc, std::make_pair(string{}, schema_namespace())); } -/** Write this content to a MXF file */ + void SMPTESubtitleAsset::write (boost::filesystem::path p) const { - EncryptionContext enc (key (), SMPTE); + EncryptionContext enc (key(), Standard::SMPTE); ASDCP::WriterInfo writer_info; - fill_writer_info (&writer_info, _id, SMPTE); + fill_writer_info (&writer_info, _id); ASDCP::TimedText::TimedTextDescriptor descriptor; descriptor.EditRate = ASDCP::Rational (_edit_rate.numerator, _edit_rate.denominator); descriptor.EncodingName = "UTF-8"; - BOOST_FOREACH (shared_ptr i, _load_font_nodes) { - list::const_iterator j = _fonts.begin (); + /* Font references */ + + for (auto i: _load_font_nodes) { + auto j = _fonts.begin(); while (j != _fonts.end() && j->load_id != i->id) { ++j; } @@ -309,67 +431,106 @@ SMPTESubtitleAsset::write (boost::filesystem::path p) const } } - descriptor.NamespaceName = "dcst"; - memcpy (descriptor.AssetID, writer_info.AssetUUID, ASDCP::UUIDlen); + /* Image subtitle references */ + + for (auto i: _subtitles) { + auto si = dynamic_pointer_cast(i); + if (si) { + ASDCP::TimedText::TimedTextResourceDescriptor res; + unsigned int c; + Kumu::hex2bin (si->id().c_str(), res.ResourceID, Kumu::UUID_Length, &c); + DCP_ASSERT (c == Kumu::UUID_Length); + res.Type = ASDCP::TimedText::MT_PNG; + descriptor.ResourceList.push_back (res); + } + } + + descriptor.NamespaceName = schema_namespace(); + unsigned int c; + DCP_ASSERT (_xml_id); + Kumu::hex2bin (_xml_id->c_str(), descriptor.AssetID, ASDCP::UUIDlen, &c); + DCP_ASSERT (c == Kumu::UUID_Length); descriptor.ContainerDuration = _intrinsic_duration; ASDCP::TimedText::MXFWriter writer; - ASDCP::Result_t r = writer.OpenWrite (p.string().c_str(), writer_info, descriptor); + /* This header size is a guess. Empirically it seems that each subtitle reference is 90 bytes, and we need some extra. + The default size is not enough for some feature-length PNG sub projects (see DCP-o-matic #1561). + */ + ASDCP::Result_t r = writer.OpenWrite(dcp::filesystem::fix_long_path(p).string().c_str(), writer_info, descriptor, _subtitles.size() * 90 + 16384); if (ASDCP_FAILURE (r)) { boost::throw_exception (FileError ("could not open subtitle MXF for writing", p.string(), r)); } - /* XXX: no encryption */ - r = writer.WriteTimedTextResource (xml_as_string (), enc.encryption(), enc.hmac()); + _raw_xml = xml_as_string (); + + r = writer.WriteTimedTextResource (*_raw_xml, enc.context(), enc.hmac()); if (ASDCP_FAILURE (r)) { boost::throw_exception (MXFFileError ("could not write XML to timed text resource", p.string(), r)); } - BOOST_FOREACH (shared_ptr i, _load_font_nodes) { - list::const_iterator j = _fonts.begin (); + /* Font payload */ + + for (auto i: _load_font_nodes) { + auto j = _fonts.begin(); while (j != _fonts.end() && j->load_id != i->id) { ++j; } if (j != _fonts.end ()) { ASDCP::TimedText::FrameBuffer buffer; - buffer.SetData (j->data.data().get(), j->data.size()); + ArrayData data_copy(j->data); + buffer.SetData (data_copy.data(), data_copy.size()); buffer.Size (j->data.size()); - r = writer.WriteAncillaryResource (buffer, enc.encryption(), enc.hmac()); - if (ASDCP_FAILURE (r)) { + r = writer.WriteAncillaryResource (buffer, enc.context(), enc.hmac()); + if (ASDCP_FAILURE(r)) { boost::throw_exception (MXFFileError ("could not write font to timed text resource", p.string(), r)); } } } + /* Image subtitle payload */ + + for (auto i: _subtitles) { + auto si = dynamic_pointer_cast(i); + if (si) { + ASDCP::TimedText::FrameBuffer buffer; + buffer.SetData (si->png_image().data(), si->png_image().size()); + buffer.Size (si->png_image().size()); + r = writer.WriteAncillaryResource (buffer, enc.context(), enc.hmac()); + if (ASDCP_FAILURE(r)) { + boost::throw_exception (MXFFileError ("could not write PNG data to timed text resource", p.string(), r)); + } + } + } + writer.Finalize (); _file = p; } bool -SMPTESubtitleAsset::equals (shared_ptr other_asset, EqualityOptions options, NoteHandler note) const +SMPTESubtitleAsset::equals(shared_ptr other_asset, EqualityOptions const& options, NoteHandler note) const { if (!SubtitleAsset::equals (other_asset, options, note)) { return false; } - shared_ptr other = dynamic_pointer_cast (other_asset); + auto other = dynamic_pointer_cast(other_asset); if (!other) { - note (DCP_ERROR, "Subtitles are in different standards"); + note (NoteType::ERROR, "Subtitles are in different standards"); return false; } - list >::const_iterator i = _load_font_nodes.begin (); - list >::const_iterator j = other->_load_font_nodes.begin (); + auto i = _load_font_nodes.begin(); + auto j = other->_load_font_nodes.begin(); while (i != _load_font_nodes.end ()) { if (j == other->_load_font_nodes.end ()) { - note (DCP_ERROR, " nodes differ"); + note (NoteType::ERROR, " nodes differ"); return false; } if ((*i)->id != (*j)->id) { - note (DCP_ERROR, " nodes differ"); + note (NoteType::ERROR, " nodes differ"); return false; } @@ -378,63 +539,83 @@ SMPTESubtitleAsset::equals (shared_ptr other_asset, EqualityOptions } if (_content_title_text != other->_content_title_text) { - note (DCP_ERROR, "Subtitle content title texts differ"); + note (NoteType::ERROR, "Subtitle content title texts differ"); return false; } if (_language != other->_language) { - note (DCP_ERROR, "Subtitle languages differ"); + note (NoteType::ERROR, String::compose("Subtitle languages differ (`%1' vs `%2')", _language.get_value_or("[none]"), other->_language.get_value_or("[none]"))); return false; } if (_annotation_text != other->_annotation_text) { - note (DCP_ERROR, "Subtitle annotation texts differ"); + note (NoteType::ERROR, "Subtitle annotation texts differ"); return false; } if (_issue_date != other->_issue_date) { if (options.issue_dates_can_differ) { - note (DCP_NOTE, "Subtitle issue dates differ"); + note (NoteType::NOTE, "Subtitle issue dates differ"); } else { - note (DCP_ERROR, "Subtitle issue dates differ"); + note (NoteType::ERROR, "Subtitle issue dates differ"); return false; } } if (_reel_number != other->_reel_number) { - note (DCP_ERROR, "Subtitle reel numbers differ"); + note (NoteType::ERROR, "Subtitle reel numbers differ"); return false; } if (_edit_rate != other->_edit_rate) { - note (DCP_ERROR, "Subtitle edit rates differ"); + note (NoteType::ERROR, "Subtitle edit rates differ"); return false; } if (_time_code_rate != other->_time_code_rate) { - note (DCP_ERROR, "Subtitle time code rates differ"); + note (NoteType::ERROR, "Subtitle time code rates differ"); return false; } if (_start_time != other->_start_time) { - note (DCP_ERROR, "Subtitle start times differ"); + note (NoteType::ERROR, "Subtitle start times differ"); return false; } return true; } + void -SMPTESubtitleAsset::add_font (string load_id, boost::filesystem::path file) +SMPTESubtitleAsset::add_font (string load_id, dcp::ArrayData data) { string const uuid = make_uuid (); - _fonts.push_back (Font (load_id, uuid, file)); - _load_font_nodes.push_back (shared_ptr (new SMPTELoadFontNode (load_id, uuid))); + _fonts.push_back (Font(load_id, uuid, data)); + _load_font_nodes.push_back (make_shared(load_id, uuid)); } + void -SMPTESubtitleAsset::add (dcp::SubtitleString s) +SMPTESubtitleAsset::add (shared_ptr s) { SubtitleAsset::add (s); - _intrinsic_duration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator); + _intrinsic_duration = latest_subtitle_out().as_editable_units_ceil(_edit_rate.numerator / _edit_rate.denominator); +} + + +string +SMPTESubtitleAsset::schema_namespace() const +{ + switch (_subtitle_standard) { + case SubtitleStandard::SMPTE_2007: + return subtitle_smpte_ns_2007; + case SubtitleStandard::SMPTE_2010: + return subtitle_smpte_ns_2010; + case SubtitleStandard::SMPTE_2014: + return subtitle_smpte_ns_2014; + default: + DCP_ASSERT(false); + } + + DCP_ASSERT(false); }