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