2 Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
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.
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.
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/>.
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
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.
34 /** @file src/smpte_subtitle_asset.cc
35 * @brief SMPTESubtitleAsset class.
38 #include "smpte_subtitle_asset.h"
39 #include "smpte_load_font_node.h"
40 #include "exceptions.h"
42 #include "raw_convert.h"
43 #include "dcp_assert.h"
45 #include "compose.hpp"
46 #include "encryption_context.h"
47 #include "decryption_context.h"
48 #include <asdcp/AS_DCP.h>
49 #include <asdcp/KM_util.h>
50 #include <libxml++/libxml++.h>
51 #include <boost/foreach.hpp>
52 #include <boost/algorithm/string.hpp>
58 using boost::shared_ptr;
60 using boost::is_any_of;
61 using boost::shared_array;
62 using boost::dynamic_pointer_cast;
65 SMPTESubtitleAsset::SMPTESubtitleAsset ()
66 : _intrinsic_duration (0)
68 , _time_code_rate (24)
73 /** Construct a SMPTESubtitleAsset by reading an MXF or XML file.
74 * @param file Filename.
76 SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file)
77 : SubtitleAsset (file)
79 shared_ptr<cxml::Document> xml (new cxml::Document ("SubtitleReel"));
81 shared_ptr<ASDCP::TimedText::MXFReader> reader (new ASDCP::TimedText::MXFReader ());
82 Kumu::Result_t r = reader->OpenRead (_file->string().c_str ());
83 if (!ASDCP_FAILURE (r)) {
85 ASDCP::WriterInfo info;
86 reader->FillWriterInfo (info);
87 _id = read_writer_info (info);
89 /* Not encrypted; read it in now */
91 reader->ReadTimedTextResource (s);
94 read_mxf_descriptor (reader, shared_ptr<DecryptionContext> (new DecryptionContext ()));
99 xml.reset (new cxml::Document ("SubtitleReel"));
100 xml->read_file (file);
102 _id = remove_urn_uuid (xml->string_child ("Id"));
103 } catch (cxml::Error& e) {
104 boost::throw_exception (
106 String::compose ("MXF failed with %1, XML failed with %2", file, static_cast<int> (r), e.what ())
114 SMPTESubtitleAsset::parse_xml (shared_ptr<cxml::Document> xml)
116 _load_font_nodes = type_children<dcp::SMPTELoadFontNode> (xml, "LoadFont");
118 _content_title_text = xml->string_child ("ContentTitleText");
119 _annotation_text = xml->optional_string_child ("AnnotationText");
120 _issue_date = LocalTime (xml->string_child ("IssueDate"));
121 _reel_number = xml->optional_number_child<int> ("ReelNumber");
122 _language = xml->optional_string_child ("Language");
124 /* This is supposed to be two numbers, but a single number has been seen in the wild */
125 string const er = xml->string_child ("EditRate");
126 vector<string> er_parts;
127 split (er_parts, er, is_any_of (" "));
128 if (er_parts.size() == 1) {
129 _edit_rate = Fraction (raw_convert<int> (er_parts[0]), 1);
130 } else if (er_parts.size() == 2) {
131 _edit_rate = Fraction (raw_convert<int> (er_parts[0]), raw_convert<int> (er_parts[1]));
133 throw XMLError ("malformed EditRate " + er);
136 _time_code_rate = xml->number_child<int> ("TimeCodeRate");
137 if (xml->optional_string_child ("StartTime")) {
138 _start_time = Time (xml->string_child ("StartTime"), _time_code_rate);
141 /* Now we need to drop down to xmlpp */
144 xmlpp::Node::NodeList c = xml->node()->get_children ();
145 for (xmlpp::Node::NodeList::const_iterator i = c.begin(); i != c.end(); ++i) {
146 xmlpp::Element const * e = dynamic_cast<xmlpp::Element const *> (*i);
147 if (e && e->get_name() == "SubtitleList") {
148 parse_subtitles (e, ps, _time_code_rate, SMPTE);
152 /* Guess intrinsic duration */
153 _intrinsic_duration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator);
157 SMPTESubtitleAsset::read_mxf_descriptor (shared_ptr<ASDCP::TimedText::MXFReader> reader, shared_ptr<DecryptionContext> dec)
159 ASDCP::TimedText::TimedTextDescriptor descriptor;
160 reader->FillTimedTextDescriptor (descriptor);
165 ASDCP::TimedText::ResourceList_t::const_iterator i = descriptor.ResourceList.begin();
166 i != descriptor.ResourceList.end();
169 if (i->Type == ASDCP::TimedText::MT_OPENTYPE) {
170 ASDCP::TimedText::FrameBuffer buffer;
171 buffer.Capacity (10 * 1024 * 1024);
172 reader->ReadAncillaryResource (i->ResourceID, buffer, dec->decryption());
175 Kumu::bin2UUIDhex (i->ResourceID, ASDCP::UUIDlen, id, sizeof (id));
177 shared_array<uint8_t> data (new uint8_t[buffer.Size()]);
178 memcpy (data.get(), buffer.RoData(), buffer.Size());
180 list<shared_ptr<SMPTELoadFontNode> >::const_iterator j = _load_font_nodes.begin ();
181 while (j != _load_font_nodes.end() && (*j)->urn != id) {
185 if (j != _load_font_nodes.end ()) {
186 _fonts.push_back (Font ((*j)->id, (*j)->urn, Data (data, buffer.Size ())));
191 /* Get intrinsic duration */
192 _intrinsic_duration = descriptor.ContainerDuration;
196 SMPTESubtitleAsset::set_key (Key key)
200 if (!_key_id || !_file) {
201 /* Either we don't have any data to read, or it wasn't
202 encrypted, so we don't need to do anything else.
207 /* Our data was encrypted; now we can decrypt it */
209 shared_ptr<ASDCP::TimedText::MXFReader> reader (new ASDCP::TimedText::MXFReader ());
210 Kumu::Result_t r = reader->OpenRead (_file->string().c_str ());
211 if (ASDCP_FAILURE (r)) {
212 boost::throw_exception (
214 String::compose ("Could not read encrypted subtitle MXF (%1)", static_cast<int> (r))
220 shared_ptr<DecryptionContext> dec (new DecryptionContext (key));
221 reader->ReadTimedTextResource (s, dec->decryption());
222 shared_ptr<cxml::Document> xml (new cxml::Document ("SubtitleReel"));
223 xml->read_string (s);
225 read_mxf_descriptor (reader, dec);
228 list<shared_ptr<LoadFontNode> >
229 SMPTESubtitleAsset::load_font_nodes () const
231 list<shared_ptr<LoadFontNode> > lf;
232 copy (_load_font_nodes.begin(), _load_font_nodes.end(), back_inserter (lf));
237 SMPTESubtitleAsset::valid_mxf (boost::filesystem::path file)
239 ASDCP::TimedText::MXFReader reader;
240 Kumu::Result_t r = reader.OpenRead (file.string().c_str ());
241 return !ASDCP_FAILURE (r);
245 SMPTESubtitleAsset::xml_as_string () const
248 xmlpp::Element* root = doc.create_root_node ("dcst:SubtitleReel");
249 root->set_namespace_declaration ("http://www.smpte-ra.org/schemas/428-7/2010/DCST", "dcst");
250 root->set_namespace_declaration ("http://www.w3.org/2001/XMLSchema", "xs");
252 root->add_child("Id", "dcst")->add_child_text ("urn:uuid:" + _id);
253 root->add_child("ContentTitleText", "dcst")->add_child_text (_content_title_text);
254 if (_annotation_text) {
255 root->add_child("AnnotationText", "dcst")->add_child_text (_annotation_text.get ());
257 root->add_child("IssueDate", "dcst")->add_child_text (_issue_date.as_string (true));
259 root->add_child("ReelNumber", "dcst")->add_child_text (raw_convert<string> (_reel_number.get ()));
262 root->add_child("Language", "dcst")->add_child_text (_language.get ());
264 root->add_child("EditRate", "dcst")->add_child_text (_edit_rate.as_string ());
265 root->add_child("TimeCodeRate", "dcst")->add_child_text (raw_convert<string> (_time_code_rate));
267 root->add_child("StartTime", "dcst")->add_child_text (_start_time.get().as_string (SMPTE));
270 BOOST_FOREACH (shared_ptr<SMPTELoadFontNode> i, _load_font_nodes) {
271 xmlpp::Element* load_font = root->add_child("LoadFont", "dcst");
272 load_font->add_child_text ("urn:uuid:" + i->urn);
273 load_font->set_attribute ("ID", i->id);
276 subtitles_as_xml (root->add_child ("SubtitleList", "dcst"), _time_code_rate, SMPTE);
278 return doc.write_to_string_formatted ("UTF-8");
281 /** Write this content to a MXF file */
283 SMPTESubtitleAsset::write (boost::filesystem::path p) const
285 EncryptionContext enc (key (), SMPTE);
287 ASDCP::WriterInfo writer_info;
288 fill_writer_info (&writer_info, _id, SMPTE);
290 ASDCP::TimedText::TimedTextDescriptor descriptor;
291 descriptor.EditRate = ASDCP::Rational (_edit_rate.numerator, _edit_rate.denominator);
292 descriptor.EncodingName = "UTF-8";
294 BOOST_FOREACH (shared_ptr<dcp::SMPTELoadFontNode> i, _load_font_nodes) {
295 list<Font>::const_iterator j = _fonts.begin ();
296 while (j != _fonts.end() && j->load_id != i->id) {
299 if (j != _fonts.end ()) {
300 ASDCP::TimedText::TimedTextResourceDescriptor res;
302 Kumu::hex2bin (i->urn.c_str(), res.ResourceID, Kumu::UUID_Length, &c);
303 DCP_ASSERT (c == Kumu::UUID_Length);
304 res.Type = ASDCP::TimedText::MT_OPENTYPE;
305 descriptor.ResourceList.push_back (res);
309 descriptor.NamespaceName = "dcst";
310 memcpy (descriptor.AssetID, writer_info.AssetUUID, ASDCP::UUIDlen);
311 descriptor.ContainerDuration = _intrinsic_duration;
313 ASDCP::TimedText::MXFWriter writer;
314 ASDCP::Result_t r = writer.OpenWrite (p.string().c_str(), writer_info, descriptor);
315 if (ASDCP_FAILURE (r)) {
316 boost::throw_exception (FileError ("could not open subtitle MXF for writing", p.string(), r));
319 /* XXX: no encryption */
320 r = writer.WriteTimedTextResource (xml_as_string (), enc.encryption(), enc.hmac());
321 if (ASDCP_FAILURE (r)) {
322 boost::throw_exception (MXFFileError ("could not write XML to timed text resource", p.string(), r));
325 BOOST_FOREACH (shared_ptr<dcp::SMPTELoadFontNode> i, _load_font_nodes) {
326 list<Font>::const_iterator j = _fonts.begin ();
327 while (j != _fonts.end() && j->load_id != i->id) {
330 if (j != _fonts.end ()) {
331 ASDCP::TimedText::FrameBuffer buffer;
332 buffer.SetData (j->data.data().get(), j->data.size());
333 buffer.Size (j->data.size());
334 r = writer.WriteAncillaryResource (buffer, enc.encryption(), enc.hmac());
335 if (ASDCP_FAILURE (r)) {
336 boost::throw_exception (MXFFileError ("could not write font to timed text resource", p.string(), r));
347 SMPTESubtitleAsset::equals (shared_ptr<const Asset> other_asset, EqualityOptions options, NoteHandler note) const
349 if (!SubtitleAsset::equals (other_asset, options, note)) {
353 shared_ptr<const SMPTESubtitleAsset> other = dynamic_pointer_cast<const SMPTESubtitleAsset> (other_asset);
355 note (DCP_ERROR, "Subtitles are in different standards");
359 list<shared_ptr<SMPTELoadFontNode> >::const_iterator i = _load_font_nodes.begin ();
360 list<shared_ptr<SMPTELoadFontNode> >::const_iterator j = other->_load_font_nodes.begin ();
362 while (i != _load_font_nodes.end ()) {
363 if (j == other->_load_font_nodes.end ()) {
364 note (DCP_ERROR, "<LoadFont> nodes differ");
368 if ((*i)->id != (*j)->id) {
369 note (DCP_ERROR, "<LoadFont> nodes differ");
377 if (_content_title_text != other->_content_title_text) {
378 note (DCP_ERROR, "Subtitle content title texts differ");
382 if (_language != other->_language) {
383 note (DCP_ERROR, "Subtitle languages differ");
387 if (_annotation_text != other->_annotation_text) {
388 note (DCP_ERROR, "Subtitle annotation texts differ");
392 if (_issue_date != other->_issue_date) {
393 if (options.issue_dates_can_differ) {
394 note (DCP_NOTE, "Subtitle issue dates differ");
396 note (DCP_ERROR, "Subtitle issue dates differ");
401 if (_reel_number != other->_reel_number) {
402 note (DCP_ERROR, "Subtitle reel numbers differ");
406 if (_edit_rate != other->_edit_rate) {
407 note (DCP_ERROR, "Subtitle edit rates differ");
411 if (_time_code_rate != other->_time_code_rate) {
412 note (DCP_ERROR, "Subtitle time code rates differ");
416 if (_start_time != other->_start_time) {
417 note (DCP_ERROR, "Subtitle start times differ");
425 SMPTESubtitleAsset::add_font (string load_id, boost::filesystem::path file)
427 string const uuid = make_uuid ();
428 _fonts.push_back (Font (load_id, uuid, file));
429 _load_font_nodes.push_back (shared_ptr<SMPTELoadFontNode> (new SMPTELoadFontNode (load_id, uuid)));
433 SMPTESubtitleAsset::add (dcp::SubtitleString s)
435 SubtitleAsset::add (s);
436 _intrinsic_duration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator);