dc895e11c2eb5cd38f77de4d75eb291f013196e9
[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 <boost/filesystem.hpp>
29 #include "dcp.h"
30 #include "asset.h"
31 #include "sound_asset.h"
32 #include "picture_asset.h"
33 #include "util.h"
34 #include "metadata.h"
35 #include "exceptions.h"
36 #include "cpl.h"
37 #include "pkl.h"
38 #include "asset_map.h"
39
40 using namespace std;
41 using namespace boost;
42 using namespace libdcp;
43
44 DCP::DCP (string directory, string name, ContentKind content_kind, int fps, int length)
45         : _directory (directory)
46         , _name (name)
47         , _content_kind (content_kind)
48         , _fps (fps)
49         , _length (length)
50 {
51         
52 }
53
54 void
55 DCP::add_sound_asset (vector<string> const & files)
56 {
57         _assets.push_back (shared_ptr<SoundAsset> (new SoundAsset (files, _directory, "audio.mxf", &Progress, _fps, _length)));
58 }
59
60 void
61 DCP::add_sound_asset (sigc::slot<string, Channel> get_path, int channels)
62 {
63         _assets.push_back (shared_ptr<SoundAsset> (new SoundAsset (get_path, _directory, "audio.mxf", &Progress, _fps, _length, channels)));
64 }
65
66 void
67 DCP::add_picture_asset (vector<string> const & files, int width, int height)
68 {
69         _assets.push_back (shared_ptr<PictureAsset> (new PictureAsset (files, _directory, "video.mxf", &Progress, _fps, _length, width, height)));
70 }
71
72 void
73 DCP::add_picture_asset (sigc::slot<string, int> get_path, int width, int height)
74 {
75         _assets.push_back (shared_ptr<PictureAsset> (new PictureAsset (get_path, _directory, "video.mxf", &Progress, _fps, _length, width, height)));
76 }
77
78 void
79 DCP::write_xml () const
80 {
81         string cpl_uuid = make_uuid ();
82         string cpl_path = write_cpl (cpl_uuid);
83         int cpl_length = filesystem::file_size (cpl_path);
84         string cpl_digest = make_digest (cpl_path, 0);
85
86         string pkl_uuid = make_uuid ();
87         string pkl_path = write_pkl (pkl_uuid, cpl_uuid, cpl_digest, cpl_length);
88         
89         write_volindex ();
90         write_assetmap (cpl_uuid, cpl_length, pkl_uuid, filesystem::file_size (pkl_path));
91 }
92
93 string
94 DCP::write_cpl (string cpl_uuid) const
95 {
96         filesystem::path p;
97         p /= _directory;
98         stringstream s;
99         s << cpl_uuid << "_cpl.xml";
100         p /= s.str();
101         ofstream cpl (p.string().c_str());
102         
103         cpl << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
104             << "<CompositionPlaylist xmlns=\"http://www.smpte-ra.org/schemas/429-7/2006/CPL\">\n"
105             << "  <Id>urn:uuid:" << cpl_uuid << "</Id>\n"
106             << "  <AnnotationText>" << _name << "</AnnotationText>\n"
107             << "  <IssueDate>" << Metadata::instance()->issue_date << "</IssueDate>\n"
108             << "  <Creator>" << Metadata::instance()->creator << "</Creator>\n"
109             << "  <ContentTitleText>" << _name << "</ContentTitleText>\n"
110             << "  <ContentKind>" << content_kind_to_string (_content_kind) << "</ContentKind>\n"
111             << "  <ContentVersion>\n"
112             << "    <Id>urn:uri:" << cpl_uuid << "_" << Metadata::instance()->issue_date << "</Id>\n"
113             << "    <LabelText>" << cpl_uuid << "_" << Metadata::instance()->issue_date << "</LabelText>\n"
114             << "  </ContentVersion>\n"
115             << "  <RatingList/>\n"
116             << "  <ReelList>\n";
117
118         cpl << "    <Reel>\n"
119             << "      <Id>urn:uuid:" << make_uuid() << "</Id>\n"
120             << "      <AssetList>\n";
121
122         for (list<shared_ptr<Asset> >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) {
123                 (*i)->write_to_cpl (cpl);
124         }
125
126         cpl << "      </AssetList>\n"
127             << "    </Reel>\n"
128             << "  </ReelList>\n"
129             << "</CompositionPlaylist>\n";
130
131         return p.string ();
132 }
133
134 std::string
135 DCP::write_pkl (string pkl_uuid, string cpl_uuid, string cpl_digest, int cpl_length) const
136 {
137         filesystem::path p;
138         p /= _directory;
139         stringstream s;
140         s << pkl_uuid << "_pkl.xml";
141         p /= s.str();
142         ofstream pkl (p.string().c_str());
143
144         pkl << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
145             << "<PackingList xmlns=\"http://www.smpte-ra.org/schemas/429-8/2007/PKL\">\n"
146             << "  <Id>urn:uuid:" << pkl_uuid << "</Id>\n"
147             << "  <AnnotationText>" << _name << "</AnnotationText>\n"
148             << "  <IssueDate>" << Metadata::instance()->issue_date << "</IssueDate>\n"
149             << "  <Issuer>" << Metadata::instance()->issuer << "</Issuer>\n"
150             << "  <Creator>" << Metadata::instance()->creator << "</Creator>\n"
151             << "  <AssetList>\n";
152
153         for (list<shared_ptr<Asset> >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) {
154                 (*i)->write_to_pkl (pkl);
155         }
156
157         pkl << "    <Asset>\n"
158             << "      <Id>urn:uuid:" << cpl_uuid << "</Id>\n"
159             << "      <Hash>" << cpl_digest << "</Hash>\n"
160             << "      <Size>" << cpl_length << "</Size>\n"
161             << "      <Type>text/xml</Type>\n"
162             << "    </Asset>\n";
163
164         pkl << "  </AssetList>\n"
165             << "</PackingList>\n";
166
167         return p.string ();
168 }
169
170 void
171 DCP::write_volindex () const
172 {
173         filesystem::path p;
174         p /= _directory;
175         p /= "VOLINDEX.xml";
176         ofstream vi (p.string().c_str());
177
178         vi << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
179            << "<VolumeIndex xmlns=\"http://www.smpte-ra.org/schemas/429-9/2007/AM\">\n"
180            << "  <Index>1</Index>\n"
181            << "</VolumeIndex>\n";
182 }
183
184 void
185 DCP::write_assetmap (string cpl_uuid, int cpl_length, string pkl_uuid, int pkl_length) const
186 {
187         filesystem::path p;
188         p /= _directory;
189         p /= "ASSETMAP.xml";
190         ofstream am (p.string().c_str());
191
192         am << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
193            << "<AssetMap xmlns=\"http://www.smpte-ra.org/schemas/429-9/2007/AM\">\n"
194            << "  <Id>urn:uuid:" << make_uuid() << "</Id>\n"
195            << "  <Creator>" << Metadata::instance()->creator << "</Creator>\n"
196            << "  <VolumeCount>1</VolumeCount>\n"
197            << "  <IssueDate>" << Metadata::instance()->issue_date << "</IssueDate>\n"
198            << "  <Issuer>" << Metadata::instance()->issuer << "</Issuer>\n"
199            << "  <AssetList>\n";
200
201         am << "    <Asset>\n"
202            << "      <Id>urn:uuid:" << pkl_uuid << "</Id>\n"
203            << "      <PackingList>true</PackingList>\n"
204            << "      <ChunkList>\n"
205            << "        <Chunk>\n"
206            << "          <Path>" << pkl_uuid << "_pkl.xml</Path>\n"
207            << "          <VolumeIndex>1</VolumeIndex>\n"
208            << "          <Offset>0</Offset>\n"
209            << "          <Length>" << pkl_length << "</Length>\n"
210            << "        </Chunk>\n"
211            << "      </ChunkList>\n"
212            << "    </Asset>\n";
213
214         am << "    <Asset>\n"
215            << "      <Id>urn:uuid:" << cpl_uuid << "</Id>\n"
216            << "      <ChunkList>\n"
217            << "        <Chunk>\n"
218            << "          <Path>" << cpl_uuid << "_cpl.xml</Path>\n"
219            << "          <VolumeIndex>1</VolumeIndex>\n"
220            << "          <Offset>0</Offset>\n"
221            << "          <Length>" << cpl_length << "</Length>\n"
222            << "        </Chunk>\n"
223            << "      </ChunkList>\n"
224            << "    </Asset>\n";
225         
226         for (list<shared_ptr<Asset> >::const_iterator i = _assets.begin(); i != _assets.end(); ++i) {
227                 (*i)->write_to_assetmap (am);
228         }
229
230         am << "  </AssetList>\n"
231            << "</AssetMap>\n";
232 }
233
234
235 DCP::DCP (string directory)
236         : _directory (directory)
237 {
238         string cpl_file;
239         string pkl_file;
240         string asset_map_file;
241
242         for (filesystem::directory_iterator i = filesystem::directory_iterator(directory); i != filesystem::directory_iterator(); ++i) {
243                 string const t = i->path().string ();
244                 if (ends_with (t, "_cpl.xml")) {
245                         if (cpl_file.empty ()) {
246                                 cpl_file = t;
247                         } else {
248                                 throw DCPReadError ("duplicate CPLs found");
249                         }
250                 } else if (ends_with (t, "_pkl.xml")) {
251                         if (pkl_file.empty ()) {
252                                 pkl_file = t;
253                         } else {
254                                 throw DCPReadError ("duplicate PKLs found");
255                         }
256                 } else if (ends_with (t, "ASSETMAP.xml")) {
257                         if (asset_map_file.empty ()) {
258                                 asset_map_file = t;
259                         } else {
260                                 throw DCPReadError ("duplicate AssetMaps found");
261                         }
262                 }
263         }
264
265         /* Read the XML */
266         CPL cpl (cpl_file);
267         PKL pkl (pkl_file);
268         AssetMap asset_map (asset_map_file);
269
270         /* Cross-check */
271         /* XXX */
272
273         /* Now cherry-pick the required bits into our own data structure */
274         
275         _name = cpl.annotation_text;
276         _content_kind = cpl.content_kind;
277
278         shared_ptr<CPLAssetList> cpl_assets = cpl.reels.front()->asset_list;
279         
280         /* XXX */
281         _fps = cpl_assets->main_picture->frame_rate.numerator;
282         _length = cpl_assets->main_picture->duration;
283
284         _assets.push_back (shared_ptr<PictureAsset> (
285                                    new PictureAsset (
286                                            _directory,
287                                            cpl_assets->main_picture->annotation_text,
288                                            _fps,
289                                            _length,
290                                            cpl_assets->main_picture->screen_aspect_ratio.numerator,
291                                            cpl_assets->main_picture->screen_aspect_ratio.denominator
292                                            )
293                                    ));
294
295         if (cpl_assets->main_sound) {
296                 _assets.push_back (shared_ptr<SoundAsset> (
297                                            new SoundAsset (
298                                                    _directory,
299                                                    cpl_assets->main_picture->annotation_text,
300                                                    _fps,
301                                                    _length
302                                                    )
303                                            ));
304         }
305 }
306
307 list<string>
308 DCP::equals (DCP const & other, EqualityType type) const
309 {
310         list<string> notes;
311         
312         switch (type) {
313         case LIBDCP_METADATA:
314                 if (_name != other._name) {
315                         notes.push_back ("names differ");
316                 }
317                 if (_content_kind != other._content_kind) {
318                         notes.push_back ("content kinds differ");
319                 }
320                 if (_fps != other._fps) {
321                         notes.push_back ("frames per second differ");
322                 }
323                 if (_length != other._length) {
324                         notes.push_back ("lengths differ");
325                 }
326                 if (_assets.size() != other._assets.size()) {
327                         notes.push_back ("asset counts differ");
328                 }
329                 break;
330         }
331
332         return notes;
333 }