Merge master.
[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         setup_default_colour_conversion ();
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         setup_default_colour_conversion ();
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         setup_default_colour_conversion ();
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         _colour_conversion = ColourConversion (node->node_child ("ColourConversion"));
121         if (version >= 32) {
122                 _fade_in = ContentTime (node->number_child<int64_t> ("FadeIn"));
123                 _fade_out = ContentTime (node->number_child<int64_t> ("FadeOut"));
124         }
125 }
126
127 VideoContent::VideoContent (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
128         : Content (f, c)
129         , _video_length (0)
130 {
131         shared_ptr<VideoContent> ref = dynamic_pointer_cast<VideoContent> (c[0]);
132         assert (ref);
133
134         for (size_t i = 0; i < c.size(); ++i) {
135                 shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (c[i]);
136
137                 if (vc->video_size() != ref->video_size()) {
138                         throw JoinError (_("Content to be joined must have the same picture size."));
139                 }
140
141                 if (vc->video_frame_rate() != ref->video_frame_rate()) {
142                         throw JoinError (_("Content to be joined must have the same video frame rate."));
143                 }
144
145                 if (vc->video_frame_type() != ref->video_frame_type()) {
146                         throw JoinError (_("Content to be joined must have the same video frame type."));
147                 }
148
149                 if (vc->crop() != ref->crop()) {
150                         throw JoinError (_("Content to be joined must have the same crop."));
151                 }
152
153                 if (vc->scale() != ref->scale()) {
154                         throw JoinError (_("Content to be joined must have the same scale setting."));
155                 }
156
157                 if (vc->colour_conversion() != ref->colour_conversion()) {
158                         throw JoinError (_("Content to be joined must have the same colour conversion."));
159                 }
160
161                 if (vc->fade_in() != ref->fade_in() || vc->fade_out() != ref->fade_out()) {
162                         throw JoinError (_("Content to be joined must have the same fades."));
163                 }
164                 
165                 _video_length += vc->video_length ();
166         }
167
168         _video_size = ref->video_size ();
169         _video_frame_rate = ref->video_frame_rate ();
170         _video_frame_type = ref->video_frame_type ();
171         _crop = ref->crop ();
172         _scale = ref->scale ();
173         _colour_conversion = ref->colour_conversion ();
174         _fade_in = ref->fade_in ();
175         _fade_out = ref->fade_out ();
176 }
177
178 void
179 VideoContent::as_xml (xmlpp::Node* node) const
180 {
181         boost::mutex::scoped_lock lm (_mutex);
182         node->add_child("VideoLength")->add_child_text (raw_convert<string> (_video_length.get ()));
183         node->add_child("VideoWidth")->add_child_text (raw_convert<string> (_video_size.width));
184         node->add_child("VideoHeight")->add_child_text (raw_convert<string> (_video_size.height));
185         node->add_child("VideoFrameRate")->add_child_text (raw_convert<string> (_video_frame_rate));
186         node->add_child("VideoFrameType")->add_child_text (raw_convert<string> (static_cast<int> (_video_frame_type)));
187         _crop.as_xml (node);
188         _scale.as_xml (node->add_child("Scale"));
189         _colour_conversion.as_xml (node->add_child("ColourConversion"));
190         node->add_child("FadeIn")->add_child_text (raw_convert<string> (_fade_in.get ()));
191         node->add_child("FadeOut")->add_child_text (raw_convert<string> (_fade_out.get ()));
192 }
193
194 void
195 VideoContent::setup_default_colour_conversion ()
196 {
197         _colour_conversion = PresetColourConversion (_("sRGB"), 2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6).conversion;
198 }
199
200 void
201 VideoContent::take_from_video_examiner (shared_ptr<VideoExaminer> d)
202 {
203         /* These examiner calls could call other content methods which take a lock on the mutex */
204         dcp::Size const vs = d->video_size ();
205         float const vfr = d->video_frame_rate ();
206         ContentTime vl = d->video_length ();
207
208         {
209                 boost::mutex::scoped_lock lm (_mutex);
210                 _video_size = vs;
211                 _video_frame_rate = vfr;
212                 _video_length = vl;
213         }
214
215         shared_ptr<const Film> film = _film.lock ();
216         assert (film);
217         LOG_GENERAL ("Video length obtained from header as %1 frames", _video_length.frames (_video_frame_rate));
218         
219         signal_changed (VideoContentProperty::VIDEO_SIZE);
220         signal_changed (VideoContentProperty::VIDEO_FRAME_RATE);
221         signal_changed (ContentProperty::LENGTH);
222 }
223
224
225 string
226 VideoContent::information () const
227 {
228         if (video_size().width == 0 || video_size().height == 0) {
229                 return "";
230         }
231         
232         SafeStringStream s;
233
234         s << String::compose (
235                 _("%1x%2 pixels (%3:1)"),
236                 video_size().width,
237                 video_size().height,
238                 setprecision (3), video_size().ratio ()
239                 );
240         
241         return s.str ();
242 }
243
244 void
245 VideoContent::set_left_crop (int c)
246 {
247         {
248                 boost::mutex::scoped_lock lm (_mutex);
249                 
250                 if (_crop.left == c) {
251                         return;
252                 }
253                 
254                 _crop.left = c;
255         }
256         
257         signal_changed (VideoContentProperty::VIDEO_CROP);
258 }
259
260 void
261 VideoContent::set_right_crop (int c)
262 {
263         {
264                 boost::mutex::scoped_lock lm (_mutex);
265                 if (_crop.right == c) {
266                         return;
267                 }
268                 
269                 _crop.right = c;
270         }
271         
272         signal_changed (VideoContentProperty::VIDEO_CROP);
273 }
274
275 void
276 VideoContent::set_top_crop (int c)
277 {
278         {
279                 boost::mutex::scoped_lock lm (_mutex);
280                 if (_crop.top == c) {
281                         return;
282                 }
283                 
284                 _crop.top = c;
285         }
286         
287         signal_changed (VideoContentProperty::VIDEO_CROP);
288 }
289
290 void
291 VideoContent::set_bottom_crop (int c)
292 {
293         {
294                 boost::mutex::scoped_lock lm (_mutex);
295                 if (_crop.bottom == c) {
296                         return;
297                 }
298                 
299                 _crop.bottom = c;
300         }
301
302         signal_changed (VideoContentProperty::VIDEO_CROP);
303 }
304
305 void
306 VideoContent::set_scale (VideoContentScale s)
307 {
308         {
309                 boost::mutex::scoped_lock lm (_mutex);
310                 if (_scale == s) {
311                         return;
312                 }
313
314                 _scale = s;
315         }
316
317         signal_changed (VideoContentProperty::VIDEO_SCALE);
318 }
319
320 /** @return string which includes everything about how this content looks */
321 string
322 VideoContent::identifier () const
323 {
324         SafeStringStream s;
325         s << Content::identifier()
326           << "_" << crop().left
327           << "_" << crop().right
328           << "_" << crop().top
329           << "_" << crop().bottom
330           << "_" << scale().id()
331           << "_" << colour_conversion().identifier ();
332
333         return s.str ();
334 }
335
336 void
337 VideoContent::set_video_frame_type (VideoFrameType t)
338 {
339         {
340                 boost::mutex::scoped_lock lm (_mutex);
341                 _video_frame_type = t;
342         }
343
344         signal_changed (VideoContentProperty::VIDEO_FRAME_TYPE);
345 }
346
347 string
348 VideoContent::technical_summary () const
349 {
350         return String::compose (
351                 "video: length %1, size %2x%3, rate %4",
352                 video_length_after_3d_combine().seconds(),
353                 video_size().width,
354                 video_size().height,
355                 video_frame_rate()
356                 );
357 }
358
359 dcp::Size
360 VideoContent::video_size_after_3d_split () const
361 {
362         dcp::Size const s = video_size ();
363         switch (video_frame_type ()) {
364         case VIDEO_FRAME_TYPE_2D:
365         case VIDEO_FRAME_TYPE_3D_ALTERNATE:
366         case VIDEO_FRAME_TYPE_3D_LEFT:
367         case VIDEO_FRAME_TYPE_3D_RIGHT:
368                 return s;
369         case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
370                 return dcp::Size (s.width / 2, s.height);
371         case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
372                 return dcp::Size (s.width, s.height / 2);
373         }
374
375         assert (false);
376 }
377
378 void
379 VideoContent::set_colour_conversion (ColourConversion c)
380 {
381         {
382                 boost::mutex::scoped_lock lm (_mutex);
383                 _colour_conversion = c;
384         }
385
386         signal_changed (VideoContentProperty::COLOUR_CONVERSION);
387 }
388
389 void
390 VideoContent::set_fade_in (ContentTime t)
391 {
392         {
393                 boost::mutex::scoped_lock lm (_mutex);
394                 _fade_in = t;
395         }
396
397         signal_changed (VideoContentProperty::VIDEO_FADE_IN);
398 }
399
400 void
401 VideoContent::set_fade_out (ContentTime t)
402 {
403         {
404                 boost::mutex::scoped_lock lm (_mutex);
405                 _fade_out = t;
406         }
407
408         signal_changed (VideoContentProperty::VIDEO_FADE_OUT);
409 }
410
411 /** @return Video size after 3D split and crop */
412 dcp::Size
413 VideoContent::video_size_after_crop () const
414 {
415         return crop().apply (video_size_after_3d_split ());
416 }
417
418 /** @param t A time offset from the start of this piece of content.
419  *  @return Corresponding time with respect to the content.
420  */
421 ContentTime
422 VideoContent::dcp_time_to_content_time (DCPTime t) const
423 {
424         shared_ptr<const Film> film = _film.lock ();
425         assert (film);
426         return ContentTime (t, FrameRateChange (video_frame_rate(), film->video_frame_rate()));
427 }
428
429 void
430 VideoContent::scale_and_crop_to_fit_width ()
431 {
432         shared_ptr<const Film> film = _film.lock ();
433         assert (film);
434
435         set_scale (VideoContentScale (film->container ()));
436
437         int const crop = max (0, int (video_size().height - double (film->frame_size().height) * video_size().width / film->frame_size().width));
438         set_top_crop (crop / 2);
439         set_bottom_crop (crop / 2);
440 }
441
442 void
443 VideoContent::scale_and_crop_to_fit_height ()
444 {
445         shared_ptr<const Film> film = _film.lock ();
446         assert (film);
447
448         set_scale (VideoContentScale (film->container ()));
449
450         int const crop = max (0, int (video_size().width - double (film->frame_size().width) * video_size().height / film->frame_size().height));
451         set_left_crop (crop / 2);
452         set_right_crop (crop / 2);
453 }
454
455 void
456 VideoContent::set_video_frame_rate (float r)
457 {
458         {
459                 boost::mutex::scoped_lock lm (_mutex);
460                 if (_video_frame_rate == r) {
461                         return;
462                 }
463                 
464                 _video_frame_rate = r;
465         }
466         
467         signal_changed (VideoContentProperty::VIDEO_FRAME_RATE);
468 }
469
470 optional<float>
471 VideoContent::fade (VideoFrame f) const
472 {
473         assert (f >= 0);
474         
475         if (f < fade_in().frames (video_frame_rate ())) {
476                 return float (f) / _fade_in.frames (video_frame_rate ());
477         }
478
479         VideoFrame fade_out_start = ContentTime (video_length() - fade_out()).frames (video_frame_rate ());
480         if (f >= fade_out_start) {
481                 return 1 - float (f - fade_out_start) / fade_out().frames (video_frame_rate ());
482         }
483
484         return optional<float> ();
485 }