Split ReelClosedCaptionAsset into Interop and SMPTE parts.
[libdcp.git] / src / reel.cc
1 /*
2     Copyright (C) 2014-2021 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
35 /** @file  src/reel.cc
36  *  @brief Reel class
37  */
38
39
40 #include "reel.h"
41 #include "util.h"
42 #include "picture_asset.h"
43 #include "mono_picture_asset.h"
44 #include "stereo_picture_asset.h"
45 #include "sound_asset.h"
46 #include "subtitle_asset.h"
47 #include "reel_mono_picture_asset.h"
48 #include "reel_stereo_picture_asset.h"
49 #include "reel_sound_asset.h"
50 #include "reel_interop_closed_caption_asset.h"
51 #include "reel_interop_subtitle_asset.h"
52 #include "reel_smpte_closed_caption_asset.h"
53 #include "reel_smpte_subtitle_asset.h"
54 #include "reel_subtitle_asset.h"
55 #include "reel_markers_asset.h"
56 #include "decrypted_kdm_key.h"
57 #include "decrypted_kdm.h"
58 #include "interop_subtitle_asset.h"
59 #include "smpte_subtitle_asset.h"
60 #include "reel_atmos_asset.h"
61 #include "reel_closed_caption_asset.h"
62 #include <libxml++/nodes/element.h>
63 #include <stdint.h>
64
65
66 using std::string;
67 using std::cout;
68 using std::min;
69 using std::make_shared;
70 using std::shared_ptr;
71 using std::dynamic_pointer_cast;
72 using std::vector;
73 using namespace dcp;
74
75
76 Reel::Reel (std::shared_ptr<const cxml::Node> node, dcp::Standard standard)
77         : Object (remove_urn_uuid (node->string_child ("Id")))
78 {
79         auto asset_list = node->node_child ("AssetList");
80
81         auto main_picture = asset_list->optional_node_child ("MainPicture");
82         if (main_picture) {
83                 _main_picture = make_shared<ReelMonoPictureAsset>(main_picture);
84         }
85
86         auto main_stereoscopic_picture = asset_list->optional_node_child ("MainStereoscopicPicture");
87         if (main_stereoscopic_picture) {
88                 _main_picture = make_shared<ReelStereoPictureAsset>(main_stereoscopic_picture);
89         }
90
91         auto main_sound = asset_list->optional_node_child ("MainSound");
92         if (main_sound) {
93                 _main_sound = make_shared<ReelSoundAsset>(main_sound);
94         }
95
96         auto main_subtitle = asset_list->optional_node_child ("MainSubtitle");
97         if (main_subtitle) {
98                 switch (standard) {
99                         case Standard::INTEROP:
100                                 _main_subtitle = make_shared<ReelInteropSubtitleAsset>(main_subtitle);
101                                 break;
102                         case Standard::SMPTE:
103                                 _main_subtitle = make_shared<ReelSMPTESubtitleAsset>(main_subtitle);
104                                 break;
105                 }
106         }
107
108         auto main_markers = asset_list->optional_node_child ("MainMarkers");
109         if (main_markers) {
110                 _main_markers = make_shared<ReelMarkersAsset>(main_markers);
111         }
112
113         /* XXX: it's not ideal that we silently tolerate Interop or SMPTE nodes here */
114         /* XXX: not sure if Interop supports multiple closed captions */
115         auto closed_captions = asset_list->node_children ("MainClosedCaption");
116         if (closed_captions.empty()) {
117                 closed_captions = asset_list->node_children ("ClosedCaption");
118         }
119         for (auto i: closed_captions) {
120                 switch (standard) {
121                         case Standard::INTEROP:
122                                 _closed_captions.push_back (make_shared<ReelInteropClosedCaptionAsset>(i));
123                                 break;
124                         case Standard::SMPTE:
125                                 _closed_captions.push_back (make_shared<ReelSMPTEClosedCaptionAsset>(i));
126                                 break;
127                 }
128         }
129
130         auto atmos = asset_list->optional_node_child ("AuxData");
131         if (atmos) {
132                 _atmos = make_shared<ReelAtmosAsset>(atmos);
133         }
134
135         node->ignore_child ("AnnotationText");
136         node->done ();
137 }
138
139
140 xmlpp::Element *
141 Reel::write_to_cpl (xmlpp::Element* node, Standard standard) const
142 {
143         auto reel = node->add_child ("Reel");
144         reel->add_child("Id")->add_child_text("urn:uuid:" + _id);
145         xmlpp::Element* asset_list = reel->add_child ("AssetList");
146
147         if (_main_markers) {
148                 _main_markers->write_to_cpl (asset_list, standard);
149         }
150
151         if (_main_picture && dynamic_pointer_cast<ReelMonoPictureAsset> (_main_picture)) {
152                 /* Mono pictures come before other stuff... */
153                 _main_picture->write_to_cpl (asset_list, standard);
154         }
155
156         if (_main_sound) {
157                 _main_sound->write_to_cpl (asset_list, standard);
158         }
159
160         if (_main_subtitle) {
161                 _main_subtitle->write_to_cpl (asset_list, standard);
162         }
163
164         for (auto i: _closed_captions) {
165                 i->write_to_cpl (asset_list, standard);
166         }
167
168         if (_main_picture && dynamic_pointer_cast<ReelStereoPictureAsset> (_main_picture)) {
169                 /* ... but stereo pictures must come after */
170                 _main_picture->write_to_cpl (asset_list, standard);
171         }
172
173         if (_atmos) {
174                 _atmos->write_to_cpl (asset_list, standard);
175         }
176
177         return asset_list;
178 }
179
180
181 bool
182 Reel::equals (std::shared_ptr<const Reel> other, EqualityOptions opt, NoteHandler note) const
183 {
184         if ((_main_picture && !other->_main_picture) || (!_main_picture && other->_main_picture)) {
185                 note (NoteType::ERROR, "Reel: picture assets differ");
186                 return false;
187         }
188
189         if (_main_picture && !_main_picture->equals (other->_main_picture, opt, note)) {
190                 return false;
191         }
192
193         if ((_main_sound && !other->_main_sound) || (!_main_sound && other->_main_sound)) {
194                 note (NoteType::ERROR, "Reel: sound assets differ");
195                 return false;
196         }
197
198         if (_main_sound && !_main_sound->equals (other->_main_sound, opt, note)) {
199                 return false;
200         }
201
202         if ((_main_subtitle && !other->_main_subtitle) || (!_main_subtitle && other->_main_subtitle)) {
203                 note (NoteType::ERROR, "Reel: subtitle assets differ");
204                 return false;
205         }
206
207         bool same_type = false;
208
209         {
210                 auto interop = dynamic_pointer_cast<ReelInteropSubtitleAsset>(_main_subtitle);
211                 auto interop_other = dynamic_pointer_cast<ReelInteropSubtitleAsset>(other->_main_subtitle);
212                 if (interop && interop_other) {
213                         same_type = true;
214                         if (!interop->equals(interop_other, opt, note)) {
215                                 return false;
216                         }
217                 }
218         }
219
220         {
221                 auto smpte = dynamic_pointer_cast<ReelSMPTESubtitleAsset>(_main_subtitle);
222                 auto smpte_other = dynamic_pointer_cast<ReelSMPTESubtitleAsset>(other->_main_subtitle);
223                 if (smpte && smpte_other) {
224                         same_type = true;
225                         if (!smpte->equals(smpte_other, opt, note)) {
226                                 return false;
227                         }
228                 }
229         }
230
231         if ((_main_subtitle || other->_main_subtitle) && !same_type) {
232                 return false;
233         }
234
235         if ((_main_markers && !other->_main_markers) || (!_main_markers && other->_main_markers)) {
236                 note (NoteType::ERROR, "Reel: one has markers and the other does not");
237                 return false;
238         }
239
240         if (_main_markers && !_main_markers->equals(other->_main_markers, opt, note)) {
241                 note (NoteType::ERROR, "Reel: marker assets differ");
242                 return false;
243         }
244
245         if (_closed_captions.size() != other->_closed_captions.size()) {
246                 return false;
247         }
248
249         auto i = _closed_captions.begin();
250         auto j = other->_closed_captions.begin();
251         while (i != _closed_captions.end()) {
252                 if (!(*i)->equals(*j, opt, note)) {
253                         return false;
254                 }
255                 ++i;
256                 ++j;
257         }
258
259         if ((_atmos && !other->_atmos) || (!_atmos && other->_atmos)) {
260                 note (NoteType::ERROR, "Reel: atmos assets differ");
261                 return false;
262         }
263
264         if (_atmos && !_atmos->equals (other->_atmos, opt, note)) {
265                 return false;
266         }
267
268         return true;
269 }
270
271
272 bool
273 Reel::any_encrypted () const
274 {
275         auto ecc = false;
276         for (auto i: _closed_captions) {
277                 if (auto enc = dynamic_pointer_cast<ReelEncryptableAsset>(i)) {
278                         if (enc->encrypted()) {
279                                 ecc = true;
280                         }
281                 }
282         }
283
284         bool esub = false;
285         if (_main_subtitle) {
286                 if (auto enc = dynamic_pointer_cast<ReelEncryptableAsset>(_main_picture)) {
287                         esub = enc->encrypted();
288                 }
289         }
290
291         return (
292                 (_main_picture && _main_picture->encrypted()) ||
293                 (_main_sound && _main_sound->encrypted()) ||
294                 esub ||
295                 ecc ||
296                 (_atmos && _atmos->encrypted())
297                 );
298 }
299
300
301 bool
302 Reel::all_encrypted () const
303 {
304         auto ecc = true;
305         for (auto i: _closed_captions) {
306                 if (auto enc = dynamic_pointer_cast<ReelEncryptableAsset>(i)) {
307                         if (!enc->encrypted()) {
308                                 ecc = false;
309                         }
310                 }
311         }
312
313         /* It's ok if there's no subtitle, or it's not encryptable */
314         bool esub = true;
315         if (_main_subtitle) {
316                 if (auto enc = dynamic_pointer_cast<ReelEncryptableAsset>(_main_picture)) {
317                         esub = enc->encrypted();
318                 }
319         }
320
321         return (
322                 (!_main_picture || _main_picture->encrypted()) &&
323                 (!_main_sound || _main_sound->encrypted()) &&
324                 esub &&
325                 ecc &&
326                 (!_atmos || _atmos->encrypted())
327                );
328 }
329
330
331 void
332 Reel::add (DecryptedKDM const & kdm)
333 {
334         auto keys = kdm.keys ();
335
336         for (auto const& i: keys) {
337                 if (_main_picture && i.id() == _main_picture->key_id()) {
338                         _main_picture->asset()->set_key (i.key());
339                 }
340                 if (_main_sound && i.id() == _main_sound->key_id()) {
341                         _main_sound->asset()->set_key (i.key());
342                 }
343                 if (_main_subtitle) {
344                         auto smpte = dynamic_pointer_cast<ReelSMPTESubtitleAsset>(_main_picture);
345                         if (smpte && i.id() == smpte->key_id()) {
346                                 smpte->smpte_asset()->set_key(i.key());
347                         }
348                 }
349                 for (auto j: _closed_captions) {
350                         auto smpte = dynamic_pointer_cast<ReelSMPTESubtitleAsset>(j);
351                         if (smpte && i.id() == smpte->key_id()) {
352                                 smpte->smpte_asset()->set_key(i.key());
353                         }
354                 }
355                 if (_atmos && i.id() == _atmos->key_id()) {
356                         _atmos->asset()->set_key (i.key());
357                 }
358         }
359 }
360
361
362 void
363 Reel::add (shared_ptr<ReelAsset> asset)
364 {
365         auto p = dynamic_pointer_cast<ReelPictureAsset> (asset);
366         auto so = dynamic_pointer_cast<ReelSoundAsset> (asset);
367         auto su = dynamic_pointer_cast<ReelSubtitleAsset> (asset);
368         auto m = dynamic_pointer_cast<ReelMarkersAsset> (asset);
369         auto c = dynamic_pointer_cast<ReelClosedCaptionAsset> (asset);
370         auto a = dynamic_pointer_cast<ReelAtmosAsset> (asset);
371         if (p) {
372                 _main_picture = p;
373         } else if (so) {
374                 _main_sound = so;
375         } else if (su) {
376                 _main_subtitle = su;
377         } else if (m) {
378                 _main_markers = m;
379         } else if (c) {
380                 _closed_captions.push_back (c);
381         } else if (a) {
382                 _atmos = a;
383         }
384 }
385
386
387 vector<shared_ptr<ReelAsset>>
388 Reel::assets () const
389 {
390         vector<shared_ptr<ReelAsset>> a;
391         if (_main_picture) {
392                 a.push_back (_main_picture);
393         }
394         if (_main_sound) {
395                 a.push_back (_main_sound);
396         }
397         if (_main_subtitle) {
398                 a.push_back (_main_subtitle);
399         }
400         std::copy (_closed_captions.begin(), _closed_captions.end(), back_inserter(a));
401         if (_atmos) {
402                 a.push_back (_atmos);
403         }
404         return a;
405 }
406
407
408 void
409 Reel::resolve_refs (vector<shared_ptr<Asset>> assets)
410 {
411         if (_main_picture) {
412                 _main_picture->asset_ref().resolve(assets);
413         }
414
415         if (_main_sound) {
416                 _main_sound->asset_ref().resolve(assets);
417         }
418
419         if (_main_subtitle) {
420                 _main_subtitle->asset_ref().resolve(assets);
421
422                 /* Interop subtitle handling is all special cases */
423                 if (_main_subtitle->asset_ref().resolved()) {
424                         auto iop = dynamic_pointer_cast<InteropSubtitleAsset> (_main_subtitle->asset_ref().asset());
425                         if (iop) {
426                                 iop->resolve_fonts (assets);
427                         }
428                 }
429         }
430
431         for (auto i: _closed_captions) {
432                 i->asset_ref().resolve(assets);
433
434                 /* Interop subtitle handling is all special cases */
435                 if (i->asset_ref().resolved()) {
436                         auto iop = dynamic_pointer_cast<InteropSubtitleAsset> (i->asset_ref().asset());
437                         if (iop) {
438                                 iop->resolve_fonts (assets);
439                         }
440                 }
441         }
442
443         if (_atmos) {
444                 _atmos->asset_ref().resolve (assets);
445         }
446 }
447
448
449 int64_t
450 Reel::duration () const
451 {
452         if (_main_picture) {
453                 return _main_picture->actual_duration();
454         }
455
456         int64_t d = INT64_MAX;
457
458         if (_main_sound) {
459                 d = min (d, _main_sound->actual_duration());
460         }
461         if (_main_subtitle) {
462                 d = min (d, _main_subtitle->actual_duration());
463         }
464         if (_main_markers) {
465                 d = min (d, _main_markers->actual_duration());
466         }
467         for (auto i: _closed_captions) {
468                 d = min (d, i->actual_duration());
469         }
470         if (_atmos) {
471                 d = min (d, _atmos->actual_duration());
472         }
473
474         DCP_ASSERT (d < INT64_MAX);
475
476         return d;
477 }