Don't overlap simultaneous video content in the timeline. Fix keep-aligned for separ...
[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 (
321                 "video: length %1, size %2x%3, rate %4",
322                 video_length_after_3d_combine(), video_size().width, video_size().height, video_frame_rate()
323                 );
324 }
325
326 libdcp::Size
327 VideoContent::video_size_after_3d_split () const
328 {
329         libdcp::Size const s = video_size ();
330         switch (video_frame_type ()) {
331         case VIDEO_FRAME_TYPE_2D:
332         case VIDEO_FRAME_TYPE_3D_ALTERNATE:
333         case VIDEO_FRAME_TYPE_3D_LEFT:
334         case VIDEO_FRAME_TYPE_3D_RIGHT:
335                 return s;
336         case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
337                 return libdcp::Size (s.width / 2, s.height);
338         case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
339                 return libdcp::Size (s.width, s.height / 2);
340         }
341
342         assert (false);
343 }
344
345 void
346 VideoContent::set_colour_conversion (ColourConversion c)
347 {
348         {
349                 boost::mutex::scoped_lock lm (_mutex);
350                 _colour_conversion = c;
351         }
352
353         signal_changed (VideoContentProperty::COLOUR_CONVERSION);
354 }
355
356 /** @return Video size after 3D split and crop */
357 libdcp::Size
358 VideoContent::video_size_after_crop () const
359 {
360         return crop().apply (video_size_after_3d_split ());
361 }
362
363 /** @param t A time offset from the start of this piece of content.
364  *  @return Corresponding frame index.
365  */
366 VideoContent::Frame
367 VideoContent::time_to_content_video_frames (Time t) const
368 {
369         shared_ptr<const Film> film = _film.lock ();
370         assert (film);
371         
372         FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
373
374         /* Here we are converting from time (in the DCP) to a frame number in the content.
375            Hence we need to use the DCP's frame rate and the double/skip correction, not
376            the source's rate.
377         */
378         return t * film->video_frame_rate() / (frc.factor() * TIME_HZ);
379 }
380
381 VideoContentScale::VideoContentScale (Ratio const * r)
382         : _ratio (r)
383         , _scale (true)
384 {
385
386 }
387
388 VideoContentScale::VideoContentScale ()
389         : _ratio (0)
390         , _scale (false)
391 {
392
393 }
394
395 VideoContentScale::VideoContentScale (bool scale)
396         : _ratio (0)
397         , _scale (scale)
398 {
399
400 }
401
402 VideoContentScale::VideoContentScale (shared_ptr<cxml::Node> node)
403         : _ratio (0)
404         , _scale (true)
405 {
406         optional<string> r = node->optional_string_child ("Ratio");
407         if (r) {
408                 _ratio = Ratio::from_id (r.get ());
409         } else {
410                 _scale = node->bool_child ("Scale");
411         }
412 }
413
414 void
415 VideoContentScale::as_xml (xmlpp::Node* node) const
416 {
417         if (_ratio) {
418                 node->add_child("Ratio")->add_child_text (_ratio->id ());
419         } else {
420                 node->add_child("Scale")->add_child_text (_scale ? "1" : "0");
421         }
422 }
423
424 string
425 VideoContentScale::id () const
426 {
427         stringstream s;
428         
429         if (_ratio) {
430                 s << _ratio->id () << "_";
431         } else {
432                 s << (_scale ? "S1" : "S0");
433         }
434         
435         return s.str ();
436 }
437
438 string
439 VideoContentScale::name () const
440 {
441         if (_ratio) {
442                 return _ratio->nickname ();
443         }
444
445         if (_scale) {
446                 return _("No stretch");
447         }
448
449         return _("No scale");
450 }
451
452 /** @param display_container Size of the container that we are displaying this content in.
453  *  @param film_container The size of the film's image.
454  */
455 libdcp::Size
456 VideoContentScale::size (shared_ptr<const VideoContent> c, libdcp::Size display_container, libdcp::Size film_container) const
457 {
458         if (_ratio) {
459                 return fit_ratio_within (_ratio->ratio (), display_container);
460         }
461
462         libdcp::Size const ac = c->video_size_after_crop ();
463
464         /* Force scale if the film_container is smaller than the content's image */
465         if (_scale || film_container.width < ac.width || film_container.height < ac.height) {
466                 return fit_ratio_within (ac.ratio (), display_container);
467         }
468
469         /* Scale the image so that it will be in the right place in film_container, even if display_container is a
470            different size.
471         */
472         return libdcp::Size (
473                 c->video_size().width  * float(display_container.width)  / film_container.width,
474                 c->video_size().height * float(display_container.height) / film_container.height
475                 );
476 }
477
478 void
479 VideoContentScale::setup_scales ()
480 {
481         vector<Ratio const *> ratios = Ratio::all ();
482         for (vector<Ratio const *>::const_iterator i = ratios.begin(); i != ratios.end(); ++i) {
483                 _scales.push_back (VideoContentScale (*i));
484         }
485
486         _scales.push_back (VideoContentScale (true));
487         _scales.push_back (VideoContentScale (false));
488 }
489
490 bool
491 operator== (VideoContentScale const & a, VideoContentScale const & b)
492 {
493         return (a.ratio() == b.ratio() && a.scale() == b.scale());
494 }
495
496 bool
497 operator!= (VideoContentScale const & a, VideoContentScale const & b)
498 {
499         return (a.ratio() != b.ratio() || a.scale() != b.scale());
500 }