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