Basics of MP4 support in the player.
[dcpomatic.git] / src / wx / controls.cc
1 /*
2     Copyright (C) 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 "controls.h"
22 #include "film_viewer.h"
23 #include "wx_util.h"
24 #include "playhead_to_timecode_dialog.h"
25 #include "playhead_to_frame_dialog.h"
26 #include "lib/job_manager.h"
27 #include "lib/player_video.h"
28 #include "lib/dcp_content.h"
29 #include "lib/job.h"
30 #include "lib/examine_content_job.h"
31 #include "lib/content_factory.h"
32 #include "lib/cross.h"
33 #include <dcp/dcp.h>
34 #include <dcp/cpl.h>
35 #include <dcp/reel.h>
36 #include <dcp/reel_picture_asset.h>
37 #include <wx/wx.h>
38 #include <wx/tglbtn.h>
39 #include <wx/listctrl.h>
40 #include <wx/progdlg.h>
41
42 using std::string;
43 using std::list;
44 using std::make_pair;
45 using boost::optional;
46 using boost::shared_ptr;
47 using boost::weak_ptr;
48 using boost::dynamic_pointer_cast;
49
50 Controls::Controls (wxWindow* parent, shared_ptr<FilmViewer> viewer, bool editor_controls)
51         : wxPanel (parent)
52         , _viewer (viewer)
53         , _slider_being_moved (false)
54         , _was_running_before_slider (false)
55         , _outline_content (0)
56         , _eye (0)
57         , _jump_to_selected (0)
58         , _slider (new wxSlider (this, wxID_ANY, 0, 0, 4096))
59         , _rewind_button (new wxButton (this, wxID_ANY, wxT("|<")))
60         , _back_button (new wxButton (this, wxID_ANY, wxT("<")))
61         , _forward_button (new wxButton (this, wxID_ANY, wxT(">")))
62         , _frame_number (new wxStaticText (this, wxID_ANY, wxT("")))
63         , _timecode (new wxStaticText (this, wxID_ANY, wxT("")))
64 #ifdef DCPOMATIC_VARIANT_SWAROOP
65         , _play_button (new wxButton(this, wxID_ANY, _("Play")))
66         , _pause_button (new wxButton(this, wxID_ANY, _("Pause")))
67         , _stop_button (new wxButton(this, wxID_ANY, _("Stop")))
68 #else
69         , _play_button (new wxToggleButton(this, wxID_ANY, _("Play")))
70 #endif
71 {
72         _v_sizer = new wxBoxSizer (wxVERTICAL);
73         SetSizer (_v_sizer);
74
75         wxBoxSizer* view_options = new wxBoxSizer (wxHORIZONTAL);
76         if (editor_controls) {
77                 _outline_content = new wxCheckBox (this, wxID_ANY, _("Outline content"));
78                 view_options->Add (_outline_content, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_GAP);
79                 _eye = new wxChoice (this, wxID_ANY);
80                 _eye->Append (_("Left"));
81                 _eye->Append (_("Right"));
82                 _eye->SetSelection (0);
83                 view_options->Add (_eye, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_GAP);
84                 _jump_to_selected = new wxCheckBox (this, wxID_ANY, _("Jump to selected content"));
85                 view_options->Add (_jump_to_selected, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_GAP);
86         }
87
88         _v_sizer->Add (view_options, 0, wxALL, DCPOMATIC_SIZER_GAP);
89
90         wxBoxSizer* e_sizer = new wxBoxSizer (wxHORIZONTAL);
91
92         _content_view = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_NO_HEADER);
93         /* time */
94         _content_view->AppendColumn (wxT(""), wxLIST_FORMAT_LEFT, 80);
95         /* type */
96         _content_view->AppendColumn (wxT(""), wxLIST_FORMAT_LEFT, 80);
97         /* annotation text */
98         _content_view->AppendColumn (wxT(""), wxLIST_FORMAT_LEFT, 580);
99         e_sizer->Add (_content_view, 1, wxALL | wxEXPAND, DCPOMATIC_SIZER_GAP);
100
101         _spl_view = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_NO_HEADER);
102         _spl_view->AppendColumn (wxT(""), wxLIST_FORMAT_LEFT, 80);
103         _spl_view->AppendColumn (wxT(""), wxLIST_FORMAT_LEFT, 80);
104         _spl_view->AppendColumn (wxT(""), wxLIST_FORMAT_LEFT, 580);
105         e_sizer->Add (_spl_view, 1, wxALL | wxEXPAND, DCPOMATIC_SIZER_GAP);
106
107         wxBoxSizer* buttons_sizer = new wxBoxSizer (wxVERTICAL);
108         _add_button = new wxButton(this, wxID_ANY, _("Add"));
109         buttons_sizer->Add (_add_button);
110         _save_button = new wxButton(this, wxID_ANY, _("Save..."));
111         buttons_sizer->Add (_save_button);
112         _load_button = new wxButton(this, wxID_ANY, _("Load..."));
113         buttons_sizer->Add (_load_button);
114         e_sizer->Add (buttons_sizer, 0, wxALL | wxEXPAND, DCPOMATIC_SIZER_GAP);
115
116         _v_sizer->Add (e_sizer, 1, wxEXPAND);
117
118         _log = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(-1, 200), wxTE_READONLY | wxTE_MULTILINE);
119         _v_sizer->Add (_log, 0, wxALL | wxEXPAND, DCPOMATIC_SIZER_GAP);
120
121         _content_view->Show (false);
122         _spl_view->Show (false);
123         _add_button->Show (false);
124         _save_button->Show (false);
125         _load_button->Show (false);
126         _log->Show (false);
127
128         wxBoxSizer* h_sizer = new wxBoxSizer (wxHORIZONTAL);
129
130         wxBoxSizer* time_sizer = new wxBoxSizer (wxVERTICAL);
131         time_sizer->Add (_frame_number, 0, wxEXPAND);
132         time_sizer->Add (_timecode, 0, wxEXPAND);
133
134         h_sizer->Add (_rewind_button, 0, wxALL, 2);
135         h_sizer->Add (_back_button, 0, wxALL, 2);
136         h_sizer->Add (time_sizer, 0, wxEXPAND);
137         h_sizer->Add (_forward_button, 0, wxALL, 2);
138         h_sizer->Add (_play_button, 0, wxEXPAND);
139 #ifdef DCPOMATIC_VARIANT_SWAROOP
140         h_sizer->Add (_pause_button, 0, wxEXPAND);
141         h_sizer->Add (_stop_button, 0, wxEXPAND);
142 #endif
143         h_sizer->Add (_slider, 1, wxEXPAND);
144
145         _v_sizer->Add (h_sizer, 0, wxEXPAND | wxALL, 6);
146
147         _frame_number->SetMinSize (wxSize (84, -1));
148         _rewind_button->SetMinSize (wxSize (32, -1));
149         _back_button->SetMinSize (wxSize (32, -1));
150         _forward_button->SetMinSize (wxSize (32, -1));
151
152         if (_eye) {
153                 _eye->Bind (wxEVT_CHOICE, boost::bind (&Controls::eye_changed, this));
154         }
155         if (_outline_content) {
156                 _outline_content->Bind (wxEVT_CHECKBOX, boost::bind (&Controls::outline_content_changed, this));
157         }
158
159         _slider->Bind           (wxEVT_SCROLL_THUMBTRACK,    boost::bind(&Controls::slider_moved,    this, false));
160         _slider->Bind           (wxEVT_SCROLL_PAGEUP,        boost::bind(&Controls::slider_moved,    this, true));
161         _slider->Bind           (wxEVT_SCROLL_PAGEDOWN,      boost::bind(&Controls::slider_moved,    this, true));
162         _slider->Bind           (wxEVT_SCROLL_CHANGED,       boost::bind(&Controls::slider_released, this));
163 #ifdef DCPOMATIC_VARIANT_SWAROOP
164         _play_button->Bind      (wxEVT_BUTTON,               boost::bind(&Controls::play_clicked,    this));
165         _pause_button->Bind     (wxEVT_BUTTON,               boost::bind(&Controls::pause_clicked,   this));
166         _stop_button->Bind      (wxEVT_BUTTON,               boost::bind(&Controls::stop_clicked,    this));
167 #else
168         _play_button->Bind      (wxEVT_TOGGLEBUTTON,         boost::bind(&Controls::play_clicked,    this));
169 #endif
170         _rewind_button->Bind    (wxEVT_LEFT_DOWN,            boost::bind(&Controls::rewind_clicked,  this, _1));
171         _back_button->Bind      (wxEVT_LEFT_DOWN,            boost::bind(&Controls::back_clicked,    this, _1));
172         _forward_button->Bind   (wxEVT_LEFT_DOWN,            boost::bind(&Controls::forward_clicked, this, _1));
173         _frame_number->Bind     (wxEVT_LEFT_DOWN,            boost::bind(&Controls::frame_number_clicked, this));
174         _timecode->Bind         (wxEVT_LEFT_DOWN,            boost::bind(&Controls::timecode_clicked, this));
175         _content_view->Bind     (wxEVT_LIST_ITEM_SELECTED,   boost::bind(&Controls::setup_sensitivity, this));
176         _content_view->Bind     (wxEVT_LIST_ITEM_DESELECTED, boost::bind(&Controls::setup_sensitivity, this));
177         if (_jump_to_selected) {
178                 _jump_to_selected->Bind (wxEVT_CHECKBOX, boost::bind (&Controls::jump_to_selected_clicked, this));
179                 _jump_to_selected->SetValue (Config::instance()->jump_to_selected ());
180         }
181         _add_button->Bind       (wxEVT_BUTTON,              boost::bind(&Controls::add_clicked, this));
182         _save_button->Bind      (wxEVT_BUTTON,              boost::bind(&Controls::save_clicked, this));
183         _load_button->Bind      (wxEVT_BUTTON,              boost::bind(&Controls::load_clicked, this));
184
185         _viewer->PositionChanged.connect (boost::bind(&Controls::position_changed, this));
186         _viewer->Started.connect (boost::bind(&Controls::started, this));
187         _viewer->Stopped.connect (boost::bind(&Controls::stopped, this));
188         _viewer->FilmChanged.connect (boost::bind(&Controls::film_changed, this));
189         _viewer->ImageChanged.connect (boost::bind(&Controls::image_changed, this, _1));
190
191         film_changed ();
192
193         setup_sensitivity ();
194         update_content_directory ();
195
196         JobManager::instance()->ActiveJobsChanged.connect (
197                 bind (&Controls::active_jobs_changed, this, _2)
198                 );
199
200         _config_changed_connection = Config::instance()->Changed.connect (bind(&Controls::config_changed, this, _1));
201         config_changed (Config::OTHER);
202 }
203
204 void
205 Controls::add_clicked ()
206 {
207         shared_ptr<Content> sel = selected_content ();
208         DCPOMATIC_ASSERT (sel);
209         _film->examine_and_add_content (sel);
210         bool const ok = display_progress (_("DCP-o-matic"), _("Loading DCP"));
211         if (!ok || !report_errors_from_last_job(this)) {
212                 return;
213         }
214         if (_film->content().size() == 1) {
215                 /* Put 1 frame of black at the start so when we seek to 0 we don't see anything */
216                 sel->set_position (DCPTime::from_frames(1, _film->video_frame_rate()));
217         }
218         add_content_to_list (sel, _spl_view);
219         setup_sensitivity ();
220 }
221
222 void
223 Controls::save_clicked ()
224 {
225         wxFileDialog* d = new wxFileDialog (
226                 this, _("Select playlist file"), wxEmptyString, wxEmptyString, wxT ("XML files (*.xml)|*.xml"),
227                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
228                 );
229
230         if (d->ShowModal() == wxID_OK) {
231                 _film->write_metadata(wx_to_std(d->GetPath()));
232         }
233
234         d->Destroy ();
235 }
236
237 void
238 Controls::load_clicked ()
239 {
240         wxFileDialog* d = new wxFileDialog (
241                 this, _("Select playlist file"), wxEmptyString, wxEmptyString, wxT ("XML files (*.xml)|*.xml")
242                 );
243
244         if (d->ShowModal() == wxID_OK) {
245                 _film->read_metadata (boost::filesystem::path(wx_to_std(d->GetPath())));
246                 _spl_view->DeleteAllItems ();
247                 BOOST_FOREACH (shared_ptr<Content> i, _film->content()) {
248                         shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent>(i);
249                         add_content_to_list (dcp, _spl_view);
250                 }
251         }
252
253         d->Destroy ();
254 }
255
256 void
257 Controls::config_changed (int property)
258 {
259         if (property == Config::PLAYER_CONTENT_DIRECTORY) {
260                 update_content_directory ();
261         } else {
262                 setup_sensitivity ();
263         }
264 }
265
266 void
267 Controls::started ()
268 {
269 #ifdef DCPOMATIC_VARIANT_SWAROOP
270         _play_button->Enable (false);
271         _pause_button->Enable (true);
272 #else
273         _play_button->SetValue (true);
274 #endif
275         setup_sensitivity ();
276 }
277
278 void
279 Controls::stopped ()
280 {
281 #ifdef DCPOMATIC_VARIANT_SWAROOP
282         _play_button->Enable (true);
283         _pause_button->Enable (false);
284 #else
285         _play_button->SetValue (false);
286 #endif
287         setup_sensitivity ();
288 }
289
290 void
291 Controls::position_changed ()
292 {
293         if (!_slider_being_moved) {
294                 update_position_label ();
295                 update_position_slider ();
296         }
297 }
298
299 void
300 Controls::eye_changed ()
301 {
302         _viewer->set_eyes (_eye->GetSelection() == 0 ? EYES_LEFT : EYES_RIGHT);
303 }
304
305 void
306 Controls::outline_content_changed ()
307 {
308         _viewer->set_outline_content (_outline_content->GetValue());
309 }
310
311 void
312 Controls::film_change (ChangeType type, Film::Property p)
313 {
314         if (type != CHANGE_TYPE_DONE) {
315                 return;
316         }
317
318         if (p == Film::CONTENT || p == Film::THREE_D) {
319                 setup_sensitivity ();
320         }
321 }
322
323 /** @param page true if this was a PAGEUP/PAGEDOWN event for which we won't receive a THUMBRELEASE */
324 void
325 Controls::slider_moved (bool page)
326 {
327         if (!_film) {
328                 return;
329         }
330
331         if (!page && !_slider_being_moved) {
332                 /* This is the first event of a drag; stop playback for the duration of the drag */
333                 _was_running_before_slider = _viewer->stop ();
334                 _slider_being_moved = true;
335         }
336
337         DCPTime t (_slider->GetValue() * _film->length().get() / 4096);
338         t = t.round (_film->video_frame_rate());
339         /* Ensure that we hit the end of the film at the end of the slider */
340         if (t >= _film->length ()) {
341                 t = _film->length() - _viewer->one_video_frame();
342         }
343         _viewer->seek (t, false);
344         update_position_label ();
345 }
346
347 void
348 Controls::slider_released ()
349 {
350         if (_was_running_before_slider) {
351                 /* Restart after a drag */
352                 _viewer->start ();
353         }
354         _slider_being_moved = false;
355 }
356
357 void
358 Controls::play_clicked ()
359 {
360 #ifdef DCPOMATIC_VARIANT_SWAROOP
361         _viewer->start ();
362 #else
363         check_play_state ();
364 #endif
365 }
366
367
368 #ifndef DCPOMATIC_VARIANT_SWAROOP
369 void
370 Controls::check_play_state ()
371 {
372         if (!_film || _film->video_frame_rate() == 0) {
373                 return;
374         }
375
376         if (_play_button->GetValue()) {
377                 _viewer->start ();
378         } else {
379                 _viewer->stop ();
380         }
381 }
382 #endif
383
384 void
385 Controls::update_position_slider ()
386 {
387         if (!_film) {
388                 _slider->SetValue (0);
389                 return;
390         }
391
392         DCPTime const len = _film->length ();
393
394         if (len.get ()) {
395                 int const new_slider_position = 4096 * _viewer->position().get() / len.get();
396                 if (new_slider_position != _slider->GetValue()) {
397                         _slider->SetValue (new_slider_position);
398                 }
399         }
400 }
401
402 void
403 Controls::update_position_label ()
404 {
405         if (!_film) {
406                 _frame_number->SetLabel ("0");
407                 _timecode->SetLabel ("0:0:0.0");
408                 return;
409         }
410
411         double const fps = _film->video_frame_rate ();
412         /* Count frame number from 1 ... not sure if this is the best idea */
413         _frame_number->SetLabel (wxString::Format (wxT("%ld"), lrint (_viewer->position().seconds() * fps) + 1));
414         _timecode->SetLabel (time_to_timecode (_viewer->position(), fps));
415 }
416
417 void
418 Controls::active_jobs_changed (optional<string> j)
419 {
420         _active_job = j;
421         setup_sensitivity ();
422 }
423
424 DCPTime
425 Controls::nudge_amount (wxKeyboardState& ev)
426 {
427         DCPTime amount = _viewer->one_video_frame ();
428
429         if (ev.ShiftDown() && !ev.ControlDown()) {
430                 amount = DCPTime::from_seconds (1);
431         } else if (!ev.ShiftDown() && ev.ControlDown()) {
432                 amount = DCPTime::from_seconds (10);
433         } else if (ev.ShiftDown() && ev.ControlDown()) {
434                 amount = DCPTime::from_seconds (60);
435         }
436
437         return amount;
438 }
439
440 void
441 Controls::rewind_clicked (wxMouseEvent& ev)
442 {
443         _viewer->seek (DCPTime(), true);
444         ev.Skip();
445 }
446
447 void
448 Controls::back_frame ()
449 {
450         _viewer->seek_by (-_viewer->one_video_frame(), true);
451 }
452
453 void
454 Controls::forward_frame ()
455 {
456         _viewer->seek_by (_viewer->one_video_frame(), true);
457 }
458
459 void
460 Controls::back_clicked (wxKeyboardState& ev)
461 {
462         _viewer->seek_by (-nudge_amount(ev), true);
463 }
464
465 void
466 Controls::forward_clicked (wxKeyboardState& ev)
467 {
468         _viewer->seek_by (nudge_amount(ev), true);
469 }
470
471 void
472 Controls::setup_sensitivity ()
473 {
474         /* examine content is the only job which stops the viewer working */
475         bool const active_job = _active_job && *_active_job != "examine_content";
476         bool const c = _film && !_film->content().empty() && !active_job;
477
478         _slider->Enable (c);
479         _rewind_button->Enable (c);
480         _back_button->Enable (c);
481         _forward_button->Enable (c);
482 #ifdef DCPOMATIC_VARIANT_SWAROOP
483         _play_button->Enable (c && !_viewer->playing());
484         _pause_button->Enable (c && (!_current_kind || _current_kind != dcp::ADVERTISEMENT) && _viewer->playing());
485         _stop_button->Enable (c && (!_current_kind || _current_kind != dcp::ADVERTISEMENT));
486         _slider->Enable (c && (!_current_kind || _current_kind != dcp::ADVERTISEMENT));
487 #else
488         _play_button->Enable (c);
489 #endif
490         if (_outline_content) {
491                 _outline_content->Enable (c);
492         }
493         _frame_number->Enable (c);
494         _timecode->Enable (c);
495         if (_jump_to_selected) {
496                 _jump_to_selected->Enable (c);
497         }
498
499         if (_eye) {
500                 _eye->Enable (c && _film->three_d ());
501         }
502
503         _add_button->Enable (Config::instance()->allow_spl_editing() && static_cast<bool>(selected_content()));
504         _save_button->Enable (Config::instance()->allow_spl_editing());
505 }
506
507 shared_ptr<Content>
508 Controls::selected_content () const
509 {
510         long int s = _content_view->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
511         if (s == -1) {
512                 return shared_ptr<Content>();
513         }
514
515         DCPOMATIC_ASSERT (s < int(_content.size()));
516         return _content[s];
517 }
518
519 void
520 Controls::timecode_clicked ()
521 {
522         PlayheadToTimecodeDialog* dialog = new PlayheadToTimecodeDialog (this, _film->video_frame_rate ());
523         if (dialog->ShowModal() == wxID_OK) {
524                 _viewer->seek (dialog->get(), true);
525         }
526         dialog->Destroy ();
527 }
528
529 void
530 Controls::frame_number_clicked ()
531 {
532         PlayheadToFrameDialog* dialog = new PlayheadToFrameDialog (this, _film->video_frame_rate ());
533         if (dialog->ShowModal() == wxID_OK) {
534                 _viewer->seek (dialog->get(), true);
535         }
536         dialog->Destroy ();
537 }
538
539 void
540 Controls::jump_to_selected_clicked ()
541 {
542         Config::instance()->set_jump_to_selected (_jump_to_selected->GetValue ());
543 }
544
545 void
546 Controls::film_changed ()
547 {
548         shared_ptr<Film> film = _viewer->film ();
549
550         if (_film == film) {
551                 return;
552         }
553
554         _film = film;
555
556         setup_sensitivity ();
557
558         update_position_slider ();
559         update_position_label ();
560
561         if (_film) {
562                 _film->Change.connect (boost::bind (&Controls::film_change, this, _1, _2));
563         }
564 }
565
566 shared_ptr<Film>
567 Controls::film () const
568 {
569         return _film;
570 }
571
572 void
573 Controls::show_extended_player_controls (bool s)
574 {
575         _content_view->Show (s);
576         if (s) {
577                 update_content_directory ();
578         }
579         _spl_view->Show (s);
580         _log->Show (s);
581         _add_button->Show (s);
582         _save_button->Show (s);
583         _load_button->Show (s);
584         _v_sizer->Layout ();
585 }
586
587 void
588 Controls::add_content_to_list (shared_ptr<Content> content, wxListCtrl* ctrl)
589 {
590         int const N = ctrl->GetItemCount();
591
592         wxListItem it;
593         it.SetId(N);
594         it.SetColumn(0);
595         DCPTime length = content->length_after_trim ();
596         int seconds = length.seconds();
597         int minutes = seconds / 60;
598         seconds -= minutes * 60;
599         int hours = minutes / 60;
600         minutes -= hours * 60;
601         it.SetText(wxString::Format("%02d:%02d:%02d", hours, minutes, seconds));
602         ctrl->InsertItem(it);
603
604         shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent>(content);
605         if (dcp && dcp->content_kind()) {
606                 it.SetId(N);
607                 it.SetColumn(1);
608                 it.SetText(std_to_wx(dcp::content_kind_to_string(*dcp->content_kind())));
609                 ctrl->SetItem(it);
610         }
611
612         it.SetId(N);
613         it.SetColumn(2);
614         it.SetText(std_to_wx(content->summary()));
615         ctrl->SetItem(it);
616 }
617
618 void
619 Controls::update_content_directory ()
620 {
621         if (!_content_view->IsShown()) {
622                 return;
623         }
624
625         using namespace boost::filesystem;
626
627         _content_view->DeleteAllItems ();
628         _content.clear ();
629         optional<path> dir = Config::instance()->player_content_directory();
630         if (!dir) {
631                 return;
632         }
633
634         wxProgressDialog progress (_("DCP-o-matic"), _("Reading content directory"));
635         JobManager* jm = JobManager::instance ();
636
637         for (directory_iterator i = directory_iterator(*dir); i != directory_iterator(); ++i) {
638                 try {
639                         shared_ptr<Content> content;
640                         if (is_directory(*i) && (is_regular_file(*i / "ASSETMAP") || is_regular_file(*i / "ASSETMAP.xml"))) {
641                                 content.reset (new DCPContent(_film, *i));
642                         } else if (i->path().extension() == ".mp4") {
643                                 content = content_factory(_film, *i).front();
644                         }
645
646                         if (content) {
647                                 jm->add (shared_ptr<Job>(new ExamineContentJob(_film, content)));
648                                 while (jm->work_to_do()) {
649                                         if (!progress.Pulse()) {
650                                                 /* user pressed cancel */
651                                                 BOOST_FOREACH (shared_ptr<Job> i, jm->get()) {
652                                                         i->cancel();
653                                                 }
654                                                 return;
655                                         }
656                                         dcpomatic_sleep (1);
657                                 }
658                                 if (report_errors_from_last_job (this)) {
659                                         add_content_to_list (content, _content_view);
660                                         _content.push_back (content);
661                                 }
662                         }
663                 } catch (boost::filesystem::filesystem_error& e) {
664                         /* Never mind */
665                 } catch (dcp::DCPReadError& e) {
666                         /* Never mind */
667                 }
668         }
669 }
670
671 #ifdef DCPOMATIC_VARIANT_SWAROOP
672 void
673 Controls::pause_clicked ()
674 {
675         _viewer->stop ();
676 }
677
678 void
679 Controls::stop_clicked ()
680 {
681         _viewer->stop ();
682         _viewer->seek (DCPTime(), true);
683 }
684 #endif
685
686 void
687 Controls::log (wxString s)
688 {
689         struct timeval time;
690         gettimeofday (&time, 0);
691         char buffer[64];
692         time_t const sec = time.tv_sec;
693         struct tm* t = localtime (&sec);
694         strftime (buffer, 64, "%c", t);
695         wxString ts = std_to_wx(string(buffer)) + N_(": ");
696         _log->SetValue(_log->GetValue() + ts + s + "\n");
697 }
698
699 void
700 Controls::image_changed (boost::weak_ptr<PlayerVideo> weak_pv)
701 {
702 #ifdef DCPOMATIC_VARIANT_SWAROOP
703         shared_ptr<PlayerVideo> pv = weak_pv.lock ();
704         if (!pv) {
705                 return;
706         }
707
708         shared_ptr<Content> c = pv->content().lock();
709         if (!c) {
710                 return;
711         }
712
713         shared_ptr<DCPContent> dc = dynamic_pointer_cast<DCPContent> (c);
714         if (!dc) {
715                 return;
716         }
717
718         if (!_current_kind || *_current_kind != dc->content_kind()) {
719                 _current_kind = dc->content_kind ();
720                 setup_sensitivity ();
721         }
722 #endif
723 }