Fix bad implementation of bad hack.
[libdcp.git] / src / cpl.cc
1 /*
2     Copyright (C) 2012-2018 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 "cpl.h"
35 #include "util.h"
36 #include "reel.h"
37 #include "metadata.h"
38 #include "certificate_chain.h"
39 #include "xml.h"
40 #include "reel_picture_asset.h"
41 #include "reel_sound_asset.h"
42 #include "reel_subtitle_asset.h"
43 #include "reel_closed_caption_asset.h"
44 #include "reel_atmos_asset.h"
45 #include "local_time.h"
46 #include "dcp_assert.h"
47 #include "compose.hpp"
48 #include <libxml/parser.h>
49 #include <libxml++/libxml++.h>
50 #include <boost/foreach.hpp>
51
52 using std::string;
53 using std::list;
54 using std::pair;
55 using std::make_pair;
56 using std::cout;
57 using boost::shared_ptr;
58 using boost::optional;
59 using boost::dynamic_pointer_cast;
60 using namespace dcp;
61
62 static string const cpl_interop_ns = "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#";
63 static string const cpl_smpte_ns   = "http://www.smpte-ra.org/schemas/429-7/2006/CPL";
64
65 CPL::CPL (string annotation_text, ContentKind content_kind)
66         /* default _content_title_text to annotation_text */
67         : _content_title_text (annotation_text)
68         , _content_kind (content_kind)
69 {
70         _metadata.annotation_text = annotation_text;
71         /* default _content_version_id to a random ID and _content_version_label to
72            a random ID and the current time.
73         */
74         string const uuid = make_uuid();
75         _content_version_id = "urn:uuid:" + uuid;
76         _content_version_label_text = uuid + LocalTime().as_string ();
77 }
78
79 /** Construct a CPL object from a XML file */
80 CPL::CPL (boost::filesystem::path file)
81         : Asset (file)
82         , _content_kind (FEATURE)
83 {
84         cxml::Document f ("CompositionPlaylist");
85         f.read_file (file);
86
87         if (f.namespace_uri() == cpl_interop_ns) {
88                 _standard = INTEROP;
89         } else if (f.namespace_uri() == cpl_smpte_ns) {
90                 _standard = SMPTE;
91         } else {
92                 boost::throw_exception (XMLError ("Unrecognised CPL namespace " + f.namespace_uri()));
93         }
94
95         _id = remove_urn_uuid (f.string_child ("Id"));
96         _metadata.annotation_text = f.optional_string_child ("AnnotationText").get_value_or ("");
97         _metadata.issuer = f.optional_string_child ("Issuer").get_value_or ("");
98         _metadata.creator = f.optional_string_child ("Creator").get_value_or ("");
99         _metadata.issue_date = f.string_child ("IssueDate");
100         _content_title_text = f.string_child ("ContentTitleText");
101         _content_kind = content_kind_from_string (f.string_child ("ContentKind"));
102         shared_ptr<cxml::Node> content_version = f.optional_node_child ("ContentVersion");
103         if (content_version) {
104                 _content_version_id = content_version->optional_string_child ("Id").get_value_or ("");
105                 _content_version_label_text = content_version->string_child ("LabelText");
106                 content_version->done ();
107         } else if (_standard == SMPTE) {
108                 /* ContentVersion is required in SMPTE */
109                 throw XMLError ("Missing ContentVersion tag in CPL");
110         }
111         cxml::ConstNodePtr rating_list = f.node_child ("RatingList");
112         if (rating_list) {
113                 BOOST_FOREACH (cxml::ConstNodePtr i, rating_list->node_children("Rating")) {
114                         _ratings.push_back (Rating(i));
115                 }
116         }
117         _reels = type_grand_children<Reel> (f, "ReelList", "Reel");
118
119         f.ignore_child ("Issuer");
120         f.ignore_child ("Signer");
121         f.ignore_child ("Signature");
122
123         f.done ();
124 }
125
126 /** Add a reel to this CPL.
127  *  @param reel Reel to add.
128  */
129 void
130 CPL::add (boost::shared_ptr<Reel> reel)
131 {
132         _reels.push_back (reel);
133 }
134
135 /** Write an CompositonPlaylist XML file.
136  *  @param file Filename to write.
137  *  @param standard INTEROP or SMPTE.
138  *  @param signer Signer to sign the CPL, or 0 to add no signature.
139  */
140 void
141 CPL::write_xml (boost::filesystem::path file, Standard standard, shared_ptr<const CertificateChain> signer) const
142 {
143         xmlpp::Document doc;
144         xmlpp::Element* root;
145         if (standard == INTEROP) {
146                 root = doc.create_root_node ("CompositionPlaylist", cpl_interop_ns);
147         } else {
148                 root = doc.create_root_node ("CompositionPlaylist", cpl_smpte_ns);
149         }
150
151         root->add_child("Id")->add_child_text ("urn:uuid:" + _id);
152         root->add_child("AnnotationText")->add_child_text (_metadata.annotation_text);
153         root->add_child("IssueDate")->add_child_text (_metadata.issue_date);
154         root->add_child("Issuer")->add_child_text (_metadata.issuer);
155         root->add_child("Creator")->add_child_text (_metadata.creator);
156         root->add_child("ContentTitleText")->add_child_text (_content_title_text);
157         root->add_child("ContentKind")->add_child_text (content_kind_to_string (_content_kind));
158         {
159                 xmlpp::Node* cv = root->add_child ("ContentVersion");
160                 cv->add_child ("Id")->add_child_text (_content_version_id);
161                 cv->add_child ("LabelText")->add_child_text (_content_version_label_text);
162         }
163         xmlpp::Element* rating_list = root->add_child("RatingList");
164         BOOST_FOREACH (Rating i, _ratings) {
165                 i.as_xml (rating_list->add_child("Rating"));
166         }
167
168         xmlpp::Element* reel_list = root->add_child ("ReelList");
169
170         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
171                 i->write_to_cpl (reel_list, standard);
172         }
173
174         indent (root, 0);
175
176         if (signer) {
177                 signer->sign (root, standard);
178         }
179
180         doc.write_to_file_formatted (file.string(), "UTF-8");
181
182         set_file (file);
183 }
184
185 list<shared_ptr<ReelMXF> >
186 CPL::reel_mxfs ()
187 {
188         list<shared_ptr<ReelMXF> > c;
189
190         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
191                 if (i->main_picture ()) {
192                         c.push_back (i->main_picture());
193                 }
194                 if (i->main_sound ()) {
195                         c.push_back (i->main_sound());
196                 }
197                 if (i->main_subtitle ()) {
198                         c.push_back (i->main_subtitle());
199                 }
200                 BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> j, i->closed_captions()) {
201                         c.push_back (j);
202                 }
203                 if (i->atmos ()) {
204                         c.push_back (i->atmos());
205                 }
206         }
207
208         return c;
209 }
210
211 list<shared_ptr<const ReelMXF> >
212 CPL::reel_mxfs () const
213 {
214         list<shared_ptr<const ReelMXF> > c;
215
216         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
217                 if (i->main_picture ()) {
218                         c.push_back (i->main_picture());
219                 }
220                 if (i->main_sound ()) {
221                         c.push_back (i->main_sound());
222                 }
223                 if (i->main_subtitle ()) {
224                         c.push_back (i->main_subtitle());
225                 }
226                 BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> j, i->closed_captions()) {
227                         c.push_back (j);
228                 }
229                 if (i->atmos ()) {
230                         c.push_back (i->atmos());
231                 }
232         }
233
234         return c;
235 }
236
237 bool
238 CPL::equals (shared_ptr<const Asset> other, EqualityOptions opt, NoteHandler note) const
239 {
240         shared_ptr<const CPL> other_cpl = dynamic_pointer_cast<const CPL> (other);
241         if (!other_cpl) {
242                 return false;
243         }
244
245         if (_metadata.annotation_text != other_cpl->_metadata.annotation_text && !opt.cpl_annotation_texts_can_differ) {
246                 string const s = "CPL: annotation texts differ: " + _metadata.annotation_text + " vs " + other_cpl->_metadata.annotation_text + "\n";
247                 note (DCP_ERROR, s);
248                 return false;
249         }
250
251         if (_content_kind != other_cpl->_content_kind) {
252                 note (DCP_ERROR, "CPL: content kinds differ");
253                 return false;
254         }
255
256         if (_reels.size() != other_cpl->_reels.size()) {
257                 note (DCP_ERROR, String::compose ("CPL: reel counts differ (%1 vs %2)", _reels.size(), other_cpl->_reels.size()));
258                 return false;
259         }
260
261         list<shared_ptr<Reel> >::const_iterator a = _reels.begin ();
262         list<shared_ptr<Reel> >::const_iterator b = other_cpl->_reels.begin ();
263
264         while (a != _reels.end ()) {
265                 if (!(*a)->equals (*b, opt, note)) {
266                         return false;
267                 }
268                 ++a;
269                 ++b;
270         }
271
272         return true;
273 }
274
275 /** @return true if we have any encrypted content */
276 bool
277 CPL::encrypted () const
278 {
279         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
280                 if (i->encrypted ()) {
281                         return true;
282                 }
283         }
284
285         return false;
286 }
287
288 /** Add a KDM to this CPL.  If the KDM is for any of this CPLs assets it will be used
289  *  to decrypt those assets.
290  *  @param kdm KDM.
291  */
292 void
293 CPL::add (DecryptedKDM const & kdm)
294 {
295         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
296                 i->add (kdm);
297         }
298 }
299
300 void
301 CPL::resolve_refs (list<shared_ptr<Asset> > assets)
302 {
303         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
304                 i->resolve_refs (assets);
305         }
306 }
307
308 string
309 CPL::pkl_type (Standard standard) const
310 {
311         return static_pkl_type (standard);
312 }
313
314 string
315 CPL::static_pkl_type (Standard standard)
316 {
317         switch (standard) {
318         case INTEROP:
319                 return "text/xml;asdcpKind=CPL";
320         case SMPTE:
321                 return "text/xml";
322         default:
323                 DCP_ASSERT (false);
324         }
325 }
326
327 int64_t
328 CPL::duration () const
329 {
330         int64_t d = 0;
331         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
332                 d += i->duration ();
333         }
334         return d;
335 }