Fix crash if quick_refresh() fails.
[dcpomatic.git] / src / lib / player_video.cc
1 /*
2     Copyright (C) 2013-2018 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21 #include "player_video.h"
22 #include "content.h"
23 #include "video_content.h"
24 #include "image.h"
25 #include "image_proxy.h"
26 #include "j2k_image_proxy.h"
27 #include "film.h"
28 #include <dcp/raw_convert.h>
29 extern "C" {
30 #include <libavutil/pixfmt.h>
31 }
32 #include <libxml++/libxml++.h>
33 #include <iostream>
34
35 using std::string;
36 using std::cout;
37 using boost::shared_ptr;
38 using boost::weak_ptr;
39 using boost::dynamic_pointer_cast;
40 using boost::optional;
41 using boost::function;
42 using dcp::Data;
43 using dcp::raw_convert;
44
45 PlayerVideo::PlayerVideo (
46         shared_ptr<const ImageProxy> in,
47         Crop crop,
48         boost::optional<double> fade,
49         dcp::Size inter_size,
50         dcp::Size out_size,
51         Eyes eyes,
52         Part part,
53         optional<ColourConversion> colour_conversion,
54         weak_ptr<Content> content,
55         optional<Frame> video_frame
56         )
57         : _in (in)
58         , _crop (crop)
59         , _fade (fade)
60         , _inter_size (inter_size)
61         , _out_size (out_size)
62         , _eyes (eyes)
63         , _part (part)
64         , _colour_conversion (colour_conversion)
65         , _content (content)
66         , _video_frame (video_frame)
67 {
68
69 }
70
71 PlayerVideo::PlayerVideo (shared_ptr<cxml::Node> node, shared_ptr<Socket> socket)
72 {
73         _crop = Crop (node);
74         _fade = node->optional_number_child<double> ("Fade");
75
76         _inter_size = dcp::Size (node->number_child<int> ("InterWidth"), node->number_child<int> ("InterHeight"));
77         _out_size = dcp::Size (node->number_child<int> ("OutWidth"), node->number_child<int> ("OutHeight"));
78         _eyes = (Eyes) node->number_child<int> ("Eyes");
79         _part = (Part) node->number_child<int> ("Part");
80
81         /* Assume that the ColourConversion uses the current state version */
82         _colour_conversion = ColourConversion::from_xml (node, Film::current_state_version);
83
84         _in = image_proxy_factory (node->node_child ("In"), socket);
85
86         if (node->optional_number_child<int> ("SubtitleX")) {
87
88                 shared_ptr<Image> image (
89                         new Image (AV_PIX_FMT_BGRA, dcp::Size (node->number_child<int> ("SubtitleWidth"), node->number_child<int> ("SubtitleHeight")), true)
90                         );
91
92                 image->read_from_socket (socket);
93
94                 _subtitle = PositionImage (image, Position<int> (node->number_child<int> ("SubtitleX"), node->number_child<int> ("SubtitleY")));
95         }
96 }
97
98 void
99 PlayerVideo::set_subtitle (PositionImage image)
100 {
101         _subtitle = image;
102 }
103
104 /** Create an image for this frame.
105  *  @param note Handler for any notes that are made during the process.
106  *  @param pixel_format Function which is called to decide what pixel format the output image should be;
107  *  it is passed the pixel format of the input image from the ImageProxy, and should return the desired
108  *  output pixel format.  Two functions always_rgb and keep_xyz_or_rgb are provided for use here.
109  *  @param aligned true if the output image should be aligned to 32-byte boundaries.
110  *  @param fast true to be fast at the expense of quality.
111  */
112 shared_ptr<Image>
113 PlayerVideo::image (dcp::NoteHandler note, function<AVPixelFormat (AVPixelFormat)> pixel_format, bool aligned, bool fast) const
114 {
115         shared_ptr<Image> im = _in->image (optional<dcp::NoteHandler> (note), _inter_size);
116
117         Crop total_crop = _crop;
118         switch (_part) {
119         case PART_LEFT_HALF:
120                 total_crop.right += im->size().width / 2;
121                 break;
122         case PART_RIGHT_HALF:
123                 total_crop.left += im->size().width / 2;
124                 break;
125         case PART_TOP_HALF:
126                 total_crop.bottom += im->size().height / 2;
127                 break;
128         case PART_BOTTOM_HALF:
129                 total_crop.top += im->size().height / 2;
130                 break;
131         default:
132                 break;
133         }
134
135         dcp::YUVToRGB yuv_to_rgb = dcp::YUV_TO_RGB_REC601;
136         if (_colour_conversion) {
137                 yuv_to_rgb = _colour_conversion.get().yuv_to_rgb();
138         }
139
140         shared_ptr<Image> out = im->crop_scale_window (
141                 total_crop, _inter_size, _out_size, yuv_to_rgb, pixel_format (_in->pixel_format()), aligned, fast
142                 );
143
144         if (_subtitle) {
145                 out->alpha_blend (Image::ensure_aligned (_subtitle->image), _subtitle->position);
146         }
147
148         if (_fade) {
149                 out->fade (_fade.get ());
150         }
151
152         return out;
153 }
154
155 void
156 PlayerVideo::add_metadata (xmlpp::Node* node) const
157 {
158         _crop.as_xml (node);
159         if (_fade) {
160                 node->add_child("Fade")->add_child_text (raw_convert<string> (_fade.get ()));
161         }
162         _in->add_metadata (node->add_child ("In"));
163         node->add_child("InterWidth")->add_child_text (raw_convert<string> (_inter_size.width));
164         node->add_child("InterHeight")->add_child_text (raw_convert<string> (_inter_size.height));
165         node->add_child("OutWidth")->add_child_text (raw_convert<string> (_out_size.width));
166         node->add_child("OutHeight")->add_child_text (raw_convert<string> (_out_size.height));
167         node->add_child("Eyes")->add_child_text (raw_convert<string> (static_cast<int> (_eyes)));
168         node->add_child("Part")->add_child_text (raw_convert<string> (static_cast<int> (_part)));
169         if (_colour_conversion) {
170                 _colour_conversion.get().as_xml (node);
171         }
172         if (_subtitle) {
173                 node->add_child ("SubtitleWidth")->add_child_text (raw_convert<string> (_subtitle->image->size().width));
174                 node->add_child ("SubtitleHeight")->add_child_text (raw_convert<string> (_subtitle->image->size().height));
175                 node->add_child ("SubtitleX")->add_child_text (raw_convert<string> (_subtitle->position.x));
176                 node->add_child ("SubtitleY")->add_child_text (raw_convert<string> (_subtitle->position.y));
177         }
178 }
179
180 void
181 PlayerVideo::send_binary (shared_ptr<Socket> socket) const
182 {
183         _in->send_binary (socket);
184         if (_subtitle) {
185                 _subtitle->image->write_to_socket (socket);
186         }
187 }
188
189 bool
190 PlayerVideo::has_j2k () const
191 {
192         /* XXX: maybe other things */
193
194         shared_ptr<const J2KImageProxy> j2k = dynamic_pointer_cast<const J2KImageProxy> (_in);
195         if (!j2k) {
196                 return false;
197         }
198
199         return _crop == Crop () && _out_size == j2k->size() && !_subtitle && !_fade && !_colour_conversion;
200 }
201
202 Data
203 PlayerVideo::j2k () const
204 {
205         shared_ptr<const J2KImageProxy> j2k = dynamic_pointer_cast<const J2KImageProxy> (_in);
206         DCPOMATIC_ASSERT (j2k);
207         return j2k->j2k ();
208 }
209
210 Position<int>
211 PlayerVideo::inter_position () const
212 {
213         return Position<int> ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.height) / 2);
214 }
215
216 /** @return true if this PlayerVideo is definitely the same as another, false if it is probably not */
217 bool
218 PlayerVideo::same (shared_ptr<const PlayerVideo> other) const
219 {
220         if (_crop != other->_crop ||
221             _fade.get_value_or(0) != other->_fade.get_value_or(0) ||
222             _inter_size != other->_inter_size ||
223             _out_size != other->_out_size ||
224             _eyes != other->_eyes ||
225             _part != other->_part ||
226             _colour_conversion != other->_colour_conversion) {
227                 return false;
228         }
229
230         if ((!_subtitle && other->_subtitle) || (_subtitle && !other->_subtitle)) {
231                 /* One has a subtitle and the other doesn't */
232                 return false;
233         }
234
235         if (_subtitle && other->_subtitle && !_subtitle->same (other->_subtitle.get ())) {
236                 /* They both have subtitles but they are different */
237                 return false;
238         }
239
240         /* Now neither has subtitles */
241
242         return _in->same (other->_in);
243 }
244
245 AVPixelFormat
246 PlayerVideo::always_rgb (AVPixelFormat)
247 {
248         return AV_PIX_FMT_RGB24;
249 }
250
251 AVPixelFormat
252 PlayerVideo::keep_xyz_or_rgb (AVPixelFormat p)
253 {
254         return p == AV_PIX_FMT_XYZ12LE ? AV_PIX_FMT_XYZ12LE : AV_PIX_FMT_RGB48LE;
255 }
256
257 void
258 PlayerVideo::prepare ()
259 {
260         _in->prepare (_inter_size);
261 }
262
263 size_t
264 PlayerVideo::memory_used () const
265 {
266         return _in->memory_used();
267 }
268
269 /** @return Shallow copy of this; _in and _subtitle are shared between the original and the copy */
270 shared_ptr<PlayerVideo>
271 PlayerVideo::shallow_copy () const
272 {
273         return shared_ptr<PlayerVideo>(
274                 new PlayerVideo(
275                         _in,
276                         _crop,
277                         _fade,
278                         _inter_size,
279                         _out_size,
280                         _eyes,
281                         _part,
282                         _colour_conversion,
283                         _content,
284                         _video_frame
285                         )
286                 );
287 }
288
289 /** Re-read crop, fade, inter/out size and colour conversion from our content.
290  *  @return true if this was possible, false if not.
291  */
292 bool
293 PlayerVideo::reset_metadata (dcp::Size video_container_size, dcp::Size film_frame_size)
294 {
295         shared_ptr<Content> content = _content.lock();
296         if (!content || !_video_frame) {
297                 return false;
298         }
299
300         _crop = content->video->crop();
301         _fade = content->video->fade(_video_frame.get());
302         _inter_size = content->video->scale().size(content->video, video_container_size, film_frame_size);
303         _out_size = video_container_size;
304         _colour_conversion = content->video->colour_conversion();
305
306         return true;
307 }