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