Clean up a bit by using Content::film() more.
[dcpomatic.git] / src / lib / dcp_content.cc
1 /*
2     Copyright (C) 2014-2015 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 "dcp_content.h"
21 #include "dcp_examiner.h"
22 #include "job.h"
23 #include "film.h"
24 #include "config.h"
25 #include "overlaps.h"
26 #include "compose.hpp"
27 #include "dcp_decoder.h"
28 #include <dcp/dcp.h>
29 #include <dcp/exceptions.h>
30 #include <dcp/reel_picture_asset.h>
31 #include <dcp/reel.h>
32 #include <libxml++/libxml++.h>
33 #include <boost/foreach.hpp>
34 #include <iterator>
35 #include <iostream>
36
37 #include "i18n.h"
38
39 using std::string;
40 using std::cout;
41 using std::distance;
42 using std::pair;
43 using std::list;
44 using boost::shared_ptr;
45 using boost::scoped_ptr;
46 using boost::optional;
47
48 int const DCPContentProperty::CAN_BE_PLAYED      = 600;
49 int const DCPContentProperty::REFERENCE_VIDEO    = 601;
50 int const DCPContentProperty::REFERENCE_AUDIO    = 602;
51 int const DCPContentProperty::REFERENCE_SUBTITLE = 603;
52
53 DCPContent::DCPContent (shared_ptr<const Film> film, boost::filesystem::path p)
54         : Content (film)
55         , VideoContent (film)
56         , SingleStreamAudioContent (film)
57         , SubtitleContent (film)
58         , _has_subtitles (false)
59         , _encrypted (false)
60         , _kdm_valid (false)
61         , _reference_video (false)
62         , _reference_audio (false)
63         , _reference_subtitle (false)
64 {
65         read_directory (p);
66 }
67
68 DCPContent::DCPContent (shared_ptr<const Film> film, cxml::ConstNodePtr node, int version)
69         : Content (film, node)
70         , VideoContent (film, node, version)
71         , SingleStreamAudioContent (film, node, version)
72         , SubtitleContent (film, node, version)
73 {
74         _name = node->string_child ("Name");
75         _has_subtitles = node->bool_child ("HasSubtitles");
76         _encrypted = node->bool_child ("Encrypted");
77         if (node->optional_node_child ("KDM")) {
78                 _kdm = dcp::EncryptedKDM (node->string_child ("KDM"));
79         }
80         _kdm_valid = node->bool_child ("KDMValid");
81         _reference_video = node->optional_bool_child ("ReferenceVideo").get_value_or (false);
82         _reference_audio = node->optional_bool_child ("ReferenceAudio").get_value_or (false);
83         _reference_subtitle = node->optional_bool_child ("ReferenceSubtitle").get_value_or (false);
84 }
85
86 void
87 DCPContent::read_directory (boost::filesystem::path p)
88 {
89         for (boost::filesystem::directory_iterator i(p); i != boost::filesystem::directory_iterator(); ++i) {
90                 if (boost::filesystem::is_regular_file (i->path ())) {
91                         _paths.push_back (i->path ());
92                 } else if (boost::filesystem::is_directory (i->path ())) {
93                         read_directory (i->path ());
94                 }
95         }
96 }
97
98 void
99 DCPContent::examine (shared_ptr<Job> job)
100 {
101         bool const could_be_played = can_be_played ();
102
103         job->set_progress_unknown ();
104         Content::examine (job);
105
106         shared_ptr<DCPExaminer> examiner (new DCPExaminer (shared_from_this ()));
107         take_from_video_examiner (examiner);
108         take_from_audio_examiner (examiner);
109
110         {
111                 boost::mutex::scoped_lock lm (_mutex);
112                 _name = examiner->name ();
113                 _has_subtitles = examiner->has_subtitles ();
114                 _encrypted = examiner->encrypted ();
115                 _kdm_valid = examiner->kdm_valid ();
116         }
117
118         if (could_be_played != can_be_played ()) {
119                 signal_changed (DCPContentProperty::CAN_BE_PLAYED);
120         }
121 }
122
123 string
124 DCPContent::summary () const
125 {
126         boost::mutex::scoped_lock lm (_mutex);
127         return String::compose (_("%1 [DCP]"), _name);
128 }
129
130 string
131 DCPContent::technical_summary () const
132 {
133         return Content::technical_summary() + " - "
134                 + VideoContent::technical_summary() + " - "
135                 + AudioContent::technical_summary() + " - ";
136 }
137
138 void
139 DCPContent::as_xml (xmlpp::Node* node) const
140 {
141         node->add_child("Type")->add_child_text ("DCP");
142
143         Content::as_xml (node);
144         VideoContent::as_xml (node);
145         SingleStreamAudioContent::as_xml (node);
146         SubtitleContent::as_xml (node);
147
148         boost::mutex::scoped_lock lm (_mutex);
149         node->add_child("Name")->add_child_text (_name);
150         node->add_child("HasSubtitles")->add_child_text (_has_subtitles ? "1" : "0");
151         node->add_child("Encrypted")->add_child_text (_encrypted ? "1" : "0");
152         if (_kdm) {
153                 node->add_child("KDM")->add_child_text (_kdm->as_xml ());
154         }
155         node->add_child("KDMValid")->add_child_text (_kdm_valid ? "1" : "0");
156         node->add_child("ReferenceVideo")->add_child_text (_reference_video ? "1" : "0");
157         node->add_child("ReferenceAudio")->add_child_text (_reference_audio ? "1" : "0");
158         node->add_child("ReferenceSubtitle")->add_child_text (_reference_subtitle ? "1" : "0");
159 }
160
161 DCPTime
162 DCPContent::full_length () const
163 {
164         FrameRateChange const frc (video_frame_rate (), film()->video_frame_rate ());
165         return DCPTime::from_frames (llrint (video_length () * frc.factor ()), film()->video_frame_rate ());
166 }
167
168 string
169 DCPContent::identifier () const
170 {
171         SafeStringStream s;
172         s << VideoContent::identifier() << "_" << SubtitleContent::identifier () << " "
173           << (_reference_video ? "1" : "0")
174           << (_reference_subtitle ? "1" : "0");
175         return s.str ();
176 }
177
178 void
179 DCPContent::add_kdm (dcp::EncryptedKDM k)
180 {
181         _kdm = k;
182 }
183
184 bool
185 DCPContent::can_be_played () const
186 {
187         boost::mutex::scoped_lock lm (_mutex);
188         return !_encrypted || _kdm_valid;
189 }
190
191 boost::filesystem::path
192 DCPContent::directory () const
193 {
194         optional<size_t> smallest;
195         boost::filesystem::path dir;
196         for (size_t i = 0; i < number_of_paths(); ++i) {
197                 boost::filesystem::path const p = path (i).parent_path ();
198                 size_t const d = distance (p.begin(), p.end());
199                 if (!smallest || d < smallest.get ()) {
200                         dir = p;
201                 }
202         }
203
204         return dir;
205 }
206
207 void
208 DCPContent::add_properties (list<pair<string, string> >& p) const
209 {
210         SingleStreamAudioContent::add_properties (p);
211 }
212
213 void
214 DCPContent::set_default_colour_conversion ()
215 {
216         /* Default to no colour conversion for DCPs */
217         unset_colour_conversion ();
218 }
219
220 void
221 DCPContent::set_reference_video (bool r)
222 {
223         {
224                 boost::mutex::scoped_lock lm (_mutex);
225                 _reference_video = r;
226         }
227
228         signal_changed (DCPContentProperty::REFERENCE_VIDEO);
229 }
230
231 void
232 DCPContent::set_reference_audio (bool r)
233 {
234         {
235                 boost::mutex::scoped_lock lm (_mutex);
236                 _reference_audio = r;
237         }
238
239         signal_changed (DCPContentProperty::REFERENCE_AUDIO);
240 }
241
242 void
243 DCPContent::set_reference_subtitle (bool r)
244 {
245         {
246                 boost::mutex::scoped_lock lm (_mutex);
247                 _reference_subtitle = r;
248         }
249
250         signal_changed (DCPContentProperty::REFERENCE_SUBTITLE);
251 }
252
253 list<DCPTimePeriod>
254 DCPContent::reels () const
255 {
256         list<DCPTimePeriod> p;
257         scoped_ptr<DCPDecoder> decoder;
258         try {
259                 decoder.reset (new DCPDecoder (shared_from_this(), false));
260         } catch (...) {
261                 /* Could not load the DCP; guess reels */
262                 list<DCPTimePeriod> p;
263                 p.push_back (DCPTimePeriod (position(), end()));
264                 return p;
265         }
266
267         DCPTime from = position ();
268         BOOST_FOREACH (shared_ptr<dcp::Reel> i, decoder->reels()) {
269                 DCPTime const to = from + DCPTime::from_frames (i->main_picture()->duration(), film()->video_frame_rate());
270                 p.push_back (DCPTimePeriod (from, to));
271                 from = to;
272         }
273
274         return p;
275 }
276
277 list<DCPTime>
278 DCPContent::reel_split_points () const
279 {
280         list<DCPTime> s;
281         BOOST_FOREACH (DCPTimePeriod i, reels()) {
282                 s.push_back (i.from);
283         }
284         return s;
285 }
286
287 template <class T>
288 bool
289 DCPContent::can_reference (string overlapping, list<string>& why_not) const
290 {
291         list<DCPTimePeriod> const fr = film()->reels ();
292         /* fr must contain reels().  It can also contain other reels, but it must at
293            least contain reels().
294         */
295         BOOST_FOREACH (DCPTimePeriod i, reels()) {
296                 if (find (fr.begin(), fr.end(), i) == fr.end ()) {
297                         why_not.push_back (_("Reel lengths in the project differ from those in the DCP; set the reel mode to `split by video content'."));
298                         return false;
299                 }
300         }
301
302         list<shared_ptr<T> > a = overlaps<T> (film()->content(), position(), end());
303         if (a.size() != 1 || a.front().get() != this) {
304                 why_not.push_back (overlapping);
305                 return false;
306         }
307
308         return true;
309 }
310
311 bool
312 DCPContent::can_reference_video (list<string>& why_not) const
313 {
314         return can_reference<VideoContent> (_("There is other video content overlapping this DCP; remove it."), why_not);
315 }
316
317 bool
318 DCPContent::can_reference_audio (list<string>& why_not) const
319 {
320         DCPDecoder decoder (shared_from_this(), false);
321         BOOST_FOREACH (shared_ptr<dcp::Reel> i, decoder.reels()) {
322                 if (!i->main_sound()) {
323                         why_not.push_back (_("The DCP does not have sound in all reels."));
324                         return false;
325                 }
326         }
327
328         return can_reference<AudioContent> (_("There is other audio content overlapping this DCP; remove it."), why_not);
329 }
330
331 bool
332 DCPContent::can_reference_subtitle (list<string>& why_not) const
333 {
334         DCPDecoder decoder (shared_from_this(), false);
335         BOOST_FOREACH (shared_ptr<dcp::Reel> i, decoder.reels()) {
336                 if (!i->main_subtitle()) {
337                         why_not.push_back (_("The DCP does not have subtitles in all reels."));
338                         return false;
339                 }
340         }
341
342         return can_reference<SubtitleContent> (_("There is other subtitle content overlapping this DCP; remove it."), why_not);
343 }