Fix crash when signalling VideoContent during the constructor (hence indirectly causi...
[dcpomatic.git] / src / lib / video_content.cc
1 /*
2     Copyright (C) 2013-2014 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 <iomanip>
21 #include <libcxml/cxml.h>
22 #include <dcp/colour_matrix.h>
23 #include <dcp/raw_convert.h>
24 #include "video_content.h"
25 #include "video_examiner.h"
26 #include "compose.hpp"
27 #include "ratio.h"
28 #include "config.h"
29 #include "colour_conversion.h"
30 #include "util.h"
31 #include "film.h"
32 #include "exceptions.h"
33 #include "frame_rate_change.h"
34 #include "log.h"
35 #include "safe_stringstream.h"
36
37 #include "i18n.h"
38
39 #define LOG_GENERAL(...) film->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
40
41 int const VideoContentProperty::VIDEO_SIZE        = 0;
42 int const VideoContentProperty::VIDEO_FRAME_RATE  = 1;
43 int const VideoContentProperty::VIDEO_FRAME_TYPE  = 2;
44 int const VideoContentProperty::VIDEO_CROP        = 3;
45 int const VideoContentProperty::VIDEO_SCALE       = 4;
46 int const VideoContentProperty::COLOUR_CONVERSION = 5;
47 int const VideoContentProperty::VIDEO_FADE_IN     = 6;
48 int const VideoContentProperty::VIDEO_FADE_OUT    = 7;
49
50 using std::string;
51 using std::setprecision;
52 using std::cout;
53 using std::vector;
54 using std::min;
55 using std::max;
56 using boost::shared_ptr;
57 using boost::optional;
58 using boost::dynamic_pointer_cast;
59 using dcp::raw_convert;
60
61 VideoContent::VideoContent (shared_ptr<const Film> f)
62         : Content (f)
63         , _video_length (0)
64         , _video_frame_rate (0)
65         , _video_frame_type (VIDEO_FRAME_TYPE_2D)
66         , _scale (Config::instance()->default_scale ())
67 {
68         set_default_colour_conversion (false);
69 }
70
71 VideoContent::VideoContent (shared_ptr<const Film> f, DCPTime s, ContentTime len)
72         : Content (f, s)
73         , _video_length (len)
74         , _video_frame_rate (0)
75         , _video_frame_type (VIDEO_FRAME_TYPE_2D)
76         , _scale (Config::instance()->default_scale ())
77 {
78         set_default_colour_conversion (false);
79 }
80
81 VideoContent::VideoContent (shared_ptr<const Film> f, boost::filesystem::path p)
82         : Content (f, p)
83         , _video_length (0)
84         , _video_frame_rate (0)
85         , _video_frame_type (VIDEO_FRAME_TYPE_2D)
86         , _scale (Config::instance()->default_scale ())
87 {
88         set_default_colour_conversion (false);
89 }
90
91 VideoContent::VideoContent (shared_ptr<const Film> f, cxml::ConstNodePtr node, int version)
92         : Content (f, node)
93 {
94         _video_size.width = node->number_child<int> ("VideoWidth");
95         _video_size.height = node->number_child<int> ("VideoHeight");
96         _video_frame_rate = node->number_child<float> ("VideoFrameRate");
97
98         if (version < 32) {
99                 /* DCP-o-matic 1.0 branch */
100                 _video_length = ContentTime::from_frames (node->number_child<int64_t> ("VideoLength"), _video_frame_rate);
101         } else {
102                 _video_length = ContentTime (node->number_child<ContentTime::Type> ("VideoLength"));
103         }
104         
105         _video_frame_type = static_cast<VideoFrameType> (node->number_child<int> ("VideoFrameType"));
106         _crop.left = node->number_child<int> ("LeftCrop");
107         _crop.right = node->number_child<int> ("RightCrop");
108         _crop.top = node->number_child<int> ("TopCrop");
109         _crop.bottom = node->number_child<int> ("BottomCrop");
110
111         if (version <= 7) {
112                 optional<string> r = node->optional_string_child ("Ratio");
113                 if (r) {
114                         _scale = VideoContentScale (Ratio::from_id (r.get ()));
115                 }
116         } else {
117                 _scale = VideoContentScale (node->node_child ("Scale"));
118         }
119
120         
121         if (node->optional_node_child ("ColourConversion")) {
122                 _colour_conversion = ColourConversion (node->node_child ("ColourConversion"));
123         }
124         if (version >= 32) {
125                 _fade_in = ContentTime (node->number_child<int64_t> ("FadeIn"));
126                 _fade_out = ContentTime (node->number_child<int64_t> ("FadeOut"));
127         }
128 }
129
130 VideoContent::VideoContent (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
131         : Content (f, c)
132         , _video_length (0)
133 {
134         shared_ptr<VideoContent> ref = dynamic_pointer_cast<VideoContent> (c[0]);
135         assert (ref);
136
137         for (size_t i = 0; i < c.size(); ++i) {
138                 shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (c[i]);
139
140                 if (vc->video_size() != ref->video_size()) {
141                         throw JoinError (_("Content to be joined must have the same picture size."));
142                 }
143
144                 if (vc->video_frame_rate() != ref->video_frame_rate()) {
145                         throw JoinError (_("Content to be joined must have the same video frame rate."));
146                 }
147
148                 if (vc->video_frame_type() != ref->video_frame_type()) {
149                         throw JoinError (_("Content to be joined must have the same video frame type."));
150                 }
151
152                 if (vc->crop() != ref->crop()) {
153                         throw JoinError (_("Content to be joined must have the same crop."));
154                 }
155
156                 if (vc->scale() != ref->scale()) {
157                         throw JoinError (_("Content to be joined must have the same scale setting."));
158                 }
159
160                 if (vc->colour_conversion() != ref->colour_conversion()) {
161                         throw JoinError (_("Content to be joined must have the same colour conversion."));
162                 }
163
164                 if (vc->fade_in() != ref->fade_in() || vc->fade_out() != ref->fade_out()) {
165                         throw JoinError (_("Content to be joined must have the same fades."));
166                 }
167                 
168                 _video_length += vc->video_length ();
169         }
170
171         _video_size = ref->video_size ();
172         _video_frame_rate = ref->video_frame_rate ();
173         _video_frame_type = ref->video_frame_type ();
174         _crop = ref->crop ();
175         _scale = ref->scale ();
176         _colour_conversion = ref->colour_conversion ();
177         _fade_in = ref->fade_in ();
178         _fade_out = ref->fade_out ();
179 }
180
181 void
182 VideoContent::as_xml (xmlpp::Node* node) const
183 {
184         boost::mutex::scoped_lock lm (_mutex);
185         node->add_child("VideoLength")->add_child_text (raw_convert<string> (_video_length.get ()));
186         node->add_child("VideoWidth")->add_child_text (raw_convert<string> (_video_size.width));
187         node->add_child("VideoHeight")->add_child_text (raw_convert<string> (_video_size.height));
188         node->add_child("VideoFrameRate")->add_child_text (raw_convert<string> (_video_frame_rate));
189         node->add_child("VideoFrameType")->add_child_text (raw_convert<string> (static_cast<int> (_video_frame_type)));
190         _crop.as_xml (node);
191         _scale.as_xml (node->add_child("Scale"));
192         if (_colour_conversion) {
193                 _colour_conversion.get().as_xml (node->add_child("ColourConversion"));
194         }
195         node->add_child("FadeIn")->add_child_text (raw_convert<string> (_fade_in.get ()));
196         node->add_child("FadeOut")->add_child_text (raw_convert<string> (_fade_out.get ()));
197 }
198
199 void
200 VideoContent::set_default_colour_conversion (bool signal)
201 {
202         {
203                 boost::mutex::scoped_lock lm (_mutex);
204                 _colour_conversion = PresetColourConversion (_("sRGB"), 2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6).conversion;
205         }
206
207         if (signal) {
208                 signal_changed (VideoContentProperty::COLOUR_CONVERSION);
209         }
210 }
211
212 void
213 VideoContent::take_from_video_examiner (shared_ptr<VideoExaminer> d)
214 {
215         /* These examiner calls could call other content methods which take a lock on the mutex */
216         dcp::Size const vs = d->video_size ();
217         float const vfr = d->video_frame_rate ();
218         ContentTime vl = d->video_length ();
219
220         {
221                 boost::mutex::scoped_lock lm (_mutex);
222                 _video_size = vs;
223                 _video_frame_rate = vfr;
224                 _video_length = vl;
225         }
226
227         shared_ptr<const Film> film = _film.lock ();
228         assert (film);
229         LOG_GENERAL ("Video length obtained from header as %1 frames", _video_length.frames (_video_frame_rate));
230         
231         signal_changed (VideoContentProperty::VIDEO_SIZE);
232         signal_changed (VideoContentProperty::VIDEO_FRAME_RATE);
233         signal_changed (ContentProperty::LENGTH);
234 }
235
236
237 string
238 VideoContent::information () const
239 {
240         if (video_size().width == 0 || video_size().height == 0) {
241                 return "";
242         }
243         
244         SafeStringStream s;
245
246         s << String::compose (
247                 _("%1x%2 pixels (%3:1)"),
248                 video_size().width,
249                 video_size().height,
250                 setprecision (3), video_size().ratio ()
251                 );
252         
253         return s.str ();
254 }
255
256 void
257 VideoContent::set_left_crop (int c)
258 {
259         {
260                 boost::mutex::scoped_lock lm (_mutex);
261                 
262                 if (_crop.left == c) {
263                         return;
264                 }
265                 
266                 _crop.left = c;
267         }
268         
269         signal_changed (VideoContentProperty::VIDEO_CROP);
270 }
271
272 void
273 VideoContent::set_right_crop (int c)
274 {
275         {
276                 boost::mutex::scoped_lock lm (_mutex);
277                 if (_crop.right == c) {
278                         return;
279                 }
280                 
281                 _crop.right = c;
282         }
283         
284         signal_changed (VideoContentProperty::VIDEO_CROP);
285 }
286
287 void
288 VideoContent::set_top_crop (int c)
289 {
290         {
291                 boost::mutex::scoped_lock lm (_mutex);
292                 if (_crop.top == c) {
293                         return;
294                 }
295                 
296                 _crop.top = c;
297         }
298         
299         signal_changed (VideoContentProperty::VIDEO_CROP);
300 }
301
302 void
303 VideoContent::set_bottom_crop (int c)
304 {
305         {
306                 boost::mutex::scoped_lock lm (_mutex);
307                 if (_crop.bottom == c) {
308                         return;
309                 }
310                 
311                 _crop.bottom = c;
312         }
313
314         signal_changed (VideoContentProperty::VIDEO_CROP);
315 }
316
317 void
318 VideoContent::set_scale (VideoContentScale s)
319 {
320         {
321                 boost::mutex::scoped_lock lm (_mutex);
322                 if (_scale == s) {
323                         return;
324                 }
325
326                 _scale = s;
327         }
328
329         signal_changed (VideoContentProperty::VIDEO_SCALE);
330 }
331
332 /** @return string which includes everything about how this content looks */
333 string
334 VideoContent::identifier () const
335 {
336         SafeStringStream s;
337         s << Content::identifier()
338           << "_" << crop().left
339           << "_" << crop().right
340           << "_" << crop().top
341           << "_" << crop().bottom
342           << "_" << scale().id();
343
344         if (colour_conversion()) {
345                 s << "_" << colour_conversion().get().identifier ();
346         }
347
348         return s.str ();
349 }
350
351 void
352 VideoContent::set_video_frame_type (VideoFrameType t)
353 {
354         {
355                 boost::mutex::scoped_lock lm (_mutex);
356                 _video_frame_type = t;
357         }
358
359         signal_changed (VideoContentProperty::VIDEO_FRAME_TYPE);
360 }
361
362 string
363 VideoContent::technical_summary () const
364 {
365         return String::compose (
366                 "video: length %1, size %2x%3, rate %4",
367                 video_length_after_3d_combine().seconds(),
368                 video_size().width,
369                 video_size().height,
370                 video_frame_rate()
371                 );
372 }
373
374 dcp::Size
375 VideoContent::video_size_after_3d_split () const
376 {
377         dcp::Size const s = video_size ();
378         switch (video_frame_type ()) {
379         case VIDEO_FRAME_TYPE_2D:
380         case VIDEO_FRAME_TYPE_3D_ALTERNATE:
381         case VIDEO_FRAME_TYPE_3D_LEFT:
382         case VIDEO_FRAME_TYPE_3D_RIGHT:
383                 return s;
384         case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
385                 return dcp::Size (s.width / 2, s.height);
386         case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
387                 return dcp::Size (s.width, s.height / 2);
388         }
389
390         assert (false);
391 }
392
393 void
394 VideoContent::unset_colour_conversion ()
395 {
396         {
397                 boost::mutex::scoped_lock lm (_mutex);
398                 _colour_conversion = boost::optional<ColourConversion> ();
399         }
400
401         signal_changed (VideoContentProperty::COLOUR_CONVERSION);
402 }
403
404 void
405 VideoContent::set_colour_conversion (ColourConversion c)
406 {
407         {
408                 boost::mutex::scoped_lock lm (_mutex);
409                 _colour_conversion = c;
410         }
411
412         signal_changed (VideoContentProperty::COLOUR_CONVERSION);
413 }
414
415 void
416 VideoContent::set_fade_in (ContentTime t)
417 {
418         {
419                 boost::mutex::scoped_lock lm (_mutex);
420                 _fade_in = t;
421         }
422
423         signal_changed (VideoContentProperty::VIDEO_FADE_IN);
424 }
425
426 void
427 VideoContent::set_fade_out (ContentTime t)
428 {
429         {
430                 boost::mutex::scoped_lock lm (_mutex);
431                 _fade_out = t;
432         }
433
434         signal_changed (VideoContentProperty::VIDEO_FADE_OUT);
435 }
436
437 /** @return Video size after 3D split and crop */
438 dcp::Size
439 VideoContent::video_size_after_crop () const
440 {
441         return crop().apply (video_size_after_3d_split ());
442 }
443
444 /** @param t A time offset from the start of this piece of content.
445  *  @return Corresponding time with respect to the content.
446  */
447 ContentTime
448 VideoContent::dcp_time_to_content_time (DCPTime t) const
449 {
450         shared_ptr<const Film> film = _film.lock ();
451         assert (film);
452         return ContentTime (t, FrameRateChange (video_frame_rate(), film->video_frame_rate()));
453 }
454
455 void
456 VideoContent::scale_and_crop_to_fit_width ()
457 {
458         shared_ptr<const Film> film = _film.lock ();
459         assert (film);
460
461         set_scale (VideoContentScale (film->container ()));
462
463         int const crop = max (0, int (video_size().height - double (film->frame_size().height) * video_size().width / film->frame_size().width));
464         set_top_crop (crop / 2);
465         set_bottom_crop (crop / 2);
466 }
467
468 void
469 VideoContent::scale_and_crop_to_fit_height ()
470 {
471         shared_ptr<const Film> film = _film.lock ();
472         assert (film);
473
474         set_scale (VideoContentScale (film->container ()));
475
476         int const crop = max (0, int (video_size().width - double (film->frame_size().width) * video_size().height / film->frame_size().height));
477         set_left_crop (crop / 2);
478         set_right_crop (crop / 2);
479 }
480
481 void
482 VideoContent::set_video_frame_rate (float r)
483 {
484         {
485                 boost::mutex::scoped_lock lm (_mutex);
486                 if (_video_frame_rate == r) {
487                         return;
488                 }
489                 
490                 _video_frame_rate = r;
491         }
492         
493         signal_changed (VideoContentProperty::VIDEO_FRAME_RATE);
494 }
495
496 optional<float>
497 VideoContent::fade (VideoFrame f) const
498 {
499         assert (f >= 0);
500         
501         if (f < fade_in().frames (video_frame_rate ())) {
502                 return float (f) / _fade_in.frames (video_frame_rate ());
503         }
504
505         VideoFrame fade_out_start = ContentTime (video_length() - fade_out()).frames (video_frame_rate ());
506         if (f >= fade_out_start) {
507                 return 1 - float (f - fade_out_start) / fade_out().frames (video_frame_rate ());
508         }
509
510         return optional<float> ();
511 }