Add some new channels to the enum.
[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 std::vector;
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         /* default _content_title_text to annotation_text */
68         : _issuer ("libdcp" LIBDCP_VERSION)
69         , _creator ("libdcp" LIBDCP_VERSION)
70         , _issue_date (LocalTime().as_string())
71         , _annotation_text (annotation_text)
72         , _content_title_text (annotation_text)
73         , _content_kind (content_kind)
74 {
75         ContentVersion cv;
76         cv.label_text = cv.id + LocalTime().as_string();
77         _content_versions.push_back (cv);
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         _issuer = f.optional_string_child("Issuer").get_value_or("");
99         _creator = f.optional_string_child("Creator").get_value_or("");
100         _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                 /* XXX: SMPTE should insist that Id is present */
106                 _content_versions.push_back (
107                         ContentVersion (
108                                 content_version->optional_string_child("Id").get_value_or(""),
109                                 content_version->string_child("LabelText")
110                                 )
111                         );
112                 content_version->done ();
113         } else if (_standard == SMPTE) {
114                 /* ContentVersion is required in SMPTE */
115                 throw XMLError ("Missing ContentVersion tag in CPL");
116         }
117         cxml::ConstNodePtr rating_list = f.node_child ("RatingList");
118         if (rating_list) {
119                 BOOST_FOREACH (cxml::ConstNodePtr i, rating_list->node_children("Rating")) {
120                         _ratings.push_back (Rating(i));
121                 }
122         }
123         _reels = type_grand_children<Reel> (f, "ReelList", "Reel");
124
125         f.ignore_child ("Issuer");
126         f.ignore_child ("Signer");
127         f.ignore_child ("Signature");
128
129         f.done ();
130 }
131
132 /** Add a reel to this CPL.
133  *  @param reel Reel to add.
134  */
135 void
136 CPL::add (boost::shared_ptr<Reel> reel)
137 {
138         _reels.push_back (reel);
139 }
140
141 /** Write an CompositonPlaylist XML file.
142  *  @param file Filename to write.
143  *  @param standard INTEROP or SMPTE.
144  *  @param signer Signer to sign the CPL, or 0 to add no signature.
145  */
146 void
147 CPL::write_xml (boost::filesystem::path file, Standard standard, shared_ptr<const CertificateChain> signer) const
148 {
149         xmlpp::Document doc;
150         xmlpp::Element* root;
151         if (standard == INTEROP) {
152                 root = doc.create_root_node ("CompositionPlaylist", cpl_interop_ns);
153         } else {
154                 root = doc.create_root_node ("CompositionPlaylist", cpl_smpte_ns);
155         }
156
157         root->add_child("Id")->add_child_text ("urn:uuid:" + _id);
158         root->add_child("AnnotationText")->add_child_text (_annotation_text);
159         root->add_child("IssueDate")->add_child_text (_issue_date);
160         root->add_child("Issuer")->add_child_text (_issuer);
161         root->add_child("Creator")->add_child_text (_creator);
162         root->add_child("ContentTitleText")->add_child_text (_content_title_text);
163         root->add_child("ContentKind")->add_child_text (content_kind_to_string (_content_kind));
164         DCP_ASSERT (!_content_versions.empty());
165         _content_versions[0].as_xml (root);
166
167         xmlpp::Element* rating_list = root->add_child("RatingList");
168         BOOST_FOREACH (Rating i, _ratings) {
169                 i.as_xml (rating_list->add_child("Rating"));
170         }
171
172         xmlpp::Element* reel_list = root->add_child ("ReelList");
173
174         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
175                 i->write_to_cpl (reel_list, standard);
176         }
177
178         indent (root, 0);
179
180         if (signer) {
181                 signer->sign (root, standard);
182         }
183
184         doc.write_to_file_formatted (file.string(), "UTF-8");
185
186         set_file (file);
187 }
188
189 list<shared_ptr<ReelMXF> >
190 CPL::reel_mxfs ()
191 {
192         list<shared_ptr<ReelMXF> > c;
193
194         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
195                 if (i->main_picture ()) {
196                         c.push_back (i->main_picture());
197                 }
198                 if (i->main_sound ()) {
199                         c.push_back (i->main_sound());
200                 }
201                 if (i->main_subtitle ()) {
202                         c.push_back (i->main_subtitle());
203                 }
204                 BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> j, i->closed_captions()) {
205                         c.push_back (j);
206                 }
207                 if (i->atmos ()) {
208                         c.push_back (i->atmos());
209                 }
210         }
211
212         return c;
213 }
214
215 list<shared_ptr<const ReelMXF> >
216 CPL::reel_mxfs () const
217 {
218         list<shared_ptr<const ReelMXF> > c;
219
220         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
221                 if (i->main_picture ()) {
222                         c.push_back (i->main_picture());
223                 }
224                 if (i->main_sound ()) {
225                         c.push_back (i->main_sound());
226                 }
227                 if (i->main_subtitle ()) {
228                         c.push_back (i->main_subtitle());
229                 }
230                 BOOST_FOREACH (shared_ptr<ReelClosedCaptionAsset> j, i->closed_captions()) {
231                         c.push_back (j);
232                 }
233                 if (i->atmos ()) {
234                         c.push_back (i->atmos());
235                 }
236         }
237
238         return c;
239 }
240
241 bool
242 CPL::equals (shared_ptr<const Asset> other, EqualityOptions opt, NoteHandler note) const
243 {
244         shared_ptr<const CPL> other_cpl = dynamic_pointer_cast<const CPL> (other);
245         if (!other_cpl) {
246                 return false;
247         }
248
249         if (_annotation_text != other_cpl->_annotation_text && !opt.cpl_annotation_texts_can_differ) {
250                 string const s = "CPL: annotation texts differ: " + _annotation_text + " vs " + other_cpl->_annotation_text + "\n";
251                 note (DCP_ERROR, s);
252                 return false;
253         }
254
255         if (_content_kind != other_cpl->_content_kind) {
256                 note (DCP_ERROR, "CPL: content kinds differ");
257                 return false;
258         }
259
260         if (_reels.size() != other_cpl->_reels.size()) {
261                 note (DCP_ERROR, String::compose ("CPL: reel counts differ (%1 vs %2)", _reels.size(), other_cpl->_reels.size()));
262                 return false;
263         }
264
265         list<shared_ptr<Reel> >::const_iterator a = _reels.begin ();
266         list<shared_ptr<Reel> >::const_iterator b = other_cpl->_reels.begin ();
267
268         while (a != _reels.end ()) {
269                 if (!(*a)->equals (*b, opt, note)) {
270                         return false;
271                 }
272                 ++a;
273                 ++b;
274         }
275
276         return true;
277 }
278
279 /** @return true if we have any encrypted content */
280 bool
281 CPL::encrypted () const
282 {
283         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
284                 if (i->encrypted ()) {
285                         return true;
286                 }
287         }
288
289         return false;
290 }
291
292 /** Add a KDM to this CPL.  If the KDM is for any of this CPLs assets it will be used
293  *  to decrypt those assets.
294  *  @param kdm KDM.
295  */
296 void
297 CPL::add (DecryptedKDM const & kdm)
298 {
299         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
300                 i->add (kdm);
301         }
302 }
303
304 void
305 CPL::resolve_refs (list<shared_ptr<Asset> > assets)
306 {
307         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
308                 i->resolve_refs (assets);
309         }
310 }
311
312 string
313 CPL::pkl_type (Standard standard) const
314 {
315         return static_pkl_type (standard);
316 }
317
318 string
319 CPL::static_pkl_type (Standard standard)
320 {
321         switch (standard) {
322         case INTEROP:
323                 return "text/xml;asdcpKind=CPL";
324         case SMPTE:
325                 return "text/xml";
326         default:
327                 DCP_ASSERT (false);
328         }
329 }
330
331 int64_t
332 CPL::duration () const
333 {
334         int64_t d = 0;
335         BOOST_FOREACH (shared_ptr<Reel> i, _reels) {
336                 d += i->duration ();
337         }
338         return d;
339 }
340 void
341 CPL::set_content_versions (vector<ContentVersion> v)
342 {
343         set<string> ids;
344         BOOST_FOREACH (ContentVersion i, v) {
345                 if (!ids.insert(i.id).second) {
346                         throw DuplicateIdError ("Duplicate ID in ContentVersion list");
347                 }
348         }
349
350         _content_versions = v;
351 }
352
353
354 ContentVersion
355 CPL::content_version () const
356 {
357         DCP_ASSERT (!_content_versions.empty());
358         return _content_versions[0];
359 }