operator bool on Time is a really bad idea; removed it and fixed lots of bugs.
[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         cout << "taking " << vfr << "\n";
184         
185         {
186                 boost::mutex::scoped_lock lm (_mutex);
187                 _video_size = vs;
188                 _video_frame_rate = vfr;
189                 cout << "and then " << _video_frame_rate << "\n";
190         }
191         
192         signal_changed (VideoContentProperty::VIDEO_SIZE);
193         signal_changed (VideoContentProperty::VIDEO_FRAME_RATE);
194 }
195
196
197 string
198 VideoContent::information () const
199 {
200         if (video_size().width == 0 || video_size().height == 0) {
201                 return "";
202         }
203         
204         stringstream s;
205
206         s << String::compose (
207                 _("%1x%2 pixels (%3:1)"),
208                 video_size().width,
209                 video_size().height,
210                 setprecision (3), video_size().ratio ()
211                 );
212         
213         return s.str ();
214 }
215
216 void
217 VideoContent::set_left_crop (int c)
218 {
219         {
220                 boost::mutex::scoped_lock lm (_mutex);
221                 
222                 if (_crop.left == c) {
223                         return;
224                 }
225                 
226                 _crop.left = c;
227         }
228         
229         signal_changed (VideoContentProperty::VIDEO_CROP);
230 }
231
232 void
233 VideoContent::set_right_crop (int c)
234 {
235         {
236                 boost::mutex::scoped_lock lm (_mutex);
237                 if (_crop.right == c) {
238                         return;
239                 }
240                 
241                 _crop.right = c;
242         }
243         
244         signal_changed (VideoContentProperty::VIDEO_CROP);
245 }
246
247 void
248 VideoContent::set_top_crop (int c)
249 {
250         {
251                 boost::mutex::scoped_lock lm (_mutex);
252                 if (_crop.top == c) {
253                         return;
254                 }
255                 
256                 _crop.top = c;
257         }
258         
259         signal_changed (VideoContentProperty::VIDEO_CROP);
260 }
261
262 void
263 VideoContent::set_bottom_crop (int c)
264 {
265         {
266                 boost::mutex::scoped_lock lm (_mutex);
267                 if (_crop.bottom == c) {
268                         return;
269                 }
270                 
271                 _crop.bottom = c;
272         }
273
274         signal_changed (VideoContentProperty::VIDEO_CROP);
275 }
276
277 void
278 VideoContent::set_scale (VideoContentScale s)
279 {
280         {
281                 boost::mutex::scoped_lock lm (_mutex);
282                 if (_scale == s) {
283                         return;
284                 }
285
286                 _scale = s;
287         }
288
289         signal_changed (VideoContentProperty::VIDEO_SCALE);
290 }
291
292 /** @return string which includes everything about how this content looks */
293 string
294 VideoContent::identifier () const
295 {
296         stringstream s;
297         s << Content::identifier()
298           << "_" << crop().left
299           << "_" << crop().right
300           << "_" << crop().top
301           << "_" << crop().bottom
302           << "_" << scale().id()
303           << "_" << colour_conversion().identifier ();
304
305         return s.str ();
306 }
307
308 void
309 VideoContent::set_video_frame_type (VideoFrameType t)
310 {
311         {
312                 boost::mutex::scoped_lock lm (_mutex);
313                 _video_frame_type = t;
314         }
315
316         signal_changed (VideoContentProperty::VIDEO_FRAME_TYPE);
317 }
318
319 string
320 VideoContent::technical_summary () const
321 {
322         return String::compose (
323                 "video: length %1, size %2x%3, rate %4",
324                 video_length().seconds(),
325                 video_size().width,
326                 video_size().height,
327                 video_frame_rate()
328                 );
329 }
330
331 dcp::Size
332 VideoContent::video_size_after_3d_split () const
333 {
334         dcp::Size const s = video_size ();
335         switch (video_frame_type ()) {
336         case VIDEO_FRAME_TYPE_2D:
337                 return s;
338         case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
339                 return dcp::Size (s.width / 2, s.height);
340         case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
341                 return dcp::Size (s.width, s.height / 2);
342         }
343
344         assert (false);
345 }
346
347 void
348 VideoContent::set_colour_conversion (ColourConversion c)
349 {
350         {
351                 boost::mutex::scoped_lock lm (_mutex);
352                 _colour_conversion = c;
353         }
354
355         signal_changed (VideoContentProperty::COLOUR_CONVERSION);
356 }
357
358 /** @return Video size after 3D split and crop */
359 dcp::Size
360 VideoContent::video_size_after_crop () const
361 {
362         return crop().apply (video_size_after_3d_split ());
363 }
364
365 /** @param t A time offset from the start of this piece of content.
366  *  @return Corresponding time with respect to the content.
367  */
368 ContentTime
369 VideoContent::dcp_time_to_content_time (DCPTime t) const
370 {
371         shared_ptr<const Film> film = _film.lock ();
372         assert (film);
373         return ContentTime (t, FrameRateChange (video_frame_rate(), film->video_frame_rate()));
374 }
375
376 VideoContentScale::VideoContentScale (Ratio const * r)
377         : _ratio (r)
378         , _scale (true)
379 {
380
381 }
382
383 VideoContentScale::VideoContentScale ()
384         : _ratio (0)
385         , _scale (false)
386 {
387
388 }
389
390 VideoContentScale::VideoContentScale (bool scale)
391         : _ratio (0)
392         , _scale (scale)
393 {
394
395 }
396
397 VideoContentScale::VideoContentScale (shared_ptr<cxml::Node> node)
398         : _ratio (0)
399         , _scale (true)
400 {
401         optional<string> r = node->optional_string_child ("Ratio");
402         if (r) {
403                 _ratio = Ratio::from_id (r.get ());
404         } else {
405                 _scale = node->bool_child ("Scale");
406         }
407 }
408
409 void
410 VideoContentScale::as_xml (xmlpp::Node* node) const
411 {
412         if (_ratio) {
413                 node->add_child("Ratio")->add_child_text (_ratio->id ());
414         } else {
415                 node->add_child("Scale")->add_child_text (_scale ? "1" : "0");
416         }
417 }
418
419 string
420 VideoContentScale::id () const
421 {
422         stringstream s;
423         
424         if (_ratio) {
425                 s << _ratio->id () << "_";
426         } else {
427                 s << (_scale ? "S1" : "S0");
428         }
429         
430         return s.str ();
431 }
432
433 string
434 VideoContentScale::name () const
435 {
436         if (_ratio) {
437                 return _ratio->nickname ();
438         }
439
440         if (_scale) {
441                 return _("No stretch");
442         }
443
444         return _("No scale");
445 }
446
447 dcp::Size
448 VideoContentScale::size (shared_ptr<const VideoContent> c, dcp::Size container) const
449 {
450         if (_ratio) {
451                 return fit_ratio_within (_ratio->ratio (), container);
452         }
453
454         /* Force scale if the container is smaller than the content's image */
455         if (_scale || container.width < c->video_size().width || container.height < c->video_size().height) {
456                 return fit_ratio_within (c->video_size().ratio (), container);
457         }
458
459         return c->video_size ();
460 }
461
462 void
463 VideoContentScale::setup_scales ()
464 {
465         vector<Ratio const *> ratios = Ratio::all ();
466         for (vector<Ratio const *>::const_iterator i = ratios.begin(); i != ratios.end(); ++i) {
467                 _scales.push_back (VideoContentScale (*i));
468         }
469
470         _scales.push_back (VideoContentScale (true));
471         _scales.push_back (VideoContentScale (false));
472 }
473
474 bool
475 operator== (VideoContentScale const & a, VideoContentScale const & b)
476 {
477         return (a.ratio() == b.ratio() && a.scale() == b.scale());
478 }
479
480 bool
481 operator!= (VideoContentScale const & a, VideoContentScale const & b)
482 {
483         return (a.ratio() != b.ratio() || a.scale() != b.scale());
484 }