Store and allow access to the raw XML that is read in from
[libdcp.git] / src / reel.cc
1 /*
2     Copyright (C) 2014-2017 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 void
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:" + make_uuid());
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
156 bool
157 Reel::equals (boost::shared_ptr<const Reel> other, EqualityOptions opt, NoteHandler note) const
158 {
159         if ((_main_picture && !other->_main_picture) || (!_main_picture && other->_main_picture)) {
160                 note (DCP_ERROR, "Reel: picture assets differ");
161                 return false;
162         }
163
164         if (_main_picture && !_main_picture->equals (other->_main_picture, opt, note)) {
165                 return false;
166         }
167
168         if ((_main_sound && !other->_main_sound) || (!_main_sound && other->_main_sound)) {
169                 note (DCP_ERROR, "Reel: sound assets differ");
170                 return false;
171         }
172
173         if (_main_sound && !_main_sound->equals (other->_main_sound, opt, note)) {
174                 return false;
175         }
176
177         if ((_main_subtitle && !other->_main_subtitle) || (!_main_subtitle && other->_main_subtitle)) {
178                 note (DCP_ERROR, "Reel: subtitle assets differ");
179                 return false;
180         }
181
182         if (_main_subtitle && !_main_subtitle->equals (other->_main_subtitle, opt, note)) {
183                 return false;
184         }
185
186         if (_main_markers && !_main_markers->equals (other->_main_markers, opt, note)) {
187                 return false;
188         }
189
190         if (_closed_captions.size() != other->_closed_captions.size()) {
191                 return false;
192         }
193
194         list<shared_ptr<ReelClosedCaptionAsset> >::const_iterator i = _closed_captions.begin();
195         list<shared_ptr<ReelClosedCaptionAsset> >::const_iterator j = other->_closed_captions.begin();
196         while (i != _closed_captions.end()) {
197                 if (!(*i)->equals(*j, opt, note)) {
198                         return false;
199                 }
200                 ++i;
201                 ++j;
202         }
203
204         if ((_atmos && !other->_atmos) || (!_atmos && other->_atmos)) {
205                 note (DCP_ERROR, "Reel: atmos assets differ");
206                 return false;
207         }
208
209         if (_atmos && !_atmos->equals (other->_atmos, opt, note)) {
210                 return false;
211         }
212
213         return true;
214 }
215
216 bool
217 Reel::encrypted () const
218 {
219         bool ecc = false;
220         BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> i, _closed_captions) {
221                 if (i->encrypted()) {
222                         ecc = true;
223                 }
224         }
225
226         return (
227                 (_main_picture && _main_picture->encrypted ()) ||
228                 (_main_sound && _main_sound->encrypted ()) ||
229                 (_main_subtitle && _main_subtitle->encrypted ()) ||
230                 ecc ||
231                 (_atmos && _atmos->encrypted ())
232                 );
233 }
234
235 void
236 Reel::add (DecryptedKDM const & kdm)
237 {
238         list<DecryptedKDMKey> keys = kdm.keys ();
239
240         for (list<DecryptedKDMKey>::iterator i = keys.begin(); i != keys.end(); ++i) {
241                 if (_main_picture && i->id() == _main_picture->key_id()) {
242                         _main_picture->asset()->set_key (i->key ());
243                 }
244                 if (_main_sound && i->id() == _main_sound->key_id()) {
245                         _main_sound->asset()->set_key (i->key ());
246                 }
247                 if (_main_subtitle && i->id() == _main_subtitle->key_id()) {
248                         shared_ptr<SMPTESubtitleAsset> s = dynamic_pointer_cast<SMPTESubtitleAsset> (_main_subtitle->asset());
249                         if (s) {
250                                 s->set_key (i->key ());
251                         }
252                 }
253                 BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> j, _closed_captions) {
254                         if (i->id() == j->key_id()) {
255                                 shared_ptr<SMPTESubtitleAsset> s = dynamic_pointer_cast<SMPTESubtitleAsset> (j->asset());
256                                 if (s) {
257                                         s->set_key (i->key ());
258                                 }
259                         }
260                 }
261                 if (_atmos && i->id() == _atmos->key_id()) {
262                         _atmos->asset()->set_key (i->key ());
263                 }
264         }
265 }
266
267 void
268 Reel::add (shared_ptr<ReelAsset> asset)
269 {
270         shared_ptr<ReelPictureAsset> p = dynamic_pointer_cast<ReelPictureAsset> (asset);
271         shared_ptr<ReelSoundAsset> so = dynamic_pointer_cast<ReelSoundAsset> (asset);
272         shared_ptr<ReelSubtitleAsset> su = dynamic_pointer_cast<ReelSubtitleAsset> (asset);
273         shared_ptr<ReelMarkersAsset> m = dynamic_pointer_cast<ReelMarkersAsset> (asset);
274         shared_ptr<ReelClosedCaptionAsset> c = dynamic_pointer_cast<ReelClosedCaptionAsset> (asset);
275         shared_ptr<ReelAtmosAsset> a = dynamic_pointer_cast<ReelAtmosAsset> (asset);
276         if (p) {
277                 _main_picture = p;
278         } else if (so) {
279                 _main_sound = so;
280         } else if (su) {
281                 _main_subtitle = su;
282         } else if (m) {
283                 _main_markers = m;
284         } else if (c) {
285                 _closed_captions.push_back (c);
286         } else if (a) {
287                 _atmos = a;
288         }
289 }
290
291 list<shared_ptr<ReelAsset> >
292 Reel::assets () const
293 {
294         list<shared_ptr<ReelAsset> > a;
295         if (_main_picture) {
296                 a.push_back (_main_picture);
297         }
298         if (_main_sound) {
299                 a.push_back (_main_sound);
300         }
301         if (_main_subtitle) {
302                 a.push_back (_main_subtitle);
303         }
304         std::copy (_closed_captions.begin(), _closed_captions.end(), back_inserter(a));
305         if (_atmos) {
306                 a.push_back (_atmos);
307         }
308         return a;
309 }
310
311 void
312 Reel::resolve_refs (list<shared_ptr<Asset> > assets)
313 {
314         if (_main_picture) {
315                 _main_picture->asset_ref().resolve (assets);
316         }
317
318         if (_main_sound) {
319                 _main_sound->asset_ref().resolve (assets);
320         }
321
322         if (_main_subtitle) {
323                 _main_subtitle->asset_ref().resolve (assets);
324
325                 /* Interop subtitle handling is all special cases */
326                 if (_main_subtitle->asset_ref().resolved()) {
327                         shared_ptr<InteropSubtitleAsset> iop = dynamic_pointer_cast<InteropSubtitleAsset> (_main_subtitle->asset_ref().asset());
328                         if (iop) {
329                                 iop->resolve_fonts (assets);
330                         }
331                 }
332         }
333
334         BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> i, _closed_captions) {
335                 i->asset_ref().resolve(assets);
336
337                 /* Interop subtitle handling is all special cases */
338                 if (i->asset_ref().resolved()) {
339                         shared_ptr<InteropSubtitleAsset> iop = dynamic_pointer_cast<InteropSubtitleAsset> (i->asset_ref().asset());
340                         if (iop) {
341                                 iop->resolve_fonts (assets);
342                         }
343                 }
344         }
345
346         if (_atmos) {
347                 _atmos->asset_ref().resolve (assets);
348         }
349 }
350
351 int64_t
352 Reel::duration () const
353 {
354         if (_main_picture) {
355                 return _main_picture->actual_duration();
356         }
357
358         int64_t d = INT64_MAX;
359
360         if (_main_sound) {
361                 d = min (d, _main_sound->actual_duration());
362         }
363         if (_main_subtitle) {
364                 d = min (d, _main_subtitle->actual_duration());
365         }
366         if (_main_markers) {
367                 d = min (d, _main_markers->actual_duration());
368         }
369         BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> i, _closed_captions) {
370                 d = min (d, i->actual_duration());
371         }
372         if (_atmos) {
373                 d = min (d, _atmos->actual_duration());
374         }
375
376         DCP_ASSERT (d < INT64_MAX);
377
378         return d;
379 }