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