Access sound asset sampling rate, channels; access sound asset from DCP; fix bad...
[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 "util.h"
36 #include "metadata.h"
37 #include "exceptions.h"
38 #include "cpl.h"
39 #include "pkl.h"
40 #include "asset_map.h"
41
42 using namespace std;
43 using namespace boost;
44 using namespace libdcp;
45
46 DCP::DCP (string directory, string name, ContentKind content_kind, int fps, int length)
47         : _directory (directory)
48         , _name (name)
49         , _content_kind (content_kind)
50         , _fps (fps)
51         , _length (length)
52 {
53         
54 }
55
56 void
57 DCP::add_sound_asset (vector<string> const & files)
58 {
59         _assets.push_back (shared_ptr<SoundAsset> (new SoundAsset (files, _directory, "audio.mxf", &Progress, _fps, _length)));
60 }
61
62 void
63 DCP::add_sound_asset (sigc::slot<string, Channel> get_path, int channels)
64 {
65         _assets.push_back (shared_ptr<SoundAsset> (new SoundAsset (get_path, _directory, "audio.mxf", &Progress, _fps, _length, channels)));
66 }
67
68 void
69 DCP::add_picture_asset (vector<string> const & files, int width, int height)
70 {
71         _assets.push_back (shared_ptr<PictureAsset> (new PictureAsset (files, _directory, "video.mxf", &Progress, _fps, _length, width, height)));
72 }
73
74 void
75 DCP::add_picture_asset (sigc::slot<string, int> get_path, int width, int height)
76 {
77         _assets.push_back (shared_ptr<PictureAsset> (new PictureAsset (get_path, _directory, "video.mxf", &Progress, _fps, _length, width, height)));
78 }
79
80 void
81 DCP::write_xml () const
82 {
83         string cpl_uuid = make_uuid ();
84         string cpl_path = write_cpl (cpl_uuid);
85         int cpl_length = filesystem::file_size (cpl_path);
86         string cpl_digest = make_digest (cpl_path, 0);
87
88         string pkl_uuid = make_uuid ();
89         string pkl_path = write_pkl (pkl_uuid, cpl_uuid, cpl_digest, cpl_length);
90         
91         write_volindex ();
92         write_assetmap (cpl_uuid, cpl_length, pkl_uuid, filesystem::file_size (pkl_path));
93 }
94
95 string
96 DCP::write_cpl (string cpl_uuid) const
97 {
98         filesystem::path p;
99         p /= _directory;
100         stringstream s;
101         s << cpl_uuid << "_cpl.xml";
102         p /= s.str();
103         ofstream cpl (p.string().c_str());
104         
105         cpl << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
106             << "<CompositionPlaylist xmlns=\"http://www.smpte-ra.org/schemas/429-7/2006/CPL\">\n"
107             << "  <Id>urn:uuid:" << cpl_uuid << "</Id>\n"
108             << "  <AnnotationText>" << _name << "</AnnotationText>\n"
109             << "  <IssueDate>" << Metadata::instance()->issue_date << "</IssueDate>\n"
110             << "  <Creator>" << Metadata::instance()->creator << "</Creator>\n"
111             << "  <ContentTitleText>" << _name << "</ContentTitleText>\n"
112             << "  <ContentKind>" << content_kind_to_string (_content_kind) << "</ContentKind>\n"
113             << "  <ContentVersion>\n"
114             << "    <Id>urn:uri:" << cpl_uuid << "_" << Metadata::instance()->issue_date << "</Id>\n"
115             << "    <LabelText>" << cpl_uuid << "_" << Metadata::instance()->issue_date << "</LabelText>\n"
116             << "  </ContentVersion>\n"
117             << "  <RatingList/>\n"
118             << "  <ReelList>\n";
119
120         cpl << "    <Reel>\n"
121             << "      <Id>urn:uuid:" << make_uuid() << "</Id>\n"
122             << "      <AssetList>\n";
123
124         for (list<shared_ptr<Asset> >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) {
125                 (*i)->write_to_cpl (cpl);
126         }
127
128         cpl << "      </AssetList>\n"
129             << "    </Reel>\n"
130             << "  </ReelList>\n"
131             << "</CompositionPlaylist>\n";
132
133         return p.string ();
134 }
135
136 std::string
137 DCP::write_pkl (string pkl_uuid, string cpl_uuid, string cpl_digest, int cpl_length) const
138 {
139         filesystem::path p;
140         p /= _directory;
141         stringstream s;
142         s << pkl_uuid << "_pkl.xml";
143         p /= s.str();
144         ofstream pkl (p.string().c_str());
145
146         pkl << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
147             << "<PackingList xmlns=\"http://www.smpte-ra.org/schemas/429-8/2007/PKL\">\n"
148             << "  <Id>urn:uuid:" << pkl_uuid << "</Id>\n"
149             << "  <AnnotationText>" << _name << "</AnnotationText>\n"
150             << "  <IssueDate>" << Metadata::instance()->issue_date << "</IssueDate>\n"
151             << "  <Issuer>" << Metadata::instance()->issuer << "</Issuer>\n"
152             << "  <Creator>" << Metadata::instance()->creator << "</Creator>\n"
153             << "  <AssetList>\n";
154
155         for (list<shared_ptr<Asset> >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) {
156                 (*i)->write_to_pkl (pkl);
157         }
158
159         pkl << "    <Asset>\n"
160             << "      <Id>urn:uuid:" << cpl_uuid << "</Id>\n"
161             << "      <Hash>" << cpl_digest << "</Hash>\n"
162             << "      <Size>" << cpl_length << "</Size>\n"
163             << "      <Type>text/xml</Type>\n"
164             << "    </Asset>\n";
165
166         pkl << "  </AssetList>\n"
167             << "</PackingList>\n";
168
169         return p.string ();
170 }
171
172 void
173 DCP::write_volindex () const
174 {
175         filesystem::path p;
176         p /= _directory;
177         p /= "VOLINDEX.xml";
178         ofstream vi (p.string().c_str());
179
180         vi << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
181            << "<VolumeIndex xmlns=\"http://www.smpte-ra.org/schemas/429-9/2007/AM\">\n"
182            << "  <Index>1</Index>\n"
183            << "</VolumeIndex>\n";
184 }
185
186 void
187 DCP::write_assetmap (string cpl_uuid, int cpl_length, string pkl_uuid, int pkl_length) const
188 {
189         filesystem::path p;
190         p /= _directory;
191         p /= "ASSETMAP.xml";
192         ofstream am (p.string().c_str());
193
194         am << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
195            << "<AssetMap xmlns=\"http://www.smpte-ra.org/schemas/429-9/2007/AM\">\n"
196            << "  <Id>urn:uuid:" << make_uuid() << "</Id>\n"
197            << "  <Creator>" << Metadata::instance()->creator << "</Creator>\n"
198            << "  <VolumeCount>1</VolumeCount>\n"
199            << "  <IssueDate>" << Metadata::instance()->issue_date << "</IssueDate>\n"
200            << "  <Issuer>" << Metadata::instance()->issuer << "</Issuer>\n"
201            << "  <AssetList>\n";
202
203         am << "    <Asset>\n"
204            << "      <Id>urn:uuid:" << pkl_uuid << "</Id>\n"
205            << "      <PackingList>true</PackingList>\n"
206            << "      <ChunkList>\n"
207            << "        <Chunk>\n"
208            << "          <Path>" << pkl_uuid << "_pkl.xml</Path>\n"
209            << "          <VolumeIndex>1</VolumeIndex>\n"
210            << "          <Offset>0</Offset>\n"
211            << "          <Length>" << pkl_length << "</Length>\n"
212            << "        </Chunk>\n"
213            << "      </ChunkList>\n"
214            << "    </Asset>\n";
215
216         am << "    <Asset>\n"
217            << "      <Id>urn:uuid:" << cpl_uuid << "</Id>\n"
218            << "      <ChunkList>\n"
219            << "        <Chunk>\n"
220            << "          <Path>" << cpl_uuid << "_cpl.xml</Path>\n"
221            << "          <VolumeIndex>1</VolumeIndex>\n"
222            << "          <Offset>0</Offset>\n"
223            << "          <Length>" << cpl_length << "</Length>\n"
224            << "        </Chunk>\n"
225            << "      </ChunkList>\n"
226            << "    </Asset>\n";
227         
228         for (list<shared_ptr<Asset> >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) {
229                 (*i)->write_to_assetmap (am);
230         }
231
232         am << "  </AssetList>\n"
233            << "</AssetMap>\n";
234 }
235
236
237 DCP::DCP (string directory)
238         : _directory (directory)
239 {
240         string cpl_file;
241         string pkl_file;
242         string asset_map_file;
243
244         for (filesystem::directory_iterator i = filesystem::directory_iterator(directory); i != filesystem::directory_iterator(); ++i) {
245                 
246                 string const t = i->path().string ();
247
248                 if (ends_with (t, ".mxf")) {
249                         continue;
250                 }
251
252                 xmlpp::DomParser* p = new xmlpp::DomParser;
253
254                 try {
255                         p->parse_file (t);
256                 } catch (std::exception& e) {
257                         delete p;
258                         continue;
259                 }
260                 
261                 if (!p) {
262                         delete p;
263                         continue;
264                 }
265
266                 string const root = p->get_document()->get_root_node()->get_name ();
267                 delete p;
268                 
269                 if (root == "CompositionPlaylist") {
270                         if (cpl_file.empty ()) {
271                                 cpl_file = t;
272                         } else {
273                                 throw DCPReadError ("duplicate CPLs found");
274                         }
275                 } else if (root == "PackingList") {
276                         if (pkl_file.empty ()) {
277                                 pkl_file = t;
278                         } else {
279                                 throw DCPReadError ("duplicate PKLs found");
280                         }
281                 } else if (root == "AssetMap") {
282                         if (asset_map_file.empty ()) {
283                                 asset_map_file = t;
284                         } else {
285                                 throw DCPReadError ("duplicate AssetMaps found");
286                         }
287                         asset_map_file = t;
288                 }
289         }
290
291         if (cpl_file.empty ()) {
292                 throw FileError ("no CPL file found", "");
293         }
294
295         if (pkl_file.empty ()) {
296                 throw FileError ("no PKL file found", "");
297         }
298
299         if (asset_map_file.empty ()) {
300                 throw FileError ("no AssetMap file found", "");
301         }
302
303         /* Read the XML */
304         shared_ptr<CPL> cpl;
305         try {
306                 cpl.reset (new CPL (cpl_file));
307         } catch (FileError& e) {
308                 throw FileError ("could not load CPL file", cpl_file);
309         }
310
311         shared_ptr<PKL> pkl;
312         try {
313                 pkl.reset (new PKL (pkl_file));
314         } catch (FileError& e) {
315                 throw FileError ("could not load PKL file", pkl_file);
316         }
317
318         shared_ptr<AssetMap> asset_map;
319         try {
320                 asset_map.reset (new AssetMap (asset_map_file));
321         } catch (FileError& e) {
322                 throw FileError ("could not load AssetMap file", asset_map_file);
323         }
324
325         /* Cross-check */
326         /* XXX */
327
328         /* Now cherry-pick the required bits into our own data structure */
329         
330         _name = cpl->annotation_text;
331         _content_kind = cpl->content_kind;
332
333         shared_ptr<CPLAssetList> cpl_assets = cpl->reels.front()->asset_list;
334
335         /* XXX */
336         _fps = cpl_assets->main_picture->frame_rate.numerator;
337         _length = cpl_assets->main_picture->duration;
338
339         string n = cpl_assets->main_picture->annotation_text;
340         if (n.empty ()) {
341                 n = pkl->asset_from_id(cpl_assets->main_picture->id)->original_file_name;
342         }
343
344         _assets.push_back (shared_ptr<PictureAsset> (
345                                    new PictureAsset (
346                                            _directory,
347                                            n,
348                                            _fps,
349                                            _length,
350                                            cpl_assets->main_picture->screen_aspect_ratio.numerator,
351                                            cpl_assets->main_picture->screen_aspect_ratio.denominator
352                                            )
353                                    ));
354
355         n = cpl_assets->main_sound->annotation_text;
356         if (n.empty ()) {
357                 n = pkl->asset_from_id(cpl_assets->main_sound->id)->original_file_name;
358         }
359         
360         if (cpl_assets->main_sound) {
361                 _assets.push_back (shared_ptr<SoundAsset> (
362                                            new SoundAsset (
363                                                    _directory,
364                                                    n,
365                                                    _fps,
366                                                    _length
367                                                    )
368                                            ));
369         }
370 }
371
372 list<string>
373 DCP::equals (DCP const & other, EqualityOptions opt) const
374 {
375         list<string> notes;
376         
377         if (opt.flags & LIBDCP_METADATA) {
378                 if (_name != other._name) {
379                         notes.push_back ("names differ");
380                 }
381                 if (_content_kind != other._content_kind) {
382                         notes.push_back ("content kinds differ");
383                 }
384                 if (_fps != other._fps) {
385                         notes.push_back ("frames per second differ");
386                 }
387                 if (_length != other._length) {
388                         notes.push_back ("lengths differ");
389                 }
390         }
391
392         if (_assets.size() != other._assets.size()) {
393                 notes.push_back ("asset counts differ");
394         }
395         
396         list<shared_ptr<Asset> >::const_iterator a = _assets.begin ();
397         list<shared_ptr<Asset> >::const_iterator b = other._assets.begin ();
398         
399         while (a != _assets.end ()) {
400                 list<string> n = (*a)->equals (*b, opt);
401                 notes.merge (n);
402                 ++a;
403                 ++b;
404         }
405
406         return notes;
407 }
408
409 shared_ptr<const PictureAsset>
410 DCP::picture_asset () const
411 {
412         for (list<shared_ptr<Asset> >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) {
413                 shared_ptr<PictureAsset> p = dynamic_pointer_cast<PictureAsset> (*i);
414                 if (p) {
415                         return p;
416                 }
417         }
418
419         return shared_ptr<const PictureAsset> ();
420 }
421
422 shared_ptr<const SoundAsset>
423 DCP::sound_asset () const
424 {
425         for (list<shared_ptr<Asset> >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) {
426                 shared_ptr<SoundAsset> s = dynamic_pointer_cast<SoundAsset> (*i);
427                 if (s) {
428                         return s;
429                 }
430         }
431
432         return shared_ptr<const SoundAsset> ();
433 }