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 <asdcp/AS_DCP.h>
48 #include <asdcp/KM_util.h>
49 #include <libxml++/libxml++.h>
50 #include <boost/foreach.hpp>
51 #include <boost/algorithm/string.hpp>
57 using boost::shared_ptr;
59 using boost::is_any_of;
60 using boost::shared_array;
61 using boost::dynamic_pointer_cast;
64 SMPTESubtitleAsset::SMPTESubtitleAsset ()
65 : _intrinsic_duration (0)
67 , _time_code_rate (24)
72 /** Construct a SMPTESubtitleAsset by reading an MXF or XML file.
73 * @param file Filename.
75 SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file)
76 : SubtitleAsset (file)
78 shared_ptr<cxml::Document> xml (new cxml::Document ("SubtitleReel"));
80 shared_ptr<ASDCP::TimedText::MXFReader> reader (new ASDCP::TimedText::MXFReader ());
81 Kumu::Result_t r = reader->OpenRead (file.string().c_str ());
83 if (!ASDCP_FAILURE (r)) {
85 reader->ReadTimedTextResource (s, 0, 0);
87 ASDCP::WriterInfo info;
88 reader->FillWriterInfo (info);
89 _id = read_writer_info (info);
93 xml->read_file (file);
94 _id = remove_urn_uuid (xml->string_child ("Id"));
95 } catch (cxml::Error& e) {
96 boost::throw_exception (
98 String::compose ("MXF failed with %1, XML failed with %2", file, static_cast<int> (r), e.what ())
104 _load_font_nodes = type_children<dcp::SMPTELoadFontNode> (xml, "LoadFont");
106 _content_title_text = xml->string_child ("ContentTitleText");
107 _annotation_text = xml->optional_string_child ("AnnotationText");
108 _issue_date = LocalTime (xml->string_child ("IssueDate"));
109 _reel_number = xml->optional_number_child<int> ("ReelNumber");
110 _language = xml->optional_string_child ("Language");
112 /* This is supposed to be two numbers, but a single number has been seen in the wild */
113 string const er = xml->string_child ("EditRate");
114 vector<string> er_parts;
115 split (er_parts, er, is_any_of (" "));
116 if (er_parts.size() == 1) {
117 _edit_rate = Fraction (raw_convert<int> (er_parts[0]), 1);
118 } else if (er_parts.size() == 2) {
119 _edit_rate = Fraction (raw_convert<int> (er_parts[0]), raw_convert<int> (er_parts[1]));
121 throw XMLError ("malformed EditRate " + er);
124 _time_code_rate = xml->number_child<int> ("TimeCodeRate");
125 if (xml->optional_string_child ("StartTime")) {
126 _start_time = Time (xml->string_child ("StartTime"), _time_code_rate);
129 shared_ptr<cxml::Node> subtitle_list = xml->optional_node_child ("SubtitleList");
131 list<shared_ptr<dcp::FontNode> > font_nodes;
132 BOOST_FOREACH (cxml::NodePtr const & i, subtitle_list->node_children ("Font")) {
133 font_nodes.push_back (shared_ptr<FontNode> (new FontNode (i, _time_code_rate, SMPTE)));
136 list<shared_ptr<dcp::SubtitleNode> > subtitle_nodes;
137 BOOST_FOREACH (cxml::NodePtr const & i, subtitle_list->node_children ("Subtitle")) {
138 subtitle_nodes.push_back (shared_ptr<SubtitleNode> (new SubtitleNode (i, _time_code_rate, SMPTE)));
141 parse_subtitles (xml, font_nodes, subtitle_nodes);
144 ASDCP::TimedText::TimedTextDescriptor descriptor;
145 reader->FillTimedTextDescriptor (descriptor);
150 ASDCP::TimedText::ResourceList_t::const_iterator i = descriptor.ResourceList.begin();
151 i != descriptor.ResourceList.end();
154 if (i->Type == ASDCP::TimedText::MT_OPENTYPE) {
155 ASDCP::TimedText::FrameBuffer buffer;
156 buffer.Capacity (10 * 1024 * 1024);
157 reader->ReadAncillaryResource (i->ResourceID, buffer);
160 Kumu::bin2UUIDhex (i->ResourceID, ASDCP::UUIDlen, id, sizeof (id));
162 shared_array<uint8_t> data (new uint8_t[buffer.Size()]);
163 memcpy (data.get(), buffer.RoData(), buffer.Size());
165 list<shared_ptr<SMPTELoadFontNode> >::const_iterator j = _load_font_nodes.begin ();
166 while (j != _load_font_nodes.end() && (*j)->urn != id) {
170 if (j != _load_font_nodes.end ()) {
171 _fonts.push_back (Font ((*j)->id, (*j)->urn, Data (data, buffer.Size ())));
176 /* Get intrinsic duration */
177 _intrinsic_duration = descriptor.ContainerDuration;
179 /* Guess intrinsic duration */
180 _intrinsic_duration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator);
184 list<shared_ptr<LoadFontNode> >
185 SMPTESubtitleAsset::load_font_nodes () const
187 list<shared_ptr<LoadFontNode> > lf;
188 copy (_load_font_nodes.begin(), _load_font_nodes.end(), back_inserter (lf));
193 SMPTESubtitleAsset::valid_mxf (boost::filesystem::path file)
195 ASDCP::TimedText::MXFReader reader;
196 Kumu::Result_t r = reader.OpenRead (file.string().c_str ());
197 return !ASDCP_FAILURE (r);
201 SMPTESubtitleAsset::xml_as_string () const
204 xmlpp::Element* root = doc.create_root_node ("dcst:SubtitleReel");
205 root->set_namespace_declaration ("http://www.smpte-ra.org/schemas/428-7/2010/DCST", "dcst");
206 root->set_namespace_declaration ("http://www.w3.org/2001/XMLSchema", "xs");
208 root->add_child("Id", "dcst")->add_child_text ("urn:uuid:" + _id);
209 root->add_child("ContentTitleText", "dcst")->add_child_text (_content_title_text);
210 if (_annotation_text) {
211 root->add_child("AnnotationText", "dcst")->add_child_text (_annotation_text.get ());
213 root->add_child("IssueDate", "dcst")->add_child_text (_issue_date.as_string (true));
215 root->add_child("ReelNumber", "dcst")->add_child_text (raw_convert<string> (_reel_number.get ()));
218 root->add_child("Language", "dcst")->add_child_text (_language.get ());
220 root->add_child("EditRate", "dcst")->add_child_text (_edit_rate.as_string ());
221 root->add_child("TimeCodeRate", "dcst")->add_child_text (raw_convert<string> (_time_code_rate));
223 root->add_child("StartTime", "dcst")->add_child_text (_start_time.get().as_string (SMPTE));
226 BOOST_FOREACH (shared_ptr<SMPTELoadFontNode> i, _load_font_nodes) {
227 xmlpp::Element* load_font = root->add_child("LoadFont", "dcst");
228 load_font->add_child_text ("urn:uuid:" + i->urn);
229 load_font->set_attribute ("ID", i->id);
232 subtitles_as_xml (root->add_child ("SubtitleList", "dcst"), _time_code_rate, SMPTE);
234 return doc.write_to_string_formatted ("UTF-8");
237 /** Write this content to a MXF file */
239 SMPTESubtitleAsset::write (boost::filesystem::path p) const
241 ASDCP::WriterInfo writer_info;
242 fill_writer_info (&writer_info, _id, SMPTE);
244 ASDCP::TimedText::TimedTextDescriptor descriptor;
245 descriptor.EditRate = ASDCP::Rational (_edit_rate.numerator, _edit_rate.denominator);
246 descriptor.EncodingName = "UTF-8";
248 BOOST_FOREACH (shared_ptr<dcp::SMPTELoadFontNode> i, _load_font_nodes) {
249 list<Font>::const_iterator j = _fonts.begin ();
250 while (j != _fonts.end() && j->load_id != i->id) {
253 if (j != _fonts.end ()) {
254 ASDCP::TimedText::TimedTextResourceDescriptor res;
256 Kumu::hex2bin (i->urn.c_str(), res.ResourceID, Kumu::UUID_Length, &c);
257 DCP_ASSERT (c == Kumu::UUID_Length);
258 res.Type = ASDCP::TimedText::MT_OPENTYPE;
259 descriptor.ResourceList.push_back (res);
263 descriptor.NamespaceName = "dcst";
264 memcpy (descriptor.AssetID, writer_info.AssetUUID, ASDCP::UUIDlen);
265 descriptor.ContainerDuration = _intrinsic_duration;
267 ASDCP::TimedText::MXFWriter writer;
268 ASDCP::Result_t r = writer.OpenWrite (p.string().c_str(), writer_info, descriptor);
269 if (ASDCP_FAILURE (r)) {
270 boost::throw_exception (FileError ("could not open subtitle MXF for writing", p.string(), r));
273 /* XXX: no encryption */
274 r = writer.WriteTimedTextResource (xml_as_string ());
275 if (ASDCP_FAILURE (r)) {
276 boost::throw_exception (MXFFileError ("could not write XML to timed text resource", p.string(), r));
279 BOOST_FOREACH (shared_ptr<dcp::SMPTELoadFontNode> i, _load_font_nodes) {
280 list<Font>::const_iterator j = _fonts.begin ();
281 while (j != _fonts.end() && j->load_id != i->id) {
284 if (j != _fonts.end ()) {
285 ASDCP::TimedText::FrameBuffer buffer;
286 buffer.SetData (j->data.data().get(), j->data.size());
287 buffer.Size (j->data.size());
288 r = writer.WriteAncillaryResource (buffer);
289 if (ASDCP_FAILURE (r)) {
290 boost::throw_exception (MXFFileError ("could not write font to timed text resource", p.string(), r));
301 SMPTESubtitleAsset::equals (shared_ptr<const Asset> other_asset, EqualityOptions options, NoteHandler note) const
303 if (!SubtitleAsset::equals (other_asset, options, note)) {
307 shared_ptr<const SMPTESubtitleAsset> other = dynamic_pointer_cast<const SMPTESubtitleAsset> (other_asset);
309 note (DCP_ERROR, "Subtitles are in different standards");
313 list<shared_ptr<SMPTELoadFontNode> >::const_iterator i = _load_font_nodes.begin ();
314 list<shared_ptr<SMPTELoadFontNode> >::const_iterator j = other->_load_font_nodes.begin ();
316 while (i != _load_font_nodes.end ()) {
317 if (j == other->_load_font_nodes.end ()) {
318 note (DCP_ERROR, "<LoadFont> nodes differ");
322 if ((*i)->id != (*j)->id) {
323 note (DCP_ERROR, "<LoadFont> nodes differ");
331 if (_content_title_text != other->_content_title_text) {
332 note (DCP_ERROR, "Subtitle content title texts differ");
336 if (_language != other->_language) {
337 note (DCP_ERROR, "Subtitle languages differ");
341 if (_annotation_text != other->_annotation_text) {
342 note (DCP_ERROR, "Subtitle annotation texts differ");
346 if (_issue_date != other->_issue_date) {
347 if (options.issue_dates_can_differ) {
348 note (DCP_NOTE, "Subtitle issue dates differ");
350 note (DCP_ERROR, "Subtitle issue dates differ");
355 if (_reel_number != other->_reel_number) {
356 note (DCP_ERROR, "Subtitle reel numbers differ");
360 if (_edit_rate != other->_edit_rate) {
361 note (DCP_ERROR, "Subtitle edit rates differ");
365 if (_time_code_rate != other->_time_code_rate) {
366 note (DCP_ERROR, "Subtitle time code rates differ");
370 if (_start_time != other->_start_time) {
371 note (DCP_ERROR, "Subtitle start times differ");
379 SMPTESubtitleAsset::add_font (string load_id, boost::filesystem::path file)
381 string const uuid = make_uuid ();
382 _fonts.push_back (Font (load_id, uuid, file));
383 _load_font_nodes.push_back (shared_ptr<SMPTELoadFontNode> (new SMPTELoadFontNode (load_id, uuid)));
387 SMPTESubtitleAsset::add (dcp::SubtitleString s)
389 SubtitleAsset::add (s);
390 _intrinsic_duration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator);