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