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
48 using std::string;
49 using std::setprecision;
50 using std::cout;
51 using std::vector;
52 using std::min;
53 using std::max;
54 using boost::shared_ptr;
55 using boost::optional;
56 using boost::dynamic_pointer_cast;
57 using dcp::raw_convert;
58
59 vector<VideoContentScale> VideoContentScale::_scales;
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 }
122
123 VideoContent::VideoContent (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
124         : Content (f, c)
125         , _video_length (0)
126 {
127         shared_ptr<VideoContent> ref = dynamic_pointer_cast<VideoContent> (c[0]);
128         assert (ref);
129
130         for (size_t i = 0; i < c.size(); ++i) {
131                 shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (c[i]);
132
133                 if (vc->video_size() != ref->video_size()) {
134                         throw JoinError (_("Content to be joined must have the same picture size."));
135                 }
136
137                 if (vc->video_frame_rate() != ref->video_frame_rate()) {
138                         throw JoinError (_("Content to be joined must have the same video frame rate."));
139                 }
140
141                 if (vc->video_frame_type() != ref->video_frame_type()) {
142                         throw JoinError (_("Content to be joined must have the same video frame type."));
143                 }
144
145                 if (vc->crop() != ref->crop()) {
146                         throw JoinError (_("Content to be joined must have the same crop."));
147                 }
148
149                 if (vc->scale() != ref->scale()) {
150                         throw JoinError (_("Content to be joined must have the same scale setting."));
151                 }
152
153                 if (vc->colour_conversion() != ref->colour_conversion()) {
154                         throw JoinError (_("Content to be joined must have the same colour conversion."));
155                 }
156
157                 _video_length += vc->video_length ();
158         }
159
160         _video_size = ref->video_size ();
161         _video_frame_rate = ref->video_frame_rate ();
162         _video_frame_type = ref->video_frame_type ();
163         _crop = ref->crop ();
164         _scale = ref->scale ();
165         _colour_conversion = ref->colour_conversion ();
166 }
167
168 void
169 VideoContent::as_xml (xmlpp::Node* node) const
170 {
171         boost::mutex::scoped_lock lm (_mutex);
172         node->add_child("VideoLength")->add_child_text (raw_convert<string> (_video_length.get ()));
173         node->add_child("VideoWidth")->add_child_text (raw_convert<string> (_video_size.width));
174         node->add_child("VideoHeight")->add_child_text (raw_convert<string> (_video_size.height));
175         node->add_child("VideoFrameRate")->add_child_text (raw_convert<string> (_video_frame_rate));
176         node->add_child("VideoFrameType")->add_child_text (raw_convert<string> (static_cast<int> (_video_frame_type)));
177         _crop.as_xml (node);
178         _scale.as_xml (node->add_child("Scale"));
179         _colour_conversion.as_xml (node->add_child("ColourConversion"));
180 }
181
182 void
183 VideoContent::setup_default_colour_conversion ()
184 {
185         _colour_conversion = PresetColourConversion (_("sRGB"), 2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6).conversion;
186 }
187
188 void
189 VideoContent::take_from_video_examiner (shared_ptr<VideoExaminer> d)
190 {
191         /* These examiner calls could call other content methods which take a lock on the mutex */
192         dcp::Size const vs = d->video_size ();
193         float const vfr = d->video_frame_rate ();
194         ContentTime vl = d->video_length ();
195
196         {
197                 boost::mutex::scoped_lock lm (_mutex);
198                 _video_size = vs;
199                 _video_frame_rate = vfr;
200                 _video_length = vl;
201         }
202
203         shared_ptr<const Film> film = _film.lock ();
204         assert (film);
205         LOG_GENERAL ("Video length obtained from header as %1 frames", _video_length.frames (_video_frame_rate));
206         
207         signal_changed (VideoContentProperty::VIDEO_SIZE);
208         signal_changed (VideoContentProperty::VIDEO_FRAME_RATE);
209         signal_changed (ContentProperty::LENGTH);
210 }
211
212
213 string
214 VideoContent::information () const
215 {
216         if (video_size().width == 0 || video_size().height == 0) {
217                 return "";
218         }
219         
220         SafeStringStream s;
221
222         s << String::compose (
223                 _("%1x%2 pixels (%3:1)"),
224                 video_size().width,
225                 video_size().height,
226                 setprecision (3), video_size().ratio ()
227                 );
228         
229         return s.str ();
230 }
231
232 void
233 VideoContent::set_left_crop (int c)
234 {
235         {
236                 boost::mutex::scoped_lock lm (_mutex);
237                 
238                 if (_crop.left == c) {
239                         return;
240                 }
241                 
242                 _crop.left = c;
243         }
244         
245         signal_changed (VideoContentProperty::VIDEO_CROP);
246 }
247
248 void
249 VideoContent::set_right_crop (int c)
250 {
251         {
252                 boost::mutex::scoped_lock lm (_mutex);
253                 if (_crop.right == c) {
254                         return;
255                 }
256                 
257                 _crop.right = c;
258         }
259         
260         signal_changed (VideoContentProperty::VIDEO_CROP);
261 }
262
263 void
264 VideoContent::set_top_crop (int c)
265 {
266         {
267                 boost::mutex::scoped_lock lm (_mutex);
268                 if (_crop.top == c) {
269                         return;
270                 }
271                 
272                 _crop.top = c;
273         }
274         
275         signal_changed (VideoContentProperty::VIDEO_CROP);
276 }
277
278 void
279 VideoContent::set_bottom_crop (int c)
280 {
281         {
282                 boost::mutex::scoped_lock lm (_mutex);
283                 if (_crop.bottom == c) {
284                         return;
285                 }
286                 
287                 _crop.bottom = c;
288         }
289
290         signal_changed (VideoContentProperty::VIDEO_CROP);
291 }
292
293 void
294 VideoContent::set_scale (VideoContentScale s)
295 {
296         {
297                 boost::mutex::scoped_lock lm (_mutex);
298                 if (_scale == s) {
299                         return;
300                 }
301
302                 _scale = s;
303         }
304
305         signal_changed (VideoContentProperty::VIDEO_SCALE);
306 }
307
308 /** @return string which includes everything about how this content looks */
309 string
310 VideoContent::identifier () const
311 {
312         SafeStringStream s;
313         s << Content::identifier()
314           << "_" << crop().left
315           << "_" << crop().right
316           << "_" << crop().top
317           << "_" << crop().bottom
318           << "_" << scale().id()
319           << "_" << colour_conversion().identifier ();
320
321         return s.str ();
322 }
323
324 void
325 VideoContent::set_video_frame_type (VideoFrameType t)
326 {
327         {
328                 boost::mutex::scoped_lock lm (_mutex);
329                 _video_frame_type = t;
330         }
331
332         signal_changed (VideoContentProperty::VIDEO_FRAME_TYPE);
333 }
334
335 string
336 VideoContent::technical_summary () const
337 {
338         return String::compose (
339                 "video: length %1, size %2x%3, rate %4",
340                 video_length_after_3d_combine().seconds(),
341                 video_size().width,
342                 video_size().height,
343                 video_frame_rate()
344                 );
345 }
346
347 dcp::Size
348 VideoContent::video_size_after_3d_split () const
349 {
350         dcp::Size const s = video_size ();
351         switch (video_frame_type ()) {
352         case VIDEO_FRAME_TYPE_2D:
353         case VIDEO_FRAME_TYPE_3D_ALTERNATE:
354         case VIDEO_FRAME_TYPE_3D_LEFT:
355         case VIDEO_FRAME_TYPE_3D_RIGHT:
356                 return s;
357         case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
358                 return dcp::Size (s.width / 2, s.height);
359         case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
360                 return dcp::Size (s.width, s.height / 2);
361         }
362
363         assert (false);
364 }
365
366 void
367 VideoContent::set_colour_conversion (ColourConversion c)
368 {
369         {
370                 boost::mutex::scoped_lock lm (_mutex);
371                 _colour_conversion = c;
372         }
373
374         signal_changed (VideoContentProperty::COLOUR_CONVERSION);
375 }
376
377 /** @return Video size after 3D split and crop */
378 dcp::Size
379 VideoContent::video_size_after_crop () const
380 {
381         return crop().apply (video_size_after_3d_split ());
382 }
383
384 /** @param t A time offset from the start of this piece of content.
385  *  @return Corresponding time with respect to the content.
386  */
387 ContentTime
388 VideoContent::dcp_time_to_content_time (DCPTime t) const
389 {
390         shared_ptr<const Film> film = _film.lock ();
391         assert (film);
392         return ContentTime (t, FrameRateChange (video_frame_rate(), film->video_frame_rate()));
393 }
394
395 void
396 VideoContent::scale_and_crop_to_fit_width ()
397 {
398         shared_ptr<const Film> film = _film.lock ();
399         assert (film);
400
401         set_scale (VideoContentScale (film->container ()));
402
403         int const crop = max (0, int (video_size().height - double (film->frame_size().height) * video_size().width / film->frame_size().width));
404         set_top_crop (crop / 2);
405         set_bottom_crop (crop / 2);
406 }
407
408 void
409 VideoContent::scale_and_crop_to_fit_height ()
410 {
411         shared_ptr<const Film> film = _film.lock ();
412         assert (film);
413
414         set_scale (VideoContentScale (film->container ()));
415
416         int const crop = max (0, int (video_size().width - double (film->frame_size().width) * video_size().height / film->frame_size().height));
417         set_left_crop (crop / 2);
418         set_right_crop (crop / 2);
419 }
420
421 void
422 VideoContent::set_video_frame_rate (float r)
423 {
424         {
425                 boost::mutex::scoped_lock lm (_mutex);
426                 if (_video_frame_rate == r) {
427                         return;
428                 }
429                 
430                 _video_frame_rate = r;
431         }
432         
433         signal_changed (VideoContentProperty::VIDEO_FRAME_RATE);
434 }
435
436 VideoContentScale::VideoContentScale (Ratio const * r)
437         : _ratio (r)
438         , _scale (true)
439 {
440
441 }
442
443 VideoContentScale::VideoContentScale ()
444         : _ratio (0)
445         , _scale (false)
446 {
447
448 }
449
450 VideoContentScale::VideoContentScale (bool scale)
451         : _ratio (0)
452         , _scale (scale)
453 {
454
455 }
456
457 VideoContentScale::VideoContentScale (cxml::NodePtr node)
458         : _ratio (0)
459         , _scale (true)
460 {
461         optional<string> r = node->optional_string_child ("Ratio");
462         if (r) {
463                 _ratio = Ratio::from_id (r.get ());
464         } else {
465                 _scale = node->bool_child ("Scale");
466         }
467 }
468
469 void
470 VideoContentScale::as_xml (xmlpp::Node* node) const
471 {
472         if (_ratio) {
473                 node->add_child("Ratio")->add_child_text (_ratio->id ());
474         } else {
475                 node->add_child("Scale")->add_child_text (_scale ? "1" : "0");
476         }
477 }
478
479 string
480 VideoContentScale::id () const
481 {
482         SafeStringStream s;
483         
484         if (_ratio) {
485                 s << _ratio->id () << "_";
486         } else {
487                 s << (_scale ? "S1" : "S0");
488         }
489         
490         return s.str ();
491 }
492
493 string
494 VideoContentScale::name () const
495 {
496         if (_ratio) {
497                 return _ratio->nickname ();
498         }
499
500         if (_scale) {
501                 return _("No stretch");
502         }
503
504         return _("No scale");
505 }
506
507 /** @param display_container Size of the container that we are displaying this content in.
508  *  @param film_container The size of the film's image.
509  */
510 dcp::Size
511 VideoContentScale::size (shared_ptr<const VideoContent> c, dcp::Size display_container, dcp::Size film_container, int round) const
512 {
513         if (_ratio) {
514                 return fit_ratio_within (_ratio->ratio (), display_container, round);
515         }
516
517         dcp::Size const ac = c->video_size_after_crop ();
518
519         /* Force scale if the film_container is smaller than the content's image */
520         if (_scale || film_container.width < ac.width || film_container.height < ac.height) {
521                 return fit_ratio_within (ac.ratio (), display_container, 1);
522         }
523
524         /* Scale the image so that it will be in the right place in film_container, even if display_container is a
525            different size.
526         */
527         return dcp::Size (
528                 round_to (c->video_size().width  * float(display_container.width)  / film_container.width, round),
529                 round_to (c->video_size().height * float(display_container.height) / film_container.height, round)
530                 );
531 }
532
533 void
534 VideoContentScale::setup_scales ()
535 {
536         vector<Ratio const *> ratios = Ratio::all ();
537         for (vector<Ratio const *>::const_iterator i = ratios.begin(); i != ratios.end(); ++i) {
538                 _scales.push_back (VideoContentScale (*i));
539         }
540
541         _scales.push_back (VideoContentScale (true));
542         _scales.push_back (VideoContentScale (false));
543 }
544
545 bool
546 operator== (VideoContentScale const & a, VideoContentScale const & b)
547 {
548         return (a.ratio() == b.ratio() && a.scale() == b.scale());
549 }
550
551 bool
552 operator!= (VideoContentScale const & a, VideoContentScale const & b)
553 {
554         return (a.ratio() != b.ratio() || a.scale() != b.scale());
555 }