Store interop/SMPTE in MXF.
[libdcp.git] / src / smpte_subtitle_asset.cc
1 /*
2     Copyright (C) 2012-2016 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 /** @file  src/smpte_subtitle_asset.cc
35  *  @brief SMPTESubtitleAsset class.
36  */
37
38 #include "smpte_subtitle_asset.h"
39 #include "smpte_load_font_node.h"
40 #include "exceptions.h"
41 #include "xml.h"
42 #include "raw_convert.h"
43 #include "dcp_assert.h"
44 #include "util.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>
53
54 using std::string;
55 using std::list;
56 using std::vector;
57 using std::map;
58 using boost::shared_ptr;
59 using boost::split;
60 using boost::is_any_of;
61 using boost::shared_array;
62 using boost::dynamic_pointer_cast;
63 using namespace dcp;
64
65 SMPTESubtitleAsset::SMPTESubtitleAsset ()
66         : MXF (SMPTE)
67         , _intrinsic_duration (0)
68         , _edit_rate (24, 1)
69         , _time_code_rate (24)
70         , _xml_id (make_uuid ())
71 {
72
73 }
74
75 /** Construct a SMPTESubtitleAsset by reading an MXF or XML file.
76  *  @param file Filename.
77  */
78 SMPTESubtitleAsset::SMPTESubtitleAsset (boost::filesystem::path file)
79         : SubtitleAsset (file)
80 {
81         shared_ptr<cxml::Document> xml (new cxml::Document ("SubtitleReel"));
82
83         shared_ptr<ASDCP::TimedText::MXFReader> reader (new ASDCP::TimedText::MXFReader ());
84         Kumu::Result_t r = reader->OpenRead (_file->string().c_str ());
85         if (!ASDCP_FAILURE (r)) {
86                 /* MXF-wrapped */
87                 ASDCP::WriterInfo info;
88                 reader->FillWriterInfo (info);
89                 _id = read_writer_info (info);
90                 if (!_key_id) {
91                         /* Not encrypted; read it in now */
92                         string s;
93                         reader->ReadTimedTextResource (s);
94                         xml->read_string (s);
95                         parse_xml (xml);
96                         read_mxf_descriptor (reader, shared_ptr<DecryptionContext> (new DecryptionContext ()));
97                 }
98         } else {
99                 /* Plain XML */
100                 try {
101                         xml.reset (new cxml::Document ("SubtitleReel"));
102                         xml->read_file (file);
103                         parse_xml (xml);
104                         _id = _xml_id = remove_urn_uuid (xml->string_child ("Id"));
105                 } catch (cxml::Error& e) {
106                         boost::throw_exception (
107                                 DCPReadError (
108                                         String::compose (
109                                                 "Failed to read subtitle file %1; MXF failed with %2, XML failed with %3",
110                                                 file, static_cast<int> (r), e.what ()
111                                                 )
112                                         )
113                                 );
114                 }
115         }
116 }
117
118 void
119 SMPTESubtitleAsset::parse_xml (shared_ptr<cxml::Document> xml)
120 {
121         _xml_id = remove_urn_uuid(xml->string_child("Id"));
122         _load_font_nodes = type_children<dcp::SMPTELoadFontNode> (xml, "LoadFont");
123
124         _content_title_text = xml->string_child ("ContentTitleText");
125         _annotation_text = xml->optional_string_child ("AnnotationText");
126         _issue_date = LocalTime (xml->string_child ("IssueDate"));
127         _reel_number = xml->optional_number_child<int> ("ReelNumber");
128         _language = xml->optional_string_child ("Language");
129
130         /* This is supposed to be two numbers, but a single number has been seen in the wild */
131         string const er = xml->string_child ("EditRate");
132         vector<string> er_parts;
133         split (er_parts, er, is_any_of (" "));
134         if (er_parts.size() == 1) {
135                 _edit_rate = Fraction (raw_convert<int> (er_parts[0]), 1);
136         } else if (er_parts.size() == 2) {
137                 _edit_rate = Fraction (raw_convert<int> (er_parts[0]), raw_convert<int> (er_parts[1]));
138         } else {
139                 throw XMLError ("malformed EditRate " + er);
140         }
141
142         _time_code_rate = xml->number_child<int> ("TimeCodeRate");
143         if (xml->optional_string_child ("StartTime")) {
144                 _start_time = Time (xml->string_child ("StartTime"), _time_code_rate);
145         }
146
147         /* Now we need to drop down to xmlpp */
148
149         list<ParseState> ps;
150         xmlpp::Node::NodeList c = xml->node()->get_children ();
151         for (xmlpp::Node::NodeList::const_iterator i = c.begin(); i != c.end(); ++i) {
152                 xmlpp::Element const * e = dynamic_cast<xmlpp::Element const *> (*i);
153                 if (e && e->get_name() == "SubtitleList") {
154                         parse_subtitles (e, ps, _time_code_rate, SMPTE);
155                 }
156         }
157
158         /* Guess intrinsic duration */
159         _intrinsic_duration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator);
160 }
161
162 void
163 SMPTESubtitleAsset::read_mxf_descriptor (shared_ptr<ASDCP::TimedText::MXFReader> reader, shared_ptr<DecryptionContext> dec)
164 {
165         ASDCP::TimedText::TimedTextDescriptor descriptor;
166         reader->FillTimedTextDescriptor (descriptor);
167
168         /* Load fonts */
169
170         for (
171                 ASDCP::TimedText::ResourceList_t::const_iterator i = descriptor.ResourceList.begin();
172                 i != descriptor.ResourceList.end();
173                 ++i) {
174
175                 if (i->Type == ASDCP::TimedText::MT_OPENTYPE) {
176                         ASDCP::TimedText::FrameBuffer buffer;
177                         buffer.Capacity (10 * 1024 * 1024);
178                         reader->ReadAncillaryResource (i->ResourceID, buffer, dec->decryption());
179
180                         char id[64];
181                         Kumu::bin2UUIDhex (i->ResourceID, ASDCP::UUIDlen, id, sizeof (id));
182
183                         shared_array<uint8_t> data (new uint8_t[buffer.Size()]);
184                         memcpy (data.get(), buffer.RoData(), buffer.Size());
185
186                         list<shared_ptr<SMPTELoadFontNode> >::const_iterator j = _load_font_nodes.begin ();
187                         while (j != _load_font_nodes.end() && (*j)->urn != id) {
188                                 ++j;
189                         }
190
191                         if (j != _load_font_nodes.end ()) {
192                                 _fonts.push_back (Font ((*j)->id, (*j)->urn, Data (data, buffer.Size ())));
193                         }
194                 }
195         }
196
197         /* Get intrinsic duration */
198         _intrinsic_duration = descriptor.ContainerDuration;
199 }
200
201 void
202 SMPTESubtitleAsset::set_key (Key key)
203 {
204         /* See if we already have a key; if we do, and we have a file, we'll already
205            have read that file.
206         */
207         bool const had_key = static_cast<bool> (_key);
208
209         MXF::set_key (key);
210
211         if (!_key_id || !_file || had_key) {
212                 /* Either we don't have any data to read, it wasn't
213                    encrypted, or we've already read it, so we don't
214                    need to do anything else.
215                 */
216                 return;
217         }
218
219         /* Our data was encrypted; now we can decrypt it */
220
221         shared_ptr<ASDCP::TimedText::MXFReader> reader (new ASDCP::TimedText::MXFReader ());
222         Kumu::Result_t r = reader->OpenRead (_file->string().c_str ());
223         if (ASDCP_FAILURE (r)) {
224                 boost::throw_exception (
225                         DCPReadError (
226                                 String::compose ("Could not read encrypted subtitle MXF (%1)", static_cast<int> (r))
227                                 )
228                         );
229         }
230
231         string s;
232         shared_ptr<DecryptionContext> dec (new DecryptionContext (key));
233         reader->ReadTimedTextResource (s, dec->decryption());
234         shared_ptr<cxml::Document> xml (new cxml::Document ("SubtitleReel"));
235         xml->read_string (s);
236         parse_xml (xml);
237         read_mxf_descriptor (reader, dec);
238 }
239
240 list<shared_ptr<LoadFontNode> >
241 SMPTESubtitleAsset::load_font_nodes () const
242 {
243         list<shared_ptr<LoadFontNode> > lf;
244         copy (_load_font_nodes.begin(), _load_font_nodes.end(), back_inserter (lf));
245         return lf;
246 }
247
248 bool
249 SMPTESubtitleAsset::valid_mxf (boost::filesystem::path file)
250 {
251         ASDCP::TimedText::MXFReader reader;
252         Kumu::Result_t r = reader.OpenRead (file.string().c_str ());
253         return !ASDCP_FAILURE (r);
254 }
255
256 string
257 SMPTESubtitleAsset::xml_as_string () const
258 {
259         xmlpp::Document doc;
260         xmlpp::Element* root = doc.create_root_node ("dcst:SubtitleReel");
261         root->set_namespace_declaration ("http://www.smpte-ra.org/schemas/428-7/2010/DCST", "dcst");
262         root->set_namespace_declaration ("http://www.w3.org/2001/XMLSchema", "xs");
263
264         root->add_child("Id", "dcst")->add_child_text ("urn:uuid:" + _xml_id);
265         root->add_child("ContentTitleText", "dcst")->add_child_text (_content_title_text);
266         if (_annotation_text) {
267                 root->add_child("AnnotationText", "dcst")->add_child_text (_annotation_text.get ());
268         }
269         root->add_child("IssueDate", "dcst")->add_child_text (_issue_date.as_string (true));
270         if (_reel_number) {
271                 root->add_child("ReelNumber", "dcst")->add_child_text (raw_convert<string> (_reel_number.get ()));
272         }
273         if (_language) {
274                 root->add_child("Language", "dcst")->add_child_text (_language.get ());
275         }
276         root->add_child("EditRate", "dcst")->add_child_text (_edit_rate.as_string ());
277         root->add_child("TimeCodeRate", "dcst")->add_child_text (raw_convert<string> (_time_code_rate));
278         if (_start_time) {
279                 root->add_child("StartTime", "dcst")->add_child_text (_start_time.get().as_string (SMPTE));
280         }
281
282         BOOST_FOREACH (shared_ptr<SMPTELoadFontNode> i, _load_font_nodes) {
283                 xmlpp::Element* load_font = root->add_child("LoadFont", "dcst");
284                 load_font->add_child_text ("urn:uuid:" + i->urn);
285                 load_font->set_attribute ("ID", i->id);
286         }
287
288         subtitles_as_xml (root->add_child ("SubtitleList", "dcst"), _time_code_rate, SMPTE);
289
290         return doc.write_to_string ("UTF-8");
291 }
292
293 /** Write this content to a MXF file */
294 void
295 SMPTESubtitleAsset::write (boost::filesystem::path p) const
296 {
297         EncryptionContext enc (key(), SMPTE);
298
299         ASDCP::WriterInfo writer_info;
300         fill_writer_info (&writer_info, _id);
301
302         ASDCP::TimedText::TimedTextDescriptor descriptor;
303         descriptor.EditRate = ASDCP::Rational (_edit_rate.numerator, _edit_rate.denominator);
304         descriptor.EncodingName = "UTF-8";
305
306         BOOST_FOREACH (shared_ptr<dcp::SMPTELoadFontNode> i, _load_font_nodes) {
307                 list<Font>::const_iterator j = _fonts.begin ();
308                 while (j != _fonts.end() && j->load_id != i->id) {
309                         ++j;
310                 }
311                 if (j != _fonts.end ()) {
312                         ASDCP::TimedText::TimedTextResourceDescriptor res;
313                         unsigned int c;
314                         Kumu::hex2bin (i->urn.c_str(), res.ResourceID, Kumu::UUID_Length, &c);
315                         DCP_ASSERT (c == Kumu::UUID_Length);
316                         res.Type = ASDCP::TimedText::MT_OPENTYPE;
317                         descriptor.ResourceList.push_back (res);
318                 }
319         }
320
321         descriptor.NamespaceName = "dcst";
322         memcpy (descriptor.AssetID, writer_info.AssetUUID, ASDCP::UUIDlen);
323         descriptor.ContainerDuration = _intrinsic_duration;
324
325         ASDCP::TimedText::MXFWriter writer;
326         ASDCP::Result_t r = writer.OpenWrite (p.string().c_str(), writer_info, descriptor);
327         if (ASDCP_FAILURE (r)) {
328                 boost::throw_exception (FileError ("could not open subtitle MXF for writing", p.string(), r));
329         }
330
331         /* XXX: no encryption */
332         r = writer.WriteTimedTextResource (xml_as_string (), enc.encryption(), enc.hmac());
333         if (ASDCP_FAILURE (r)) {
334                 boost::throw_exception (MXFFileError ("could not write XML to timed text resource", p.string(), r));
335         }
336
337         BOOST_FOREACH (shared_ptr<dcp::SMPTELoadFontNode> i, _load_font_nodes) {
338                 list<Font>::const_iterator j = _fonts.begin ();
339                 while (j != _fonts.end() && j->load_id != i->id) {
340                         ++j;
341                 }
342                 if (j != _fonts.end ()) {
343                         ASDCP::TimedText::FrameBuffer buffer;
344                         buffer.SetData (j->data.data().get(), j->data.size());
345                         buffer.Size (j->data.size());
346                         r = writer.WriteAncillaryResource (buffer, enc.encryption(), enc.hmac());
347                         if (ASDCP_FAILURE (r)) {
348                                 boost::throw_exception (MXFFileError ("could not write font to timed text resource", p.string(), r));
349                         }
350                 }
351         }
352
353         writer.Finalize ();
354
355         _file = p;
356 }
357
358 bool
359 SMPTESubtitleAsset::equals (shared_ptr<const Asset> other_asset, EqualityOptions options, NoteHandler note) const
360 {
361         if (!SubtitleAsset::equals (other_asset, options, note)) {
362                 return false;
363         }
364
365         shared_ptr<const SMPTESubtitleAsset> other = dynamic_pointer_cast<const SMPTESubtitleAsset> (other_asset);
366         if (!other) {
367                 note (DCP_ERROR, "Subtitles are in different standards");
368                 return false;
369         }
370
371         list<shared_ptr<SMPTELoadFontNode> >::const_iterator i = _load_font_nodes.begin ();
372         list<shared_ptr<SMPTELoadFontNode> >::const_iterator j = other->_load_font_nodes.begin ();
373
374         while (i != _load_font_nodes.end ()) {
375                 if (j == other->_load_font_nodes.end ()) {
376                         note (DCP_ERROR, "<LoadFont> nodes differ");
377                         return false;
378                 }
379
380                 if ((*i)->id != (*j)->id) {
381                         note (DCP_ERROR, "<LoadFont> nodes differ");
382                         return false;
383                 }
384
385                 ++i;
386                 ++j;
387         }
388
389         if (_content_title_text != other->_content_title_text) {
390                 note (DCP_ERROR, "Subtitle content title texts differ");
391                 return false;
392         }
393
394         if (_language != other->_language) {
395                 note (DCP_ERROR, "Subtitle languages differ");
396                 return false;
397         }
398
399         if (_annotation_text != other->_annotation_text) {
400                 note (DCP_ERROR, "Subtitle annotation texts differ");
401                 return false;
402         }
403
404         if (_issue_date != other->_issue_date) {
405                 if (options.issue_dates_can_differ) {
406                         note (DCP_NOTE, "Subtitle issue dates differ");
407                 } else {
408                         note (DCP_ERROR, "Subtitle issue dates differ");
409                         return false;
410                 }
411         }
412
413         if (_reel_number != other->_reel_number) {
414                 note (DCP_ERROR, "Subtitle reel numbers differ");
415                 return false;
416         }
417
418         if (_edit_rate != other->_edit_rate) {
419                 note (DCP_ERROR, "Subtitle edit rates differ");
420                 return false;
421         }
422
423         if (_time_code_rate != other->_time_code_rate) {
424                 note (DCP_ERROR, "Subtitle time code rates differ");
425                 return false;
426         }
427
428         if (_start_time != other->_start_time) {
429                 note (DCP_ERROR, "Subtitle start times differ");
430                 return false;
431         }
432
433         return true;
434 }
435
436 void
437 SMPTESubtitleAsset::add_font (string load_id, boost::filesystem::path file)
438 {
439         string const uuid = make_uuid ();
440         _fonts.push_back (Font (load_id, uuid, file));
441         _load_font_nodes.push_back (shared_ptr<SMPTELoadFontNode> (new SMPTELoadFontNode (load_id, uuid)));
442 }
443
444 void
445 SMPTESubtitleAsset::add (dcp::SubtitleString s)
446 {
447         SubtitleAsset::add (s);
448         _intrinsic_duration = latest_subtitle_out().as_editable_units (_edit_rate.numerator / _edit_rate.denominator);
449 }