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 "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, DCPTime s, ContentTime 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 = ContentTime (node->number_child<int64_t> ("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.get ()));
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, dcp::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         dcp::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().seconds(),
323                 video_size().width,
324                 video_size().height,
325                 video_frame_rate()
326                 );
327 }
328
329 dcp::Size
330 VideoContent::video_size_after_3d_split () const
331 {
332         dcp::Size const s = video_size ();
333         switch (video_frame_type ()) {
334         case VIDEO_FRAME_TYPE_2D:
335                 return s;
336         case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
337                 return dcp::Size (s.width / 2, s.height);
338         case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
339                 return dcp::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 dcp::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 time with respect to the content.
365  */
366 ContentTime
367 VideoContent::dcp_time_to_content_time (DCPTime t) const
368 {
369         shared_ptr<const Film> film = _film.lock ();
370         assert (film);
371         return ContentTime (t, FrameRateChange (video_frame_rate(), film->video_frame_rate()));
372 }
373
374 VideoContentScale::VideoContentScale (Ratio const * r)
375         : _ratio (r)
376         , _scale (true)
377 {
378
379 }
380
381 VideoContentScale::VideoContentScale ()
382         : _ratio (0)
383         , _scale (false)
384 {
385
386 }
387
388 VideoContentScale::VideoContentScale (bool scale)
389         : _ratio (0)
390         , _scale (scale)
391 {
392
393 }
394
395 VideoContentScale::VideoContentScale (shared_ptr<cxml::Node> node)
396         : _ratio (0)
397         , _scale (true)
398 {
399         optional<string> r = node->optional_string_child ("Ratio");
400         if (r) {
401                 _ratio = Ratio::from_id (r.get ());
402         } else {
403                 _scale = node->bool_child ("Scale");
404         }
405 }
406
407 void
408 VideoContentScale::as_xml (xmlpp::Node* node) const
409 {
410         if (_ratio) {
411                 node->add_child("Ratio")->add_child_text (_ratio->id ());
412         } else {
413                 node->add_child("Scale")->add_child_text (_scale ? "1" : "0");
414         }
415 }
416
417 string
418 VideoContentScale::id () const
419 {
420         stringstream s;
421         
422         if (_ratio) {
423                 s << _ratio->id () << "_";
424         } else {
425                 s << (_scale ? "S1" : "S0");
426         }
427         
428         return s.str ();
429 }
430
431 string
432 VideoContentScale::name () const
433 {
434         if (_ratio) {
435                 return _ratio->nickname ();
436         }
437
438         if (_scale) {
439                 return _("No stretch");
440         }
441
442         return _("No scale");
443 }
444
445 /** @param display_container Size of the container that we are displaying this content in.
446  *  @param film_container The size of the film's image.
447  */
448 dcp::Size
449 VideoContentScale::size (shared_ptr<const VideoContent> c, dcp::Size display_container, dcp::Size film_container) const
450 {
451         if (_ratio) {
452                 return fit_ratio_within (_ratio->ratio (), display_container);
453         }
454
455         libdcp::Size const ac = c->video_size_after_crop ();
456
457         /* Force scale if the film_container is smaller than the content's image */
458         if (_scale || film_container.width < ac.width || film_container.height < ac.height) {
459                 return fit_ratio_within (ac.ratio (), display_container);
460         }
461
462         /* Scale the image so that it will be in the right place in film_container, even if display_container is a
463            different size.
464         */
465         return libdcp::Size (
466                 c->video_size().width  * float(display_container.width)  / film_container.width,
467                 c->video_size().height * float(display_container.height) / film_container.height
468                 );
469 }
470
471 void
472 VideoContentScale::setup_scales ()
473 {
474         vector<Ratio const *> ratios = Ratio::all ();
475         for (vector<Ratio const *>::const_iterator i = ratios.begin(); i != ratios.end(); ++i) {
476                 _scales.push_back (VideoContentScale (*i));
477         }
478
479         _scales.push_back (VideoContentScale (true));
480         _scales.push_back (VideoContentScale (false));
481 }
482
483 bool
484 operator== (VideoContentScale const & a, VideoContentScale const & b)
485 {
486         return (a.ratio() == b.ratio() && a.scale() == b.scale());
487 }
488
489 bool
490 operator!= (VideoContentScale const & a, VideoContentScale const & b)
491 {
492         return (a.ratio() != b.ratio() || a.scale() != b.scale());
493 }