33a852cce6a5c22c5dd1d7aba10f65434959c513
[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 "decrypted_kdm.h"
41 #include "decrypted_kdm_key.h"
42 #include "interop_subtitle_asset.h"
43 #include "mono_picture_asset.h"
44 #include "picture_asset.h"
45 #include "reel.h"
46 #include "reel_atmos_asset.h"
47 #include "reel_closed_caption_asset.h"
48 #include "reel_interop_closed_caption_asset.h"
49 #include "reel_interop_subtitle_asset.h"
50 #include "reel_markers_asset.h"
51 #include "reel_mono_picture_asset.h"
52 #include "reel_smpte_closed_caption_asset.h"
53 #include "reel_smpte_subtitle_asset.h"
54 #include "reel_sound_asset.h"
55 #include "reel_stereo_picture_asset.h"
56 #include "reel_subtitle_asset.h"
57 #include "smpte_subtitle_asset.h"
58 #include "sound_asset.h"
59 #include "stereo_picture_asset.h"
60 #include "subtitle_asset.h"
61 #include "util.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 const& 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 (i->encrypted()) {
278                         ecc = true;
279                 }
280         }
281
282         return (
283                 (_main_picture && _main_picture->encrypted()) ||
284                 (_main_sound && _main_sound->encrypted()) ||
285                 (_main_subtitle && _main_subtitle->encrypted()) ||
286                 ecc ||
287                 (_atmos && _atmos->encrypted())
288                 );
289 }
290
291
292 bool
293 Reel::all_encrypted () const
294 {
295         auto ecc = true;
296         for (auto i: _closed_captions) {
297                 if (!i->encrypted()) {
298                         ecc = false;
299                 }
300         }
301
302         return (
303                 (!_main_picture || _main_picture->encrypted()) &&
304                 (!_main_sound || _main_sound->encrypted()) &&
305                 (!_main_subtitle || _main_subtitle->encrypted()) &&
306                 ecc &&
307                 (!_atmos || _atmos->encrypted())
308                );
309 }
310
311
312 void
313 Reel::add (DecryptedKDM const & kdm)
314 {
315         give_kdm_to_assets (kdm);
316         /* We have to keep the KDMs that we are given, as they will not be passed to unresolved assets.
317          * After we resolve some assets we will re-call give_kdm_to_assets() with all the KDMs that
318          * we have been given so far.
319          */
320         _kdms.push_back (kdm);
321 }
322
323
324 void
325 Reel::give_kdm_to_assets (DecryptedKDM const & kdm)
326 {
327         for (auto const& i: kdm.keys()) {
328                 if (_main_picture && i.id() == _main_picture->key_id() && _main_picture->asset_ref().resolved()) {
329                         _main_picture->asset()->set_key (i.key());
330                 }
331                 if (_main_sound && i.id() == _main_sound->key_id() && _main_sound->asset_ref().resolved()) {
332                         _main_sound->asset()->set_key (i.key());
333                 }
334                 if (_main_subtitle) {
335                         auto smpte = dynamic_pointer_cast<ReelSMPTESubtitleAsset>(_main_subtitle);
336                         if (smpte && i.id() == smpte->key_id() && smpte->asset_ref().resolved()) {
337                                 smpte->smpte_asset()->set_key(i.key());
338                         }
339                 }
340                 for (auto j: _closed_captions) {
341                         auto smpte = dynamic_pointer_cast<ReelSMPTESubtitleAsset>(j);
342                         if (smpte && i.id() == smpte->key_id() && smpte->asset_ref().resolved()) {
343                                 smpte->smpte_asset()->set_key(i.key());
344                         }
345                 }
346                 if (_atmos && i.id() == _atmos->key_id() && _atmos->asset_ref().resolved()) {
347                         _atmos->asset()->set_key (i.key());
348                 }
349         }
350 }
351
352
353 void
354 Reel::add (shared_ptr<ReelAsset> asset)
355 {
356         if (auto p = dynamic_pointer_cast<ReelPictureAsset>(asset)) {
357                 _main_picture = p;
358         } else if (auto so = dynamic_pointer_cast<ReelSoundAsset>(asset)) {
359                 _main_sound = so;
360         } else if (auto su = dynamic_pointer_cast<ReelSubtitleAsset>(asset)) {
361                 _main_subtitle = su;
362         } else if (auto m = dynamic_pointer_cast<ReelMarkersAsset>(asset)) {
363                 _main_markers = m;
364         } else if (auto c = dynamic_pointer_cast<ReelClosedCaptionAsset>(asset)) {
365                 _closed_captions.push_back (c);
366         } else if (auto a = dynamic_pointer_cast<ReelAtmosAsset>(asset)) {
367                 _atmos = a;
368         } else {
369                 DCP_ASSERT(false);
370         }
371 }
372
373
374 vector<shared_ptr<ReelAsset>>
375 Reel::assets () const
376 {
377         vector<shared_ptr<ReelAsset>> a;
378         if (_main_picture) {
379                 a.push_back (_main_picture);
380         }
381         if (_main_sound) {
382                 a.push_back (_main_sound);
383         }
384         if (_main_subtitle) {
385                 a.push_back (_main_subtitle);
386         }
387         std::copy (_closed_captions.begin(), _closed_captions.end(), back_inserter(a));
388         if (_atmos) {
389                 a.push_back (_atmos);
390         }
391         return a;
392 }
393
394
395 void
396 Reel::resolve_refs (vector<shared_ptr<Asset>> assets)
397 {
398         if (_main_picture) {
399                 _main_picture->asset_ref().resolve(assets);
400         }
401
402         if (_main_sound) {
403                 _main_sound->asset_ref().resolve(assets);
404         }
405
406         if (_main_subtitle) {
407                 _main_subtitle->asset_ref().resolve(assets);
408
409                 /* Interop subtitle handling is all special cases */
410                 if (_main_subtitle->asset_ref().resolved()) {
411                         auto iop = dynamic_pointer_cast<InteropSubtitleAsset> (_main_subtitle->asset_ref().asset());
412                         if (iop) {
413                                 iop->resolve_fonts (assets);
414                         }
415                 }
416         }
417
418         for (auto i: _closed_captions) {
419                 i->asset_ref().resolve(assets);
420
421                 /* Interop subtitle handling is all special cases */
422                 if (i->asset_ref().resolved()) {
423                         auto iop = dynamic_pointer_cast<InteropSubtitleAsset> (i->asset_ref().asset());
424                         if (iop) {
425                                 iop->resolve_fonts (assets);
426                         }
427                 }
428         }
429
430         if (_atmos) {
431                 _atmos->asset_ref().resolve (assets);
432         }
433
434         for (auto const& i: _kdms) {
435                 give_kdm_to_assets (i);
436         }
437 }
438
439
440 int64_t
441 Reel::duration () const
442 {
443         if (_main_picture) {
444                 return _main_picture->actual_duration();
445         }
446
447         int64_t d = INT64_MAX;
448
449         if (_main_sound) {
450                 d = min (d, _main_sound->actual_duration());
451         }
452         if (_main_subtitle) {
453                 d = min (d, _main_subtitle->actual_duration());
454         }
455         if (_main_markers) {
456                 d = min (d, _main_markers->actual_duration());
457         }
458         for (auto i: _closed_captions) {
459                 d = min (d, i->actual_duration());
460         }
461         if (_atmos) {
462                 d = min (d, _atmos->actual_duration());
463         }
464
465         DCP_ASSERT (d < INT64_MAX);
466
467         return d;
468 }