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