21cb435ed5eb250a7f94887ddd75f6b367ea231f from master; better colour conversion selection.
[dcpomatic.git] / src / wx / video_panel.cc
1 /*
2     Copyright (C) 2012-2015 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 "lib/filter.h"
21 #include "lib/ffmpeg_content.h"
22 #include "lib/colour_conversion.h"
23 #include "lib/config.h"
24 #include "lib/util.h"
25 #include "lib/ratio.h"
26 #include "lib/frame_rate_change.h"
27 #include "filter_dialog.h"
28 #include "video_panel.h"
29 #include "wx_util.h"
30 #include "content_colour_conversion_dialog.h"
31 #include "content_widget.h"
32 #include "content_panel.h"
33 #include <wx/spinctrl.h>
34 #include <boost/foreach.hpp>
35 #include <set>
36
37 using std::vector;
38 using std::string;
39 using std::pair;
40 using std::cout;
41 using std::list;
42 using std::set;
43 using boost::shared_ptr;
44 using boost::dynamic_pointer_cast;
45 using boost::bind;
46 using boost::optional;
47
48 static VideoContentScale
49 index_to_scale (int n)
50 {
51         vector<VideoContentScale> scales = VideoContentScale::all ();
52         DCPOMATIC_ASSERT (n >= 0);
53         DCPOMATIC_ASSERT (n < int (scales.size ()));
54         return scales[n];
55 }
56
57 static int
58 scale_to_index (VideoContentScale scale)
59 {
60         vector<VideoContentScale> scales = VideoContentScale::all ();
61         for (size_t i = 0; i < scales.size(); ++i) {
62                 if (scales[i] == scale) {
63                         return i;
64                 }
65         }
66
67         DCPOMATIC_ASSERT (false);
68 }
69
70 VideoPanel::VideoPanel (ContentPanel* p)
71         : ContentSubPanel (p, _("Video"))
72 {
73         wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
74         _sizer->Add (grid, 0, wxALL, 8);
75
76         int r = 0;
77
78         add_label_to_grid_bag_sizer (grid, this, _("Type"), true, wxGBPosition (r, 0));
79         _frame_type = new ContentChoice<VideoContent, VideoFrameType> (
80                 this,
81                 new wxChoice (this, wxID_ANY),
82                 VideoContentProperty::VIDEO_FRAME_TYPE,
83                 boost::mem_fn (&VideoContent::video_frame_type),
84                 boost::mem_fn (&VideoContent::set_video_frame_type),
85                 &caster<int, VideoFrameType>,
86                 &caster<VideoFrameType, int>
87                 );
88         _frame_type->add (grid, wxGBPosition (r, 1), wxGBSpan (1, 2));
89         ++r;
90
91         int cr = 0;
92         wxGridBagSizer* crop = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
93
94         add_label_to_grid_bag_sizer (crop, this, _("Left crop"), true, wxGBPosition (cr, 0));
95         _left_crop = new ContentSpinCtrl<VideoContent> (
96                 this,
97                 new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)),
98                 VideoContentProperty::VIDEO_CROP,
99                 boost::mem_fn (&VideoContent::left_crop),
100                 boost::mem_fn (&VideoContent::set_left_crop)
101                 );
102         _left_crop->add (crop, wxGBPosition (cr, 1));
103
104         add_label_to_grid_bag_sizer (crop, this, _("Right crop"), true, wxGBPosition (cr, 2));
105         _right_crop = new ContentSpinCtrl<VideoContent> (
106                 this,
107                 new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)),
108                 VideoContentProperty::VIDEO_CROP,
109                 boost::mem_fn (&VideoContent::right_crop),
110                 boost::mem_fn (&VideoContent::set_right_crop)
111                 );
112         _right_crop->add (crop, wxGBPosition (cr, 3));
113
114         ++cr;
115         
116         add_label_to_grid_bag_sizer (crop, this, _("Top crop"), true, wxGBPosition (cr, 0));
117         _top_crop = new ContentSpinCtrl<VideoContent> (
118                 this,
119                 new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)),
120                 VideoContentProperty::VIDEO_CROP,
121                 boost::mem_fn (&VideoContent::top_crop),
122                 boost::mem_fn (&VideoContent::set_top_crop)
123                 );
124         _top_crop->add (crop, wxGBPosition (cr, 1));
125
126         add_label_to_grid_bag_sizer (crop, this, _("Bottom crop"), true, wxGBPosition (cr, 2));
127         _bottom_crop = new ContentSpinCtrl<VideoContent> (
128                 this,
129                 new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)),
130                 VideoContentProperty::VIDEO_CROP,
131                 boost::mem_fn (&VideoContent::bottom_crop),
132                 boost::mem_fn (&VideoContent::set_bottom_crop)
133                 );
134         _bottom_crop->add (crop, wxGBPosition (cr, 3));
135
136         grid->Add (crop, wxGBPosition (r, 0), wxGBSpan (2, 3));
137         r += 2;
138
139         add_label_to_grid_bag_sizer (grid, this, _("Fade in"), true, wxGBPosition (r, 0));
140         _fade_in = new Timecode<ContentTime> (this);
141         grid->Add (_fade_in, wxGBPosition (r, 1), wxGBSpan (1, 3));
142         ++r;
143
144         add_label_to_grid_bag_sizer (grid, this, _("Fade out"), true, wxGBPosition (r, 0));
145         _fade_out = new Timecode<ContentTime> (this);
146         grid->Add (_fade_out, wxGBPosition (r, 1), wxGBSpan (1, 3));
147         ++r;
148         
149         add_label_to_grid_bag_sizer (grid, this, _("Scale to"), true, wxGBPosition (r, 0));
150         _scale = new ContentChoice<VideoContent, VideoContentScale> (
151                 this,
152                 new wxChoice (this, wxID_ANY),
153                 VideoContentProperty::VIDEO_SCALE,
154                 boost::mem_fn (&VideoContent::scale),
155                 boost::mem_fn (&VideoContent::set_scale),
156                 &index_to_scale,
157                 &scale_to_index
158                 );
159         _scale->add (grid, wxGBPosition (r, 1), wxGBSpan (1, 2));
160         ++r;
161
162         wxClientDC dc (this);
163         wxSize size = dc.GetTextExtent (wxT ("A quite long name"));
164         size.SetHeight (-1);
165         
166         add_label_to_grid_bag_sizer (grid, this, _("Filters"), true, wxGBPosition (r, 0));
167         _filters = new wxStaticText (this, wxID_ANY, _("None"), wxDefaultPosition, size);
168         grid->Add (_filters, wxGBPosition (r, 1), wxGBSpan (1, 2), wxALIGN_CENTER_VERTICAL);
169         _filters_button = new wxButton (this, wxID_ANY, _("Edit..."));
170         grid->Add (_filters_button, wxGBPosition (r, 3), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
171         ++r;
172
173         add_label_to_grid_bag_sizer (grid, this, _("Colour conversion"), true, wxGBPosition (r, 0));
174         {
175                 wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
176
177                 wxClientDC dc (this);
178                 wxSize size = dc.GetTextExtent (wxT ("A quite long-ish name"));
179                 size.SetHeight (-1);
180                 
181                 _colour_conversion = new wxChoice (this, wxID_ANY, wxDefaultPosition, size);
182                 _colour_conversion->Append (_("None"));
183                 BOOST_FOREACH (PresetColourConversion const & i, PresetColourConversion::all()) {
184                         _colour_conversion->Append (std_to_wx (i.name));
185                 }
186                 _colour_conversion->Append (_("Custom"));
187                 s->Add (_colour_conversion, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6);
188                 
189                 _edit_colour_conversion_button = new wxButton (this, wxID_ANY, _("Edit..."));
190                 s->Add (_edit_colour_conversion_button, 0, wxALIGN_CENTER_VERTICAL);
191                 
192                 grid->Add (s, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
193         }
194         ++r;
195
196         _description = new wxStaticText (this, wxID_ANY, wxT ("\n \n \n \n \n"), wxDefaultPosition, wxDefaultSize);
197         grid->Add (_description, wxGBPosition (r, 0), wxGBSpan (1, 4), wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 6);
198         wxFont font = _description->GetFont();
199         font.SetStyle(wxFONTSTYLE_ITALIC);
200         font.SetPointSize(font.GetPointSize() - 1);
201         _description->SetFont(font);
202         ++r;
203
204         _left_crop->wrapped()->SetRange (0, 1024);
205         _top_crop->wrapped()->SetRange (0, 1024);
206         _right_crop->wrapped()->SetRange (0, 1024);
207         _bottom_crop->wrapped()->SetRange (0, 1024);
208
209         vector<VideoContentScale> scales = VideoContentScale::all ();
210         _scale->wrapped()->Clear ();
211         for (vector<VideoContentScale>::iterator i = scales.begin(); i != scales.end(); ++i) {
212                 _scale->wrapped()->Append (std_to_wx (i->name ()));
213         }
214
215         _frame_type->wrapped()->Append (_("2D"));
216         _frame_type->wrapped()->Append (_("3D left/right"));
217         _frame_type->wrapped()->Append (_("3D top/bottom"));
218         _frame_type->wrapped()->Append (_("3D alternate"));
219         _frame_type->wrapped()->Append (_("3D left only"));
220         _frame_type->wrapped()->Append (_("3D right only"));
221
222         _fade_in->Changed.connect (boost::bind (&VideoPanel::fade_in_changed, this));
223         _fade_out->Changed.connect (boost::bind (&VideoPanel::fade_out_changed, this));
224         
225         _filters_button->Bind                (wxEVT_COMMAND_BUTTON_CLICKED,  boost::bind (&VideoPanel::edit_filters_clicked, this));
226         _colour_conversion->Bind             (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&VideoPanel::colour_conversion_changed, this));
227         _edit_colour_conversion_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED,  boost::bind (&VideoPanel::edit_colour_conversion_clicked, this));
228 }
229
230 void
231 VideoPanel::film_changed (Film::Property property)
232 {
233         switch (property) {
234         case Film::CONTAINER:
235         case Film::VIDEO_FRAME_RATE:
236         case Film::RESOLUTION:
237                 setup_description ();
238                 break;
239         default:
240                 break;
241         }
242 }
243
244 void
245 VideoPanel::film_content_changed (int property)
246 {
247         VideoContentList vc = _parent->selected_video ();
248         shared_ptr<VideoContent> vcs;
249         shared_ptr<FFmpegContent> fcs;
250         if (!vc.empty ()) {
251                 vcs = vc.front ();
252                 fcs = dynamic_pointer_cast<FFmpegContent> (vcs);
253         }
254         
255         if (property == VideoContentProperty::VIDEO_FRAME_TYPE) {
256                 setup_description ();
257         } else if (property == VideoContentProperty::VIDEO_CROP) {
258                 setup_description ();
259         } else if (property == VideoContentProperty::VIDEO_SCALE) {
260                 setup_description ();
261         } else if (property == VideoContentProperty::VIDEO_FRAME_RATE) {
262                 setup_description ();
263         } else if (property == VideoContentProperty::COLOUR_CONVERSION) {
264                 if (!vcs) {
265                         checked_set (_colour_conversion, 0);
266                         _edit_colour_conversion_button->Enable (false);
267                 } else if (vcs->colour_conversion ()) {
268                         optional<size_t> preset = vcs->colour_conversion().get().preset ();
269                         vector<PresetColourConversion> cc = PresetColourConversion::all ();
270                         if (preset) {
271                                 checked_set (_colour_conversion, preset.get() + 1);
272                                 _edit_colour_conversion_button->Enable (false);
273                         } else {
274                                 checked_set (_colour_conversion, cc.size() + 1);
275                                 _edit_colour_conversion_button->Enable (true);
276                         }
277                 } else {
278                         checked_set (_colour_conversion, 0);
279                         _edit_colour_conversion_button->Enable (false);
280                 }
281         } else if (property == FFmpegContentProperty::FILTERS) {
282                 if (fcs) {
283                         string p = Filter::ffmpeg_string (fcs->filters ());
284                         if (p.empty ()) {
285                                 checked_set (_filters, _("None"));
286                         } else {
287                                 if (p.length() > 25) {
288                                         p = p.substr (0, 25) + "...";
289                                 }
290                                 checked_set (_filters, p);
291                         }
292                 }
293         } else if (property == VideoContentProperty::VIDEO_FADE_IN) {
294                 set<Frame> check;
295                 for (VideoContentList::const_iterator i = vc.begin (); i != vc.end(); ++i) {
296                         check.insert ((*i)->fade_in ());
297                 }
298                 
299                 if (check.size() == 1) {
300                         _fade_in->set (ContentTime::from_frames (vc.front()->fade_in (), vc.front()->video_frame_rate ()), vc.front()->video_frame_rate ());
301                 } else {
302                         _fade_in->clear ();
303                 }
304         } else if (property == VideoContentProperty::VIDEO_FADE_OUT) {
305                 set<Frame> check;
306                 for (VideoContentList::const_iterator i = vc.begin (); i != vc.end(); ++i) {
307                         check.insert ((*i)->fade_out ());
308                 }
309                 
310                 if (check.size() == 1) {
311                         _fade_out->set (ContentTime::from_frames (vc.front()->fade_out (), vc.front()->video_frame_rate ()), vc.front()->video_frame_rate ());
312                 } else {
313                         _fade_out->clear ();
314                 }
315         }
316 }
317
318 /** Called when the `Edit filters' button has been clicked */
319 void
320 VideoPanel::edit_filters_clicked ()
321 {
322         FFmpegContentList c = _parent->selected_ffmpeg ();
323         if (c.size() != 1) {
324                 return;
325         }
326
327         FilterDialog* d = new FilterDialog (this, c.front()->filters());
328         d->ActiveChanged.connect (bind (&FFmpegContent::set_filters, c.front(), _1));
329         d->ShowModal ();
330         d->Destroy ();
331 }
332
333 void
334 VideoPanel::setup_description ()
335 {
336         VideoContentList vc = _parent->selected_video ();
337         if (vc.empty ()) {
338                 checked_set (_description, wxT (""));
339                 return;
340         } else if (vc.size() > 1) {
341                 checked_set (_description, _("Multiple content selected"));
342                 return;
343         }
344
345         string d = vc.front()->processing_description ();
346         size_t lines = count (d.begin(), d.end(), '\n');
347
348         for (int i = lines; i < 6; ++i) {
349                 d += "\n ";
350         }
351
352         checked_set (_description, d);
353         _sizer->Layout ();
354 }
355
356 void
357 VideoPanel::colour_conversion_changed ()
358 {
359         VideoContentList vc = _parent->selected_video ();
360         if (vc.size() != 1) {
361                 return;
362         }
363
364         int const s = _colour_conversion->GetSelection ();
365         vector<PresetColourConversion> all = PresetColourConversion::all ();
366
367         if (s == 0) {
368                 vc.front()->unset_colour_conversion ();
369         } else if (s == int (all.size() + 1)) {
370                 edit_colour_conversion_clicked ();
371         } else {
372                 vc.front()->set_colour_conversion (all[s - 1].conversion);
373         }
374 }
375
376 void
377 VideoPanel::edit_colour_conversion_clicked ()
378 {
379         VideoContentList vc = _parent->selected_video ();
380         if (vc.size() != 1) {
381                 return;
382         }
383
384         ContentColourConversionDialog* d = new ContentColourConversionDialog (this);
385         d->set (vc.front()->colour_conversion().get_value_or (PresetColourConversion::all().front ().conversion));
386         d->ShowModal ();
387         vc.front()->set_colour_conversion (d->get ());
388         d->Destroy ();
389 }
390
391 void
392 VideoPanel::content_selection_changed ()
393 {
394         VideoContentList video_sel = _parent->selected_video ();
395         FFmpegContentList ffmpeg_sel = _parent->selected_ffmpeg ();
396         
397         bool const single = video_sel.size() == 1;
398
399         _frame_type->set_content (video_sel);
400         _left_crop->set_content (video_sel);
401         _right_crop->set_content (video_sel);
402         _top_crop->set_content (video_sel);
403         _bottom_crop->set_content (video_sel);
404         _fade_in->Enable (!video_sel.empty ());
405         _fade_out->Enable (!video_sel.empty ());
406         _scale->set_content (video_sel);
407
408         _filters_button->Enable (single && !ffmpeg_sel.empty ());
409
410         film_content_changed (VideoContentProperty::VIDEO_CROP);
411         film_content_changed (VideoContentProperty::VIDEO_FRAME_RATE);
412         film_content_changed (VideoContentProperty::COLOUR_CONVERSION);
413         film_content_changed (VideoContentProperty::VIDEO_FADE_IN);
414         film_content_changed (VideoContentProperty::VIDEO_FADE_OUT);
415         film_content_changed (FFmpegContentProperty::FILTERS);
416 }
417
418 void
419 VideoPanel::fade_in_changed ()
420 {
421         VideoContentList vc = _parent->selected_video ();
422         for (VideoContentList::const_iterator i = vc.begin(); i != vc.end(); ++i) {
423                 int const vfr = _parent->film()->video_frame_rate ();
424                 (*i)->set_fade_in (_fade_in->get (vfr).frames (vfr));
425         }
426 }
427
428 void
429 VideoPanel::fade_out_changed ()
430 {
431         VideoContentList vc = _parent->selected_video ();
432         for (VideoContentList::const_iterator i = vc.begin(); i != vc.end(); ++i) {
433                 int const vfr = _parent->film()->video_frame_rate ();
434                 (*i)->set_fade_out (_fade_out->get (vfr).frames (vfr));
435         }
436 }