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