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