Bv2.1 7.2.3: Check that subtitle <StartTime> exists and is 0.
[libdcp.git] / src / reel.cc
1 /*
2     Copyright (C) 2014-2020 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 #include "reel.h"
35 #include "util.h"
36 #include "picture_asset.h"
37 #include "mono_picture_asset.h"
38 #include "stereo_picture_asset.h"
39 #include "sound_asset.h"
40 #include "subtitle_asset.h"
41 #include "reel_mono_picture_asset.h"
42 #include "reel_stereo_picture_asset.h"
43 #include "reel_sound_asset.h"
44 #include "reel_subtitle_asset.h"
45 #include "reel_markers_asset.h"
46 #include "decrypted_kdm_key.h"
47 #include "decrypted_kdm.h"
48 #include "interop_subtitle_asset.h"
49 #include "smpte_subtitle_asset.h"
50 #include "reel_atmos_asset.h"
51 #include "reel_closed_caption_asset.h"
52 #include <libxml++/nodes/element.h>
53 #include <boost/foreach.hpp>
54 #include <stdint.h>
55
56 /* Centos 6 does not have this */
57 #ifndef INT64_MAX
58 #define INT64_MAX 0x7fffffffffffffff
59 #endif
60
61 using std::string;
62 using std::list;
63 using std::cout;
64 using std::min;
65 using std::make_shared;
66 using std::shared_ptr;
67 using std::dynamic_pointer_cast;
68 using namespace dcp;
69
70 Reel::Reel (std::shared_ptr<const cxml::Node> node)
71         : Object (remove_urn_uuid (node->string_child ("Id")))
72 {
73         auto asset_list = node->node_child ("AssetList");
74
75         auto main_picture = asset_list->optional_node_child ("MainPicture");
76         if (main_picture) {
77                 _main_picture.reset (new ReelMonoPictureAsset (main_picture));
78         }
79
80         auto main_stereoscopic_picture = asset_list->optional_node_child ("MainStereoscopicPicture");
81         if (main_stereoscopic_picture) {
82                 _main_picture.reset (new ReelStereoPictureAsset (main_stereoscopic_picture));
83         }
84
85         auto main_sound = asset_list->optional_node_child ("MainSound");
86         if (main_sound) {
87                 _main_sound.reset (new ReelSoundAsset (main_sound));
88         }
89
90         auto main_subtitle = asset_list->optional_node_child ("MainSubtitle");
91         if (main_subtitle) {
92                 _main_subtitle.reset (new ReelSubtitleAsset (main_subtitle));
93         }
94
95         auto main_markers = asset_list->optional_node_child ("MainMarkers");
96         if (main_markers) {
97                 _main_markers.reset (new ReelMarkersAsset (main_markers));
98         }
99
100         /* XXX: it's not ideal that we silently tolerate Interop or SMPTE nodes here */
101         /* XXX: not sure if Interop supports multiple closed captions */
102         auto closed_captions = asset_list->node_children ("MainClosedCaption");
103         if (closed_captions.empty()) {
104                 closed_captions = asset_list->node_children ("ClosedCaption");
105         }
106         for (auto i: closed_captions) {
107                 _closed_captions.push_back (make_shared<ReelClosedCaptionAsset>(i));
108         }
109
110         auto atmos = asset_list->optional_node_child ("AuxData");
111         if (atmos) {
112                 _atmos = make_shared<ReelAtmosAsset>(atmos);
113         }
114
115         node->ignore_child ("AnnotationText");
116         node->done ();
117 }
118
119 xmlpp::Element *
120 Reel::write_to_cpl (xmlpp::Element* node, Standard standard) const
121 {
122         auto reel = node->add_child ("Reel");
123         reel->add_child("Id")->add_child_text("urn:uuid:" + _id);
124         xmlpp::Element* asset_list = reel->add_child ("AssetList");
125
126         if (_main_markers) {
127                 _main_markers->write_to_cpl (asset_list, standard);
128         }
129
130         if (_main_picture && dynamic_pointer_cast<ReelMonoPictureAsset> (_main_picture)) {
131                 /* Mono pictures come before other stuff... */
132                 _main_picture->write_to_cpl (asset_list, standard);
133         }
134
135         if (_main_sound) {
136                 _main_sound->write_to_cpl (asset_list, standard);
137         }
138
139         if (_main_subtitle) {
140                 _main_subtitle->write_to_cpl (asset_list, standard);
141         }
142
143         for (auto i: _closed_captions) {
144                 i->write_to_cpl (asset_list, standard);
145         }
146
147         if (_main_picture && dynamic_pointer_cast<ReelStereoPictureAsset> (_main_picture)) {
148                 /* ... but stereo pictures must come after */
149                 _main_picture->write_to_cpl (asset_list, standard);
150         }
151
152         if (_atmos) {
153                 _atmos->write_to_cpl (asset_list, standard);
154         }
155
156         return asset_list;
157 }
158
159 bool
160 Reel::equals (std::shared_ptr<const Reel> other, EqualityOptions opt, NoteHandler note) const
161 {
162         if ((_main_picture && !other->_main_picture) || (!_main_picture && other->_main_picture)) {
163                 note (DCP_ERROR, "Reel: picture assets differ");
164                 return false;
165         }
166
167         if (_main_picture && !_main_picture->equals (other->_main_picture, opt, note)) {
168                 return false;
169         }
170
171         if ((_main_sound && !other->_main_sound) || (!_main_sound && other->_main_sound)) {
172                 note (DCP_ERROR, "Reel: sound assets differ");
173                 return false;
174         }
175
176         if (_main_sound && !_main_sound->equals (other->_main_sound, opt, note)) {
177                 return false;
178         }
179
180         if ((_main_subtitle && !other->_main_subtitle) || (!_main_subtitle && other->_main_subtitle)) {
181                 note (DCP_ERROR, "Reel: subtitle assets differ");
182                 return false;
183         }
184
185         if (_main_subtitle && !_main_subtitle->equals (other->_main_subtitle, opt, note)) {
186                 return false;
187         }
188
189         if ((_main_markers && !other->_main_markers) || (!_main_markers && other->_main_markers)) {
190                 note (DCP_ERROR, "Reel: one has markers and the other does not");
191                 return false;
192         }
193
194         if (_main_markers && !_main_markers->equals(other->_main_markers, opt, note)) {
195                 note (DCP_ERROR, "Reel: marker assets differ");
196                 return false;
197         }
198
199         if (_closed_captions.size() != other->_closed_captions.size()) {
200                 return false;
201         }
202
203         auto i = _closed_captions.begin();
204         auto j = other->_closed_captions.begin();
205         while (i != _closed_captions.end()) {
206                 if (!(*i)->equals(*j, opt, note)) {
207                         return false;
208                 }
209                 ++i;
210                 ++j;
211         }
212
213         if ((_atmos && !other->_atmos) || (!_atmos && other->_atmos)) {
214                 note (DCP_ERROR, "Reel: atmos assets differ");
215                 return false;
216         }
217
218         if (_atmos && !_atmos->equals (other->_atmos, opt, note)) {
219                 return false;
220         }
221
222         return true;
223 }
224
225 bool
226 Reel::encrypted () const
227 {
228         auto ecc = false;
229         for (auto i: _closed_captions) {
230                 if (i->encrypted()) {
231                         ecc = true;
232                 }
233         }
234
235         return (
236                 (_main_picture && _main_picture->encrypted ()) ||
237                 (_main_sound && _main_sound->encrypted ()) ||
238                 (_main_subtitle && _main_subtitle->encrypted ()) ||
239                 ecc ||
240                 (_atmos && _atmos->encrypted ())
241                 );
242 }
243
244 void
245 Reel::add (DecryptedKDM const & kdm)
246 {
247         auto keys = kdm.keys ();
248
249         for (auto const& i: keys) {
250                 if (_main_picture && i.id() == _main_picture->key_id()) {
251                         _main_picture->asset()->set_key (i.key());
252                 }
253                 if (_main_sound && i.id() == _main_sound->key_id()) {
254                         _main_sound->asset()->set_key (i.key());
255                 }
256                 if (_main_subtitle && i.id() == _main_subtitle->key_id()) {
257                         shared_ptr<SMPTESubtitleAsset> s = dynamic_pointer_cast<SMPTESubtitleAsset> (_main_subtitle->asset());
258                         if (s) {
259                                 s->set_key (i.key());
260                         }
261                 }
262                 for (auto j: _closed_captions) {
263                         if (i.id() == j->key_id()) {
264                                 auto s = dynamic_pointer_cast<SMPTESubtitleAsset> (j->asset());
265                                 if (s) {
266                                         s->set_key (i.key());
267                                 }
268                         }
269                 }
270                 if (_atmos && i.id() == _atmos->key_id()) {
271                         _atmos->asset()->set_key (i.key());
272                 }
273         }
274 }
275
276 void
277 Reel::add (shared_ptr<ReelAsset> asset)
278 {
279         auto p = dynamic_pointer_cast<ReelPictureAsset> (asset);
280         auto so = dynamic_pointer_cast<ReelSoundAsset> (asset);
281         auto su = dynamic_pointer_cast<ReelSubtitleAsset> (asset);
282         auto m = dynamic_pointer_cast<ReelMarkersAsset> (asset);
283         auto c = dynamic_pointer_cast<ReelClosedCaptionAsset> (asset);
284         auto a = dynamic_pointer_cast<ReelAtmosAsset> (asset);
285         if (p) {
286                 _main_picture = p;
287         } else if (so) {
288                 _main_sound = so;
289         } else if (su) {
290                 _main_subtitle = su;
291         } else if (m) {
292                 _main_markers = m;
293         } else if (c) {
294                 _closed_captions.push_back (c);
295         } else if (a) {
296                 _atmos = a;
297         }
298 }
299
300 list<shared_ptr<ReelAsset>>
301 Reel::assets () const
302 {
303         list<shared_ptr<ReelAsset>> a;
304         if (_main_picture) {
305                 a.push_back (_main_picture);
306         }
307         if (_main_sound) {
308                 a.push_back (_main_sound);
309         }
310         if (_main_subtitle) {
311                 a.push_back (_main_subtitle);
312         }
313         std::copy (_closed_captions.begin(), _closed_captions.end(), back_inserter(a));
314         if (_atmos) {
315                 a.push_back (_atmos);
316         }
317         return a;
318 }
319
320 void
321 Reel::resolve_refs (list<shared_ptr<Asset>> assets)
322 {
323         if (_main_picture) {
324                 _main_picture->asset_ref().resolve (assets);
325         }
326
327         if (_main_sound) {
328                 _main_sound->asset_ref().resolve (assets);
329         }
330
331         if (_main_subtitle) {
332                 _main_subtitle->asset_ref().resolve (assets);
333
334                 /* Interop subtitle handling is all special cases */
335                 if (_main_subtitle->asset_ref().resolved()) {
336                         auto iop = dynamic_pointer_cast<InteropSubtitleAsset> (_main_subtitle->asset_ref().asset());
337                         if (iop) {
338                                 iop->resolve_fonts (assets);
339                         }
340                 }
341         }
342
343         for (auto i: _closed_captions) {
344                 i->asset_ref().resolve(assets);
345
346                 /* Interop subtitle handling is all special cases */
347                 if (i->asset_ref().resolved()) {
348                         auto iop = dynamic_pointer_cast<InteropSubtitleAsset> (i->asset_ref().asset());
349                         if (iop) {
350                                 iop->resolve_fonts (assets);
351                         }
352                 }
353         }
354
355         if (_atmos) {
356                 _atmos->asset_ref().resolve (assets);
357         }
358 }
359
360 int64_t
361 Reel::duration () const
362 {
363         if (_main_picture) {
364                 return _main_picture->actual_duration();
365         }
366
367         int64_t d = INT64_MAX;
368
369         if (_main_sound) {
370                 d = min (d, _main_sound->actual_duration());
371         }
372         if (_main_subtitle) {
373                 d = min (d, _main_subtitle->actual_duration());
374         }
375         if (_main_markers) {
376                 d = min (d, _main_markers->actual_duration());
377         }
378         for (auto i: _closed_captions) {
379                 d = min (d, i->actual_duration());
380         }
381         if (_atmos) {
382                 d = min (d, _atmos->actual_duration());
383         }
384
385         DCP_ASSERT (d < INT64_MAX);
386
387         return d;
388 }