Try to move XML bits out into parse/ subdir.
[libdcp.git] / src / cpl.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 #include <fstream>
21 #include "cpl.h"
22 #include "parse/cpl.h"
23 #include "util.h"
24 #include "picture_asset.h"
25 #include "sound_asset.h"
26 #include "subtitle_asset.h"
27 #include "parse/asset_map.h"
28 #include "reel.h"
29 #include "metadata.h"
30
31 using std::string;
32 using std::stringstream;
33 using std::ofstream;
34 using std::ostream;
35 using std::list;
36 using boost::shared_ptr;
37 using namespace libdcp;
38
39 CPL::CPL (string directory, string name, ContentKind content_kind, int length, int frames_per_second)
40         : _directory (directory)
41         , _name (name)
42         , _content_kind (content_kind)
43         , _length (length)
44         , _fps (frames_per_second)
45 {
46         _uuid = make_uuid ();
47 }
48
49 /** Construct a CPL object from a XML file.
50  *  @param directory The directory containing this CPL's DCP.
51  *  @param file The CPL XML filename.
52  *  @param asset_map The corresponding asset map.
53  *  @param require_mxfs true to throw an exception if a required MXF file does not exist.
54  */
55 CPL::CPL (string directory, string file, shared_ptr<const libdcp::parse::AssetMap> asset_map, bool require_mxfs)
56         : _directory (directory)
57         , _content_kind (FEATURE)
58         , _length (0)
59         , _fps (0)
60 {
61         /* Read the XML */
62         shared_ptr<parse::CPL> cpl;
63         try {
64                 cpl.reset (new parse::CPL (file));
65         } catch (FileError& e) {
66                 boost::throw_exception (FileError ("could not load CPL file", file));
67         }
68         
69         /* Now cherry-pick the required bits into our own data structure */
70         
71         _name = cpl->annotation_text;
72         _content_kind = cpl->content_kind;
73
74         for (list<shared_ptr<libdcp::parse::Reel> >::iterator i = cpl->reels.begin(); i != cpl->reels.end(); ++i) {
75
76                 shared_ptr<parse::Picture> p;
77
78                 if ((*i)->asset_list->main_picture) {
79                         p = (*i)->asset_list->main_picture;
80                 } else {
81                         p = (*i)->asset_list->main_stereoscopic_picture;
82                 }
83                 
84                 _fps = p->edit_rate.numerator;
85                 _length += p->duration;
86
87                 shared_ptr<PictureAsset> picture;
88                 shared_ptr<SoundAsset> sound;
89                 shared_ptr<SubtitleAsset> subtitle;
90
91                 /* Some rather twisted logic to decide if we are 3D or not;
92                    some DCPs give a MainStereoscopicPicture to indicate 3D, others
93                    just have a FrameRate twice the EditRate and apparently
94                    expect you to divine the fact that they are hence 3D.
95                 */
96
97                 if (!(*i)->asset_list->main_stereoscopic_picture && p->edit_rate == p->frame_rate) {
98
99                         try {
100                                 picture.reset (new MonoPictureAsset (
101                                                        _directory,
102                                                        asset_map->asset_from_id (p->id)->chunks.front()->path
103                                                        )
104                                         );
105
106                                 picture->set_entry_point (p->entry_point);
107                                 picture->set_duration (p->duration);
108                         } catch (MXFFileError) {
109                                 if (require_mxfs) {
110                                         throw;
111                                 }
112                         }
113                         
114                 } else {
115                         try {
116                                 picture.reset (new StereoPictureAsset (
117                                                        _directory,
118                                                        asset_map->asset_from_id (p->id)->chunks.front()->path,
119                                                        _fps,
120                                                        p->duration
121                                                        )
122                                         );
123
124                                 picture->set_entry_point (p->entry_point);
125                                 picture->set_duration (p->duration);
126                                 
127                         } catch (MXFFileError) {
128                                 if (require_mxfs) {
129                                         throw;
130                                 }
131                         }
132                         
133                 }
134                 
135                 if ((*i)->asset_list->main_sound) {
136                         
137                         try {
138                                 sound.reset (new SoundAsset (
139                                                      _directory,
140                                                      asset_map->asset_from_id ((*i)->asset_list->main_sound->id)->chunks.front()->path
141                                                      )
142                                         );
143
144                                 sound->set_entry_point ((*i)->asset_list->main_sound->entry_point);
145                                 sound->set_duration ((*i)->asset_list->main_sound->duration);
146                         } catch (MXFFileError) {
147                                 if (require_mxfs) {
148                                         throw;
149                                 }
150                         }
151                 }
152
153                 if ((*i)->asset_list->main_subtitle) {
154                         
155                         subtitle.reset (new SubtitleAsset (
156                                                 _directory,
157                                                 asset_map->asset_from_id ((*i)->asset_list->main_subtitle->id)->chunks.front()->path
158                                                 )
159                                 );
160
161                         subtitle->set_entry_point ((*i)->asset_list->main_subtitle->entry_point);
162                         subtitle->set_duration ((*i)->asset_list->main_subtitle->duration);
163                 }
164                         
165                 _reels.push_back (shared_ptr<Reel> (new Reel (picture, sound, subtitle)));
166         }
167 }
168
169 void
170 CPL::add_reel (shared_ptr<const Reel> reel)
171 {
172         _reels.push_back (reel);
173 }
174
175 void
176 CPL::write_xml (XMLMetadata const & metadata) const
177 {
178         boost::filesystem::path p;
179         p /= _directory;
180         stringstream s;
181         s << _uuid << "_cpl.xml";
182         p /= s.str();
183         ofstream os (p.string().c_str());
184         
185         os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
186            << "<CompositionPlaylist xmlns=\"http://www.smpte-ra.org/schemas/429-7/2006/CPL\">\n"
187            << "  <Id>urn:uuid:" << _uuid << "</Id>\n"
188            << "  <AnnotationText>" << _name << "</AnnotationText>\n"
189            << "  <IssueDate>" << metadata.issue_date << "</IssueDate>\n"
190            << "  <Creator>" << metadata.creator << "</Creator>\n"
191            << "  <ContentTitleText>" << _name << "</ContentTitleText>\n"
192            << "  <ContentKind>" << content_kind_to_string (_content_kind) << "</ContentKind>\n"
193            << "  <ContentVersion>\n"
194            << "    <Id>urn:uri:" << _uuid << "_" << metadata.issue_date << "</Id>\n"
195            << "    <LabelText>" << _uuid << "_" << metadata.issue_date << "</LabelText>\n"
196            << "  </ContentVersion>\n"
197            << "  <RatingList/>\n"
198            << "  <ReelList>\n";
199         
200         for (list<shared_ptr<const Reel> >::const_iterator i = _reels.begin(); i != _reels.end(); ++i) {
201                 (*i)->write_to_cpl (os);
202         }
203
204         os << "  </ReelList>\n"
205            << "</CompositionPlaylist>\n";
206
207         os.close ();
208
209         _digest = make_digest (p.string ());
210         _length = boost::filesystem::file_size (p.string ());
211 }
212
213 void
214 CPL::write_to_pkl (ostream& s) const
215 {
216         s << "    <Asset>\n"
217           << "      <Id>urn:uuid:" << _uuid << "</Id>\n"
218           << "      <Hash>" << _digest << "</Hash>\n"
219           << "      <Size>" << _length << "</Size>\n"
220           << "      <Type>text/xml</Type>\n"
221           << "    </Asset>\n";
222 }
223
224 list<shared_ptr<const Asset> >
225 CPL::assets () const
226 {
227         list<shared_ptr<const Asset> > a;
228         for (list<shared_ptr<const Reel> >::const_iterator i = _reels.begin(); i != _reels.end(); ++i) {
229                 if ((*i)->main_picture ()) {
230                         a.push_back ((*i)->main_picture ());
231                 }
232                 if ((*i)->main_sound ()) {
233                         a.push_back ((*i)->main_sound ());
234                 }
235                 if ((*i)->main_subtitle ()) {
236                         a.push_back ((*i)->main_subtitle ());
237                 }
238         }
239
240         return a;
241 }
242
243 void
244 CPL::write_to_assetmap (ostream& s) const
245 {
246         s << "    <Asset>\n"
247           << "      <Id>urn:uuid:" << _uuid << "</Id>\n"
248           << "      <ChunkList>\n"
249           << "        <Chunk>\n"
250           << "          <Path>" << _uuid << "_cpl.xml</Path>\n"
251           << "          <VolumeIndex>1</VolumeIndex>\n"
252           << "          <Offset>0</Offset>\n"
253           << "          <Length>" << _length << "</Length>\n"
254           << "        </Chunk>\n"
255           << "      </ChunkList>\n"
256           << "    </Asset>\n";
257 }
258         
259         
260         
261 bool
262 CPL::equals (CPL const & other, EqualityOptions opt, boost::function<void (NoteType, string)> note) const
263 {
264         if (_name != other._name) {
265                 note (ERROR, "names differ");
266                 return false;
267         }
268
269         if (_content_kind != other._content_kind) {
270                 note (ERROR, "content kinds differ");
271                 return false;
272         }
273
274         if (_fps != other._fps) {
275                 note (ERROR, "frames per second differ");
276                 return false;
277         }
278
279         if (_length != other._length) {
280                 note (ERROR, "lengths differ");
281                 return false;
282         }
283
284         if (_reels.size() != other._reels.size()) {
285                 note (ERROR, "reel counts differ");
286                 return false;
287         }
288         
289         list<shared_ptr<const Reel> >::const_iterator a = _reels.begin ();
290         list<shared_ptr<const Reel> >::const_iterator b = other._reels.begin ();
291         
292         while (a != _reels.end ()) {
293                 if (!(*a)->equals (*b, opt, note)) {
294                         return false;
295                 }
296                 ++a;
297                 ++b;
298         }
299
300         return true;
301 }