ac442333f2f44ac673accdfdefe931a866167f02
[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         : _issuer ("libdcp" LIBDCP_VERSION)
68         , _creator ("libdcp" LIBDCP_VERSION)
69         , _issue_date (LocalTime().as_string())
70         , _annotation_text (annotation_text)
71         , _content_title_text (annotation_text)
72         , _content_kind (content_kind)
73 {
74         /* default _content_version_id to a random ID and _content_version_label to
75            a random ID and the current time.
76         */
77         string const uuid = make_uuid();
78         _content_version.id = "urn:uuid:" + uuid;
79         _content_version.label_text = uuid + LocalTime().as_string ();
80 }
81
82 /** Construct a CPL object from a XML file */
83 CPL::CPL (boost::filesystem::path file)
84         : Asset (file)
85         , _content_kind (FEATURE)
86 {
87         cxml::Document f ("CompositionPlaylist");
88         f.read_file (file);
89
90         if (f.namespace_uri() == cpl_interop_ns) {
91                 _standard = INTEROP;
92         } else if (f.namespace_uri() == cpl_smpte_ns) {
93                 _standard = SMPTE;
94         } else {
95                 boost::throw_exception (XMLError ("Unrecognised CPL namespace " + f.namespace_uri()));
96         }
97
98         _id = remove_urn_uuid (f.string_child ("Id"));
99         _annotation_text = f.optional_string_child("AnnotationText").get_value_or("");
100         _issuer = f.optional_string_child("Issuer").get_value_or("");
101         _creator = f.optional_string_child("Creator").get_value_or("");
102         _issue_date = f.string_child ("IssueDate");
103         _content_title_text = f.string_child ("ContentTitleText");
104         _content_kind = content_kind_from_string (f.string_child ("ContentKind"));
105         shared_ptr<cxml::Node> content_version = f.optional_node_child ("ContentVersion");
106         if (content_version) {
107                 _content_version.id = content_version->optional_string_child("Id").get_value_or("");
108                 _content_version.label_text = content_version->string_child("LabelText");
109                 content_version->done ();
110         } else if (_standard == SMPTE) {
111                 /* ContentVersion is required in SMPTE */
112                 throw XMLError ("Missing ContentVersion tag in CPL");
113         }
114         cxml::ConstNodePtr rating_list = f.node_child ("RatingList");
115         if (rating_list) {
116                 BOOST_FOREACH (cxml::ConstNodePtr i, rating_list->node_children("Rating")) {
117                         _ratings.push_back (Rating(i));
118                 }
119         }
120         _reels = type_grand_children<Reel> (f, "ReelList", "Reel");
121
122         f.ignore_child ("Issuer");
123         f.ignore_child ("Signer");
124         f.ignore_child ("Signature");
125
126         f.done ();
127 }
128
129 /** Add a reel to this CPL.
130  *  @param reel Reel to add.
131  */
132 void
133 CPL::add (boost::shared_ptr<Reel> reel)
134 {
135         _reels.push_back (reel);
136 }
137
138 /** Write an CompositonPlaylist XML file.
139  *  @param file Filename to write.
140  *  @param standard INTEROP or SMPTE.
141  *  @param signer Signer to sign the CPL, or 0 to add no signature.
142  */
143 void
144 CPL::write_xml (boost::filesystem::path file, Standard standard, shared_ptr<const CertificateChain> signer) const
145 {
146         xmlpp::Document doc;
147         xmlpp::Element* root;
148         if (standard == INTEROP) {
149                 root = doc.create_root_node ("CompositionPlaylist", cpl_interop_ns);
150         } else {
151                 root = doc.create_root_node ("CompositionPlaylist", cpl_smpte_ns);
152         }
153
154         root->add_child("Id")->add_child_text ("urn:uuid:" + _id);
155         root->add_child("AnnotationText")->add_child_text (_annotation_text);
156         root->add_child("IssueDate")->add_child_text (_issue_date);
157         root->add_child("Issuer")->add_child_text (_issuer);
158         root->add_child("Creator")->add_child_text (_creator);
159         root->add_child("ContentTitleText")->add_child_text (_content_title_text);
160         root->add_child("ContentKind")->add_child_text (content_kind_to_string (_content_kind));
161         _content_version.as_xml (root);
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 (_annotation_text != other_cpl->_annotation_text && !opt.cpl_annotation_texts_can_differ) {
246                 string const s = "CPL: annotation texts differ: " + _annotation_text + " vs " + other_cpl->_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 }