Some comments.
[libdcp.git] / src / dcp.cc
1 /*
2     Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 /** @file  src/dcp.cc
21  *  @brief A class to create a DCP.
22  */
23
24 #include <sstream>
25 #include <fstream>
26 #include <iomanip>
27 #include <cassert>
28 #include <iostream>
29 #include <boost/filesystem.hpp>
30 #include <libxml++/libxml++.h>
31 #include "dcp.h"
32 #include "asset.h"
33 #include "sound_asset.h"
34 #include "picture_asset.h"
35 #include "subtitle_asset.h"
36 #include "util.h"
37 #include "metadata.h"
38 #include "exceptions.h"
39 #include "cpl.h"
40 #include "pkl.h"
41 #include "asset_map.h"
42
43 using namespace std;
44 using namespace boost;
45 using namespace libdcp;
46
47 DCP::DCP (string directory, string name, ContentKind content_kind, int fps, int length)
48         : _directory (directory)
49         , _name (name)
50         , _content_kind (content_kind)
51         , _fps (fps)
52         , _length (length)
53 {
54         
55 }
56
57 void
58 DCP::add_sound_asset (vector<string> const & files)
59 {
60         _assets.push_back (shared_ptr<SoundAsset> (new SoundAsset (files, _directory, "audio.mxf", &Progress, _fps, _length)));
61 }
62
63 void
64 DCP::add_sound_asset (sigc::slot<string, Channel> get_path, int channels)
65 {
66         _assets.push_back (shared_ptr<SoundAsset> (new SoundAsset (get_path, _directory, "audio.mxf", &Progress, _fps, _length, channels)));
67 }
68
69 void
70 DCP::add_picture_asset (vector<string> const & files, int width, int height)
71 {
72         _assets.push_back (shared_ptr<PictureAsset> (new PictureAsset (files, _directory, "video.mxf", &Progress, _fps, _length, width, height)));
73 }
74
75 void
76 DCP::add_picture_asset (sigc::slot<string, int> get_path, int width, int height)
77 {
78         _assets.push_back (shared_ptr<PictureAsset> (new PictureAsset (get_path, _directory, "video.mxf", &Progress, _fps, _length, width, height)));
79 }
80
81 void
82 DCP::write_xml () const
83 {
84         string cpl_uuid = make_uuid ();
85         string cpl_path = write_cpl (cpl_uuid);
86         int cpl_length = filesystem::file_size (cpl_path);
87         string cpl_digest = make_digest (cpl_path, 0);
88
89         string pkl_uuid = make_uuid ();
90         string pkl_path = write_pkl (pkl_uuid, cpl_uuid, cpl_digest, cpl_length);
91         
92         write_volindex ();
93         write_assetmap (cpl_uuid, cpl_length, pkl_uuid, filesystem::file_size (pkl_path));
94 }
95
96 string
97 DCP::write_cpl (string cpl_uuid) const
98 {
99         filesystem::path p;
100         p /= _directory;
101         stringstream s;
102         s << cpl_uuid << "_cpl.xml";
103         p /= s.str();
104         ofstream cpl (p.string().c_str());
105         
106         cpl << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
107             << "<CompositionPlaylist xmlns=\"http://www.smpte-ra.org/schemas/429-7/2006/CPL\">\n"
108             << "  <Id>urn:uuid:" << cpl_uuid << "</Id>\n"
109             << "  <AnnotationText>" << _name << "</AnnotationText>\n"
110             << "  <IssueDate>" << Metadata::instance()->issue_date << "</IssueDate>\n"
111             << "  <Creator>" << Metadata::instance()->creator << "</Creator>\n"
112             << "  <ContentTitleText>" << _name << "</ContentTitleText>\n"
113             << "  <ContentKind>" << content_kind_to_string (_content_kind) << "</ContentKind>\n"
114             << "  <ContentVersion>\n"
115             << "    <Id>urn:uri:" << cpl_uuid << "_" << Metadata::instance()->issue_date << "</Id>\n"
116             << "    <LabelText>" << cpl_uuid << "_" << Metadata::instance()->issue_date << "</LabelText>\n"
117             << "  </ContentVersion>\n"
118             << "  <RatingList/>\n"
119             << "  <ReelList>\n";
120
121         cpl << "    <Reel>\n"
122             << "      <Id>urn:uuid:" << make_uuid() << "</Id>\n"
123             << "      <AssetList>\n";
124
125         for (list<shared_ptr<Asset> >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) {
126                 (*i)->write_to_cpl (cpl);
127         }
128
129         cpl << "      </AssetList>\n"
130             << "    </Reel>\n"
131             << "  </ReelList>\n"
132             << "</CompositionPlaylist>\n";
133
134         return p.string ();
135 }
136
137 std::string
138 DCP::write_pkl (string pkl_uuid, string cpl_uuid, string cpl_digest, int cpl_length) const
139 {
140         filesystem::path p;
141         p /= _directory;
142         stringstream s;
143         s << pkl_uuid << "_pkl.xml";
144         p /= s.str();
145         ofstream pkl (p.string().c_str());
146
147         pkl << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
148             << "<PackingList xmlns=\"http://www.smpte-ra.org/schemas/429-8/2007/PKL\">\n"
149             << "  <Id>urn:uuid:" << pkl_uuid << "</Id>\n"
150             << "  <AnnotationText>" << _name << "</AnnotationText>\n"
151             << "  <IssueDate>" << Metadata::instance()->issue_date << "</IssueDate>\n"
152             << "  <Issuer>" << Metadata::instance()->issuer << "</Issuer>\n"
153             << "  <Creator>" << Metadata::instance()->creator << "</Creator>\n"
154             << "  <AssetList>\n";
155
156         for (list<shared_ptr<Asset> >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) {
157                 (*i)->write_to_pkl (pkl);
158         }
159
160         pkl << "    <Asset>\n"
161             << "      <Id>urn:uuid:" << cpl_uuid << "</Id>\n"
162             << "      <Hash>" << cpl_digest << "</Hash>\n"
163             << "      <Size>" << cpl_length << "</Size>\n"
164             << "      <Type>text/xml</Type>\n"
165             << "    </Asset>\n";
166
167         pkl << "  </AssetList>\n"
168             << "</PackingList>\n";
169
170         return p.string ();
171 }
172
173 void
174 DCP::write_volindex () const
175 {
176         filesystem::path p;
177         p /= _directory;
178         p /= "VOLINDEX.xml";
179         ofstream vi (p.string().c_str());
180
181         vi << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
182            << "<VolumeIndex xmlns=\"http://www.smpte-ra.org/schemas/429-9/2007/AM\">\n"
183            << "  <Index>1</Index>\n"
184            << "</VolumeIndex>\n";
185 }
186
187 void
188 DCP::write_assetmap (string cpl_uuid, int cpl_length, string pkl_uuid, int pkl_length) const
189 {
190         filesystem::path p;
191         p /= _directory;
192         p /= "ASSETMAP.xml";
193         ofstream am (p.string().c_str());
194
195         am << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
196            << "<AssetMap xmlns=\"http://www.smpte-ra.org/schemas/429-9/2007/AM\">\n"
197            << "  <Id>urn:uuid:" << make_uuid() << "</Id>\n"
198            << "  <Creator>" << Metadata::instance()->creator << "</Creator>\n"
199            << "  <VolumeCount>1</VolumeCount>\n"
200            << "  <IssueDate>" << Metadata::instance()->issue_date << "</IssueDate>\n"
201            << "  <Issuer>" << Metadata::instance()->issuer << "</Issuer>\n"
202            << "  <AssetList>\n";
203
204         am << "    <Asset>\n"
205            << "      <Id>urn:uuid:" << pkl_uuid << "</Id>\n"
206            << "      <PackingList>true</PackingList>\n"
207            << "      <ChunkList>\n"
208            << "        <Chunk>\n"
209            << "          <Path>" << pkl_uuid << "_pkl.xml</Path>\n"
210            << "          <VolumeIndex>1</VolumeIndex>\n"
211            << "          <Offset>0</Offset>\n"
212            << "          <Length>" << pkl_length << "</Length>\n"
213            << "        </Chunk>\n"
214            << "      </ChunkList>\n"
215            << "    </Asset>\n";
216
217         am << "    <Asset>\n"
218            << "      <Id>urn:uuid:" << cpl_uuid << "</Id>\n"
219            << "      <ChunkList>\n"
220            << "        <Chunk>\n"
221            << "          <Path>" << cpl_uuid << "_cpl.xml</Path>\n"
222            << "          <VolumeIndex>1</VolumeIndex>\n"
223            << "          <Offset>0</Offset>\n"
224            << "          <Length>" << cpl_length << "</Length>\n"
225            << "        </Chunk>\n"
226            << "      </ChunkList>\n"
227            << "    </Asset>\n";
228         
229         for (list<shared_ptr<Asset> >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) {
230                 (*i)->write_to_assetmap (am);
231         }
232
233         am << "  </AssetList>\n"
234            << "</AssetMap>\n";
235 }
236
237
238 DCP::DCP (string directory)
239         : _directory (directory)
240 {
241         Files files;
242         scan (files, directory);
243
244         if (files.cpl.empty ()) {
245                 throw FileError ("no CPL file found", "");
246         }
247
248         if (files.pkl.empty ()) {
249                 throw FileError ("no PKL file found", "");
250         }
251
252         if (files.asset_map.empty ()) {
253                 throw FileError ("no AssetMap file found", "");
254         }
255
256         /* Read the XML */
257         shared_ptr<CPL> cpl;
258         try {
259                 cpl.reset (new CPL (files.cpl));
260         } catch (FileError& e) {
261                 throw FileError ("could not load CPL file", files.cpl);
262         }
263
264         shared_ptr<PKL> pkl;
265         try {
266                 pkl.reset (new PKL (files.pkl));
267         } catch (FileError& e) {
268                 throw FileError ("could not load PKL file", files.pkl);
269         }
270
271         shared_ptr<AssetMap> asset_map;
272         try {
273                 asset_map.reset (new AssetMap (files.asset_map));
274         } catch (FileError& e) {
275                 throw FileError ("could not load AssetMap file", files.asset_map);
276         }
277
278         /* Cross-check */
279         /* XXX */
280
281         /* Now cherry-pick the required bits into our own data structure */
282         
283         _name = cpl->annotation_text;
284         _content_kind = cpl->content_kind;
285
286         shared_ptr<CPLAssetList> cpl_assets = cpl->reels.front()->asset_list;
287
288         /* XXX */
289         _fps = cpl_assets->main_picture->frame_rate.numerator;
290         _length = cpl_assets->main_picture->duration;
291
292         string n = pkl->asset_from_id(cpl_assets->main_picture->id)->original_file_name;
293         if (n.empty ()) {
294                 n = cpl_assets->main_picture->annotation_text;
295         }
296
297         _assets.push_back (shared_ptr<PictureAsset> (
298                                    new PictureAsset (
299                                            _directory,
300                                            n,
301                                            _fps,
302                                            _length
303                                            )
304                                    ));
305
306         if (cpl_assets->main_sound) {
307
308                 n = pkl->asset_from_id(cpl_assets->main_sound->id)->original_file_name;
309                 if (n.empty ()) {
310                         n = cpl_assets->main_sound->annotation_text;
311                 }
312         
313                 _assets.push_back (shared_ptr<SoundAsset> (
314                                            new SoundAsset (
315                                                    _directory,
316                                                    n,
317                                                    _fps,
318                                                    _length
319                                                    )
320                                            ));
321         }
322
323         for (list<string>::iterator i = files.subtitles.begin(); i != files.subtitles.end(); ++i) {
324                 string const l = i->substr (_directory.length ());
325                 _assets.push_back (shared_ptr<SubtitleAsset> (new SubtitleAsset (_directory, l)));
326         }
327 }
328
329
330 void
331 DCP::scan (Files& files, string directory) const
332 {
333         for (filesystem::directory_iterator i = filesystem::directory_iterator(directory); i != filesystem::directory_iterator(); ++i) {
334                 
335                 string const t = i->path().string ();
336
337                 if (filesystem::is_directory (*i)) {
338                         scan (files, t);
339                         continue;
340                 }
341
342                 if (ends_with (t, ".mxf") || ends_with (t, ".ttf")) {
343                         continue;
344                 }
345
346                 xmlpp::DomParser* p = new xmlpp::DomParser;
347
348                 try {
349                         p->parse_file (t);
350                 } catch (std::exception& e) {
351                         delete p;
352                         continue;
353                 }
354                 
355                 if (!p) {
356                         delete p;
357                         continue;
358                 }
359
360                 string const root = p->get_document()->get_root_node()->get_name ();
361                 delete p;
362                 
363                 if (root == "CompositionPlaylist") {
364                         if (files.cpl.empty ()) {
365                                 files.cpl = t;
366                         } else {
367                                 throw DCPReadError ("duplicate CPLs found");
368                         }
369                 } else if (root == "PackingList") {
370                         if (files.pkl.empty ()) {
371                                 files.pkl = t;
372                         } else {
373                                 throw DCPReadError ("duplicate PKLs found");
374                         }
375                 } else if (root == "AssetMap") {
376                         if (files.asset_map.empty ()) {
377                                 files.asset_map = t;
378                         } else {
379                                 throw DCPReadError ("duplicate AssetMaps found");
380                         }
381                         files.asset_map = t;
382                 } else if (root == "DCSubtitle") {
383                         files.subtitles.push_back (t);
384                 }
385         }
386 }
387
388
389 list<string>
390 DCP::equals (DCP const & other, EqualityOptions opt) const
391 {
392         list<string> notes;
393         
394         if (opt.flags & LIBDCP_METADATA) {
395                 if (_name != other._name) {
396                         notes.push_back ("names differ");
397                 }
398                 if (_content_kind != other._content_kind) {
399                         notes.push_back ("content kinds differ");
400                 }
401                 if (_fps != other._fps) {
402                         notes.push_back ("frames per second differ");
403                 }
404                 if (_length != other._length) {
405                         notes.push_back ("lengths differ");
406                 }
407         }
408
409         if (_assets.size() != other._assets.size()) {
410                 notes.push_back ("asset counts differ");
411         }
412         
413         list<shared_ptr<Asset> >::const_iterator a = _assets.begin ();
414         list<shared_ptr<Asset> >::const_iterator b = other._assets.begin ();
415         
416         while (a != _assets.end ()) {
417                 list<string> n = (*a)->equals (*b, opt);
418                 notes.merge (n);
419                 ++a;
420                 ++b;
421         }
422
423         return notes;
424 }
425
426 shared_ptr<const PictureAsset>
427 DCP::picture_asset () const
428 {
429         for (list<shared_ptr<Asset> >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) {
430                 shared_ptr<PictureAsset> p = dynamic_pointer_cast<PictureAsset> (*i);
431                 if (p) {
432                         return p;
433                 }
434         }
435
436         return shared_ptr<const PictureAsset> ();
437 }
438
439 shared_ptr<const SoundAsset>
440 DCP::sound_asset () const
441 {
442         for (list<shared_ptr<Asset> >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) {
443                 shared_ptr<SoundAsset> s = dynamic_pointer_cast<SoundAsset> (*i);
444                 if (s) {
445                         return s;
446                 }
447         }
448
449         return shared_ptr<const SoundAsset> ();
450 }
451
452 shared_ptr<const SubtitleAsset>
453 DCP::subtitle_asset () const
454 {
455         for (list<shared_ptr<Asset> >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) {
456                 shared_ptr<SubtitleAsset> s = dynamic_pointer_cast<SubtitleAsset> (*i);
457                 if (s) {
458                         return s;
459                 }
460         }
461
462         return shared_ptr<const SubtitleAsset> ();
463 }