2 Copyright (C) 2012-2015 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 "font_node.h"
41 #include "exceptions.h"
43 #include "raw_convert.h"
44 #include "dcp_assert.h"
46 #include "compose.hpp"
47 #include "encryption_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 ());
84 if (!ASDCP_FAILURE (r)) {
86 reader->ReadTimedTextResource (s, 0, 0);
88 ASDCP::WriterInfo info;
89 reader->FillWriterInfo (info);
90 _id = read_writer_info (info);
94 xml->read_file (file);
95 _id = remove_urn_uuid (xml->string_child ("Id"));
96 } catch (cxml::Error& e) {
97 boost::throw_exception (
99 String::compose ("MXF failed with %1, XML failed with %2", file, static_cast<int> (r), e.what ())
105 _load_font_nodes = type_children<dcp::SMPTELoadFontNode> (xml, "LoadFont");
107 _content_title_text = xml->string_child ("ContentTitleText");
108 _annotation_text = xml->optional_string_child ("AnnotationText");
109 _issue_date = LocalTime (xml->string_child ("IssueDate"));
110 _reel_number = xml->optional_number_child<int> ("ReelNumber");
111 _language = xml->optional_string_child ("Language");
113 /* This is supposed to be two numbers, but a single number has been seen in the wild */
114 string const er = xml->string_child ("EditRate");
115 vector<string> er_parts;
116 split (er_parts, er, is_any_of (" "));
117 if (er_parts.size() == 1) {
118 _edit_rate = Fraction (raw_convert<int> (er_parts[0]), 1);
119 } else if (er_parts.size() == 2) {
120 _edit_rate = Fraction (raw_convert<int> (er_parts[0]), raw_convert<int> (er_parts[1]));
122 throw XMLError ("malformed EditRate " + er);
125 _time_code_rate = xml->number_child<int> ("TimeCodeRate");
126 if (xml->optional_string_child ("StartTime")) {
127 _start_time = Time (xml->string_child ("StartTime"), _time_code_rate);
130 shared_ptr<cxml::Node> subtitle_list = xml->optional_node_child ("SubtitleList");
132 list<shared_ptr<dcp::FontNode> > font_nodes;
133 BOOST_FOREACH (cxml::NodePtr const & i, subtitle_list->node_children ("Font")) {
134 font_nodes.push_back (shared_ptr<FontNode> (new FontNode (i, _time_code_rate, SMPTE)));
137 list<shared_ptr<dcp::SubtitleNode> > subtitle_nodes;
138 BOOST_FOREACH (cxml::NodePtr const & i, subtitle_list->node_children ("Subtitle")) {
139 subtitle_nodes.push_back (shared_ptr<SubtitleNode> (new SubtitleNode (i, _time_code_rate, SMPTE)));
142 parse_subtitles (xml, font_nodes, subtitle_nodes);
145 ASDCP::TimedText::TimedTextDescriptor descriptor;
146 reader->FillTimedTextDescriptor (descriptor);
151 ASDCP::TimedText::ResourceList_t::const_iterator i = descriptor.ResourceList.begin();
152 i != descriptor.ResourceList.end();
155 if (i->Type == ASDCP::TimedText::MT_OPENTYPE) {
156 ASDCP::TimedText::FrameBuffer buffer;
157 buffer.Capacity (10 * 1024 * 1024);
158 reader->ReadAncillaryResource (i->ResourceID, buffer);
161 Kumu::bin2UUIDhex (i->ResourceID, ASDCP::UUIDlen, id, sizeof (id));
163 shared_array<uint8_t> data (new uint8_t[buffer.Size()]);
164 memcpy (data.get(), buffer.RoData(), buffer.Size());
166 list<shared_ptr<SMPTELoadFontNode> >::const_iterator j = _load_font_nodes.begin ();
167 while (j != _load_font_nodes.end() && (*j)->urn != id) {
171 if (j != _load_font_nodes.end ()) {
172 _fonts.push_back (Font ((*j)->id, (*j)->urn, Data (data, buffer.Size ())));
177 /* Get intrinsic duration */
178 _intrinsic_duration = descriptor.ContainerDuration;
180 /* Guess intrinsic duration */
181 _intrinsic_duration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator);
185 list<shared_ptr<LoadFontNode> >
186 SMPTESubtitleAsset::load_font_nodes () const
188 list<shared_ptr<LoadFontNode> > lf;
189 copy (_load_font_nodes.begin(), _load_font_nodes.end(), back_inserter (lf));
194 SMPTESubtitleAsset::valid_mxf (boost::filesystem::path file)
196 ASDCP::TimedText::MXFReader reader;
197 Kumu::Result_t r = reader.OpenRead (file.string().c_str ());
198 return !ASDCP_FAILURE (r);
202 SMPTESubtitleAsset::xml_as_string () const
205 xmlpp::Element* root = doc.create_root_node ("dcst:SubtitleReel");
206 root->set_namespace_declaration ("http://www.smpte-ra.org/schemas/428-7/2010/DCST", "dcst");
207 root->set_namespace_declaration ("http://www.w3.org/2001/XMLSchema", "xs");
209 root->add_child("Id", "dcst")->add_child_text ("urn:uuid:" + _id);
210 root->add_child("ContentTitleText", "dcst")->add_child_text (_content_title_text);
211 if (_annotation_text) {
212 root->add_child("AnnotationText", "dcst")->add_child_text (_annotation_text.get ());
214 root->add_child("IssueDate", "dcst")->add_child_text (_issue_date.as_string (true));
216 root->add_child("ReelNumber", "dcst")->add_child_text (raw_convert<string> (_reel_number.get ()));
219 root->add_child("Language", "dcst")->add_child_text (_language.get ());
221 root->add_child("EditRate", "dcst")->add_child_text (_edit_rate.as_string ());
222 root->add_child("TimeCodeRate", "dcst")->add_child_text (raw_convert<string> (_time_code_rate));
224 root->add_child("StartTime", "dcst")->add_child_text (_start_time.get().as_string (SMPTE));
227 BOOST_FOREACH (shared_ptr<SMPTELoadFontNode> i, _load_font_nodes) {
228 xmlpp::Element* load_font = root->add_child("LoadFont", "dcst");
229 load_font->add_child_text ("urn:uuid:" + i->urn);
230 load_font->set_attribute ("ID", i->id);
233 subtitles_as_xml (root->add_child ("SubtitleList", "dcst"), _time_code_rate, SMPTE);
235 return doc.write_to_string_formatted ("UTF-8");
238 /** Write this content to a MXF file */
240 SMPTESubtitleAsset::write (boost::filesystem::path p) const
242 EncryptionContext enc (key (), SMPTE);
244 ASDCP::WriterInfo writer_info;
245 fill_writer_info (&writer_info, _id, SMPTE);
247 ASDCP::TimedText::TimedTextDescriptor descriptor;
248 descriptor.EditRate = ASDCP::Rational (_edit_rate.numerator, _edit_rate.denominator);
249 descriptor.EncodingName = "UTF-8";
251 BOOST_FOREACH (shared_ptr<dcp::SMPTELoadFontNode> i, _load_font_nodes) {
252 list<Font>::const_iterator j = _fonts.begin ();
253 while (j != _fonts.end() && j->load_id != i->id) {
256 if (j != _fonts.end ()) {
257 ASDCP::TimedText::TimedTextResourceDescriptor res;
259 Kumu::hex2bin (i->urn.c_str(), res.ResourceID, Kumu::UUID_Length, &c);
260 DCP_ASSERT (c == Kumu::UUID_Length);
261 res.Type = ASDCP::TimedText::MT_OPENTYPE;
262 descriptor.ResourceList.push_back (res);
266 descriptor.NamespaceName = "dcst";
267 memcpy (descriptor.AssetID, writer_info.AssetUUID, ASDCP::UUIDlen);
268 descriptor.ContainerDuration = _intrinsic_duration;
270 ASDCP::TimedText::MXFWriter writer;
271 ASDCP::Result_t r = writer.OpenWrite (p.string().c_str(), writer_info, descriptor);
272 if (ASDCP_FAILURE (r)) {
273 boost::throw_exception (FileError ("could not open subtitle MXF for writing", p.string(), r));
276 /* XXX: no encryption */
277 r = writer.WriteTimedTextResource (xml_as_string (), enc.encryption(), enc.hmac());
278 if (ASDCP_FAILURE (r)) {
279 boost::throw_exception (MXFFileError ("could not write XML to timed text resource", p.string(), r));
282 BOOST_FOREACH (shared_ptr<dcp::SMPTELoadFontNode> i, _load_font_nodes) {
283 list<Font>::const_iterator j = _fonts.begin ();
284 while (j != _fonts.end() && j->load_id != i->id) {
287 if (j != _fonts.end ()) {
288 ASDCP::TimedText::FrameBuffer buffer;
289 buffer.SetData (j->data.data().get(), j->data.size());
290 buffer.Size (j->data.size());
291 r = writer.WriteAncillaryResource (buffer, enc.encryption(), enc.hmac());
292 if (ASDCP_FAILURE (r)) {
293 boost::throw_exception (MXFFileError ("could not write font to timed text resource", p.string(), r));
304 SMPTESubtitleAsset::equals (shared_ptr<const Asset> other_asset, EqualityOptions options, NoteHandler note) const
306 if (!SubtitleAsset::equals (other_asset, options, note)) {
310 shared_ptr<const SMPTESubtitleAsset> other = dynamic_pointer_cast<const SMPTESubtitleAsset> (other_asset);
312 note (DCP_ERROR, "Subtitles are in different standards");
316 list<shared_ptr<SMPTELoadFontNode> >::const_iterator i = _load_font_nodes.begin ();
317 list<shared_ptr<SMPTELoadFontNode> >::const_iterator j = other->_load_font_nodes.begin ();
319 while (i != _load_font_nodes.end ()) {
320 if (j == other->_load_font_nodes.end ()) {
321 note (DCP_ERROR, "<LoadFont> nodes differ");
325 if ((*i)->id != (*j)->id) {
326 note (DCP_ERROR, "<LoadFont> nodes differ");
334 if (_content_title_text != other->_content_title_text) {
335 note (DCP_ERROR, "Subtitle content title texts differ");
339 if (_language != other->_language) {
340 note (DCP_ERROR, "Subtitle languages differ");
344 if (_annotation_text != other->_annotation_text) {
345 note (DCP_ERROR, "Subtitle annotation texts differ");
349 if (_issue_date != other->_issue_date) {
350 if (options.issue_dates_can_differ) {
351 note (DCP_NOTE, "Subtitle issue dates differ");
353 note (DCP_ERROR, "Subtitle issue dates differ");
358 if (_reel_number != other->_reel_number) {
359 note (DCP_ERROR, "Subtitle reel numbers differ");
363 if (_edit_rate != other->_edit_rate) {
364 note (DCP_ERROR, "Subtitle edit rates differ");
368 if (_time_code_rate != other->_time_code_rate) {
369 note (DCP_ERROR, "Subtitle time code rates differ");
373 if (_start_time != other->_start_time) {
374 note (DCP_ERROR, "Subtitle start times differ");
382 SMPTESubtitleAsset::add_font (string load_id, boost::filesystem::path file)
384 string const uuid = make_uuid ();
385 _fonts.push_back (Font (load_id, uuid, file));
386 _load_font_nodes.push_back (shared_ptr<SMPTELoadFontNode> (new SMPTELoadFontNode (load_id, uuid)));
390 SMPTESubtitleAsset::add (dcp::SubtitleString s)
392 SubtitleAsset::add (s);
393 _intrinsic_duration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator);