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