*/
-#include "wx/wx_signal_manager.h"
-#include "wx/wx_util.h"
#include "wx/about_dialog.h"
-#include "wx/report_problem_dialog.h"
#include "wx/film_viewer.h"
-#include "wx/player_information.h"
-#include "wx/update_dialog.h"
+#include "wx/nag_dialog.h"
#include "wx/player_config_dialog.h"
-#include "wx/verify_dcp_dialog.h"
-#include "wx/standard_controls.h"
+#include "wx/player_information.h"
+#include "wx/player_stress_tester.h"
#include "wx/playlist_controls.h"
-#include "wx/timer_display.h"
+#include "wx/report_problem_dialog.h"
+#include "wx/standard_controls.h"
#include "wx/system_information_dialog.h"
-#include "wx/player_stress_tester.h"
+#include "wx/timer_display.h"
+#include "wx/update_dialog.h"
+#include "wx/verify_dcp_dialog.h"
#include "wx/verify_dcp_progress_dialog.h"
-#include "lib/cross.h"
-#include "lib/config.h"
-#include "lib/util.h"
-#include "lib/internet.h"
-#include "lib/update_checker.h"
+#include "wx/wx_signal_manager.h"
+#include "wx/wx_util.h"
#include "lib/compose.hpp"
+#include "lib/config.h"
+#include "lib/cross.h"
#include "lib/dcp_content.h"
-#include "lib/job_manager.h"
-#include "lib/job.h"
-#include "lib/film.h"
-#include "lib/null_log.h"
-#include "lib/video_content.h"
-#include "lib/text_content.h"
-#include "lib/ratio.h"
-#include "lib/verify_dcp_job.h"
#include "lib/dcp_examiner.h"
-#include "lib/examine_content_job.h"
-#include "lib/server.h"
+#include "lib/dcpomatic_log.h"
#include "lib/dcpomatic_socket.h"
-#include "lib/scoped_temporary.h"
+#include "lib/examine_content_job.h"
#include "lib/ffmpeg_content.h"
-#include "lib/dcpomatic_log.h"
#include "lib/file_log.h"
+#include "lib/film.h"
+#include "lib/image.h"
+#include "lib/image_jpeg.h"
+#include "lib/image_png.h"
+#include "lib/internet.h"
+#include "lib/job.h"
+#include "lib/job_manager.h"
+#include "lib/null_log.h"
+#include "lib/player.h"
+#include "lib/player_video.h"
+#include "lib/ratio.h"
+#include "lib/scoped_temporary.h"
+#include "lib/server.h"
+#include "lib/text_content.h"
+#include "lib/update_checker.h"
+#include "lib/util.h"
+#include "lib/verify_dcp_job.h"
+#include "lib/video_content.h"
#include <dcp/cpl.h>
#include <dcp/dcp.h>
-#include <dcp/raw_convert.h>
#include <dcp/exceptions.h>
-#include <wx/wx.h>
-#include <wx/stdpaths.h>
-#include <wx/splash.h>
+#include <dcp/raw_convert.h>
+#include <dcp/search.h>
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
#include <wx/cmdline.h>
+#include <wx/display.h>
#include <wx/preferences.h>
#include <wx/progdlg.h>
-#include <wx/display.h>
+#include <wx/splash.h>
+#include <wx/stdpaths.h>
+#include <wx/wx.h>
+LIBDCP_ENABLE_WARNINGS
#ifdef __WXGTK__
#include <X11/Xlib.h>
#endif
-#include <boost/bind/bind.hpp>
#include <boost/algorithm/string.hpp>
+#include <boost/bind/bind.hpp>
#include <iostream>
#ifdef check
#undef check
#endif
+
#define MAX_CPLS 32
+
using std::cout;
using std::dynamic_pointer_cast;
using std::exception;
using std::string;
using std::vector;
using std::weak_ptr;
-using boost::scoped_array;
+using boost::bind;
using boost::optional;
+using boost::scoped_array;
using boost::thread;
-using boost::bind;
#if BOOST_VERSION >= 106100
using namespace boost::placeholders;
#endif
using dcp::raw_convert;
using namespace dcpomatic;
+
enum {
ID_file_open = 1,
ID_file_add_ov,
ID_file_add_kdm,
+ ID_file_save_frame,
ID_file_history,
/* Allow spare IDs after _history for the recent files list */
ID_file_close = 100,
{
public:
DOMFrame ()
- : wxFrame (0, -1, _("DCP-o-matic Player"))
- , _dual_screen (0)
- , _update_news_requested (false)
- , _info (0)
+ : wxFrame (nullptr, -1, _("DCP-o-matic Player"))
, _mode (Config::instance()->player_mode())
- , _config_dialog (0)
- , _file_menu (0)
- , _history_items (0)
- , _history_position (0)
- , _history_separator (0)
- , _system_information_dialog (0)
- , _view_full_screen (0)
- , _view_dual_screen (0)
- , _main_sizer (new wxBoxSizer (wxVERTICAL))
+ , _main_sizer (new wxBoxSizer(wxVERTICAL))
{
dcpomatic_log = make_shared<NullLog>();
Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_open, this), ID_file_open);
Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_add_ov, this), ID_file_add_ov);
Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_add_kdm, this), ID_file_add_kdm);
+ Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_save_frame, this), ID_file_save_frame);
Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_history, this, _1), ID_file_history, ID_file_history + HISTORY_SIZE);
Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_close, this), ID_file_close);
Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_exit, this), wxID_EXIT);
*/
_overall_panel = new wxPanel (this, wxID_ANY);
- _viewer.reset (new FilmViewer (_overall_panel));
+ _viewer = make_shared<FilmViewer>(_overall_panel);
if (Config::instance()->player_mode() == Config::PLAYER_MODE_DUAL) {
auto pc = new PlaylistControls (_overall_panel, _viewer);
_controls = pc;
_controls = new StandardControls (_overall_panel, _viewer, false);
}
_viewer->set_dcp_decode_reduction (Config::instance()->decode_reduction ());
+ _viewer->set_optimise_for_j2k (true);
_viewer->PlaybackPermitted.connect (bind(&DOMFrame::playback_permitted, this));
- _viewer->Started.connect (bind(&DOMFrame::playback_started, this, _1));
- _viewer->Stopped.connect (bind(&DOMFrame::playback_stopped, this, _1));
+ _viewer->TooManyDropped.connect (bind(&DOMFrame::too_many_frames_dropped, this));
_info = new PlayerInformation (_overall_panel, _viewer);
setup_main_sizer (Config::instance()->player_mode());
#ifdef __WXOSX__
_stress.setup (this, _controls);
- wxAcceleratorEntry* accel = new wxAcceleratorEntry[accelerators];
+ std::vector<wxAcceleratorEntry> accel(accelerators);
accel[0].Set(wxACCEL_NORMAL, WXK_SPACE, ID_start_stop);
accel[1].Set(wxACCEL_NORMAL, WXK_LEFT, ID_go_back_frame);
accel[2].Set(wxACCEL_NORMAL, WXK_RIGHT, ID_go_forward_frame);
#ifdef __WXOSX__
accel[11].Set(wxACCEL_CTRL, static_cast<int>('W'), ID_file_close);
#endif
- wxAcceleratorTable accel_table (accelerators, accel);
+ wxAcceleratorTable accel_table (accelerators, accel.data());
SetAcceleratorTable (accel_table);
- delete[] accel;
Bind (wxEVT_MENU, boost::bind(&DOMFrame::start_stop_pressed, this), ID_start_stop);
Bind (wxEVT_MENU, boost::bind(&DOMFrame::go_back_frame, this), ID_go_back_frame);
reset_film ();
- UpdateChecker::instance()->StateChanged.connect (boost::bind (&DOMFrame::update_checker_state_changed, this));
+ UpdateChecker::instance()->StateChanged.connect (boost::bind(&DOMFrame::update_checker_state_changed, this));
setup_screen ();
_stress.LoadDCP.connect (boost::bind(&DOMFrame::load_dcp, this, _1));
return ok;
}
- void playback_started (DCPTime time)
- {
- /* XXX: this only logs the first piece of content; probably should be each piece? */
- if (_film->content().empty()) {
- return;
- }
-
- auto dcp = dynamic_pointer_cast<DCPContent>(_film->content().front());
- if (dcp) {
- DCPExaminer ex (dcp, true);
- shared_ptr<dcp::CPL> playing_cpl;
- for (auto i: ex.cpls()) {
- if (!dcp->cpl() || i->id() == *dcp->cpl()) {
- playing_cpl = i;
- }
- }
- DCPOMATIC_ASSERT (playing_cpl);
-
- _controls->log (
- wxString::Format(
- "playback-started %s %s %s",
- time.timecode(_film->video_frame_rate()).c_str(),
- dcp->directories().front().string().c_str(),
- playing_cpl->annotation_text().get_value_or("").c_str()
- )
- );
- }
- auto ffmpeg = dynamic_pointer_cast<FFmpegContent>(_film->content().front());
- if (ffmpeg) {
- _controls->log (
- wxString::Format(
- "playback-started %s %s",
- time.timecode(_film->video_frame_rate()).c_str(),
- ffmpeg->path(0).string().c_str()
- )
- );
+ void too_many_frames_dropped ()
+ {
+ if (!Config::instance()->nagged(Config::NAG_TOO_MANY_DROPPED_FRAMES)) {
+ _viewer->stop ();
}
- }
- void playback_stopped (DCPTime time)
- {
- _controls->log (wxString::Format("playback-stopped %s", time.timecode(_film->video_frame_rate()).c_str()));
+ NagDialog::maybe_nag (
+ this,
+ Config::NAG_TOO_MANY_DROPPED_FRAMES,
+ _(wxS("The player is dropping a lot of frames, so playback may not be accurate.\n\n"
+ "<b>This does not necessarily mean that the DCP you are playing is defective!</b>\n\n"
+ "You may be able to improve player performance by:\n"
+ "• choosing 'decode at half resolution' or 'decode at quarter resolution' from the View menu\n"
+ "• using a more powerful computer.\n"
+ ))
+ );
}
void set_decode_reduction (optional<int> reduction)
/* Offer a CPL menu */
auto first = dynamic_pointer_cast<DCPContent>(_film->content().front());
if (first) {
- DCPExaminer ex (first, true);
int id = ID_view_cpl;
- for (auto i: ex.cpls()) {
+ for (auto i: dcp::find_and_resolve_cpls(first->directories(), true)) {
auto j = _cpl_menu->AppendRadioItem(
id,
wxString::Format("%s (%s)", std_to_wx(i->annotation_text().get_value_or("")).data(), std_to_wx(i->id()).data())
_file_menu->Append (ID_file_open, _("&Open...\tCtrl-O"));
_file_add_ov = _file_menu->Append (ID_file_add_ov, _("&Add OV..."));
_file_add_kdm = _file_menu->Append (ID_file_add_kdm, _("Add &KDM..."));
+ _file_menu->AppendSeparator ();
+ _file_save_frame = _file_menu->Append (ID_file_save_frame, _("&Save frame to file...\tCtrl-S"));
_history_position = _file_menu->GetMenuItems().GetCount();
_info->triggered_update ();
}
+ void file_save_frame ()
+ {
+ wxFileDialog dialog (this, _("Save frame to file"), "", "", "PNG files (*.png)|*.png|JPEG files (*.jpg,*.jpeg)|*.jpg,*.jpeg", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
+ if (dialog.ShowModal() == wxID_CANCEL) {
+ return;
+ }
+
+ auto path = boost::filesystem::path (wx_to_std(dialog.GetPath()));
+
+ auto player = make_shared<Player>(_film, Image::Alignment::PADDED);
+ player->seek (_viewer->position(), true);
+
+ bool done = false;
+ player->Video.connect ([path, &done, this](shared_ptr<PlayerVideo> video, DCPTime) {
+ auto ext = boost::algorithm::to_lower_copy(path.extension().string());
+ if (ext == ".png") {
+ auto image = video->image(boost::bind(PlayerVideo::force, AV_PIX_FMT_RGBA), VideoRange::FULL, false);
+ image_as_png(image).write(path);
+ } else if (ext == ".jpg" || ext == ".jpeg") {
+ auto image = video->image(boost::bind(PlayerVideo::force, AV_PIX_FMT_RGB24), VideoRange::FULL, false);
+ image_as_jpeg(image, 80).write(path);
+ } else {
+ error_dialog (this, _(wxString::Format("Unrecognised file extension %s (use .jpg, .jpeg or .png)", std_to_wx(ext))));
+ }
+ done = true;
+ });
+
+ int tries_left = 50;
+ while (!done && tries_left >= 0) {
+ player->pass();
+ --tries_left;
+ }
+
+ DCPOMATIC_ASSERT (tries_left >= 0);
+ }
+
void file_history (wxCommandEvent& event)
{
auto history = Config::instance()->player_history ();
{
auto dcp = std::dynamic_pointer_cast<DCPContent>(_film->content().front());
DCPOMATIC_ASSERT (dcp);
- DCPExaminer ex (dcp, true);
+ auto cpls = dcp::find_and_resolve_cpls (dcp->directories(), true);
int id = ev.GetId() - ID_view_cpl;
DCPOMATIC_ASSERT (id >= 0);
- DCPOMATIC_ASSERT (id < int(ex.cpls().size()));
- auto cpls = ex.cpls();
+ DCPOMATIC_ASSERT (id < int(cpls.size()));
auto i = cpls.begin();
while (id > 0) {
++i;
_dual_screen->SetBackgroundColour (wxColour(0, 0, 0));
_dual_screen->ShowFullScreen (true);
_viewer->panel()->Reparent (_dual_screen);
+ _viewer->panel()->SetFocus();
_dual_screen->Show ();
if (wxDisplay::GetCount() > 1) {
switch (Config::instance()->image_display()) {
break;
}
}
+ _dual_screen->Bind(wxEVT_CHAR_HOOK, boost::bind(&DOMFrame::dual_screen_key_press, this, _1));
} else {
if (_dual_screen) {
_viewer->panel()->Reparent (_overall_panel);
setup_main_sizer (_mode);
}
+ void dual_screen_key_press(wxKeyEvent& ev)
+ {
+ if (ev.GetKeyCode() == WXK_F11) {
+ if (ev.ShiftDown()) {
+ view_dual_screen();
+ } else if (!ev.HasAnyModifiers()) {
+ view_full_screen();
+ }
+ }
+ }
+
void view_closed_captions ()
{
_viewer->show_closed_captions ();
_tools_verify->Enable (static_cast<bool>(_film));
_file_add_ov->Enable (static_cast<bool>(_film));
_file_add_kdm->Enable (static_cast<bool>(_film));
+ _file_save_frame->Enable (static_cast<bool>(_film));
_view_cpl->Enable (static_cast<bool>(_film));
}
_viewer->seek (_film->length() - _viewer->one_video_frame(), true);
}
- wxFrame* _dual_screen;
- bool _update_news_requested;
- PlayerInformation* _info;
+ wxFrame* _dual_screen = nullptr;
+ bool _update_news_requested = false;
+ PlayerInformation* _info = nullptr;
Config::PlayerMode _mode;
- wxPreferencesEditor* _config_dialog;
- wxPanel* _overall_panel;
- wxMenu* _file_menu;
- wxMenuItem* _view_cpl;
- wxMenu* _cpl_menu;
- int _history_items;
- int _history_position;
- wxMenuItem* _history_separator;
+ wxPreferencesEditor* _config_dialog = nullptr;
+ wxPanel* _overall_panel = nullptr;
+ wxMenu* _file_menu = nullptr;
+ wxMenuItem* _view_cpl = nullptr;
+ wxMenu* _cpl_menu = nullptr;
+ int _history_items = 0;
+ int _history_position = 0;
+ wxMenuItem* _history_separator = nullptr;
shared_ptr<FilmViewer> _viewer;
Controls* _controls;
- SystemInformationDialog* _system_information_dialog;
+ SystemInformationDialog* _system_information_dialog = nullptr;
std::shared_ptr<Film> _film;
boost::signals2::scoped_connection _config_changed_connection;
boost::signals2::scoped_connection _examine_job_connection;
- wxMenuItem* _file_add_ov;
- wxMenuItem* _file_add_kdm;
- wxMenuItem* _tools_verify;
- wxMenuItem* _view_full_screen;
- wxMenuItem* _view_dual_screen;
- wxSizer* _main_sizer;
+ wxMenuItem* _file_add_ov = nullptr;
+ wxMenuItem* _file_add_kdm = nullptr;
+ wxMenuItem* _file_save_frame = nullptr;
+ wxMenuItem* _tools_verify = nullptr;
+ wxMenuItem* _view_full_screen = nullptr;
+ wxMenuItem* _view_dual_screen = nullptr;
+ wxSizer* _main_sizer = nullptr;
PlayerStressTester _stress;
};
, _frame (frame)
{}
- void handle (shared_ptr<Socket> socket)
+ void handle (shared_ptr<Socket> socket) override
{
try {
int const length = socket->read_uint32 ();
public:
App ()
: wxApp ()
- , _frame (0)
{
#ifdef DCPOMATIC_LINUX
XInitThreads ();
private:
- bool OnInit ()
+ bool OnInit () override
{
- wxSplashScreen* splash = 0;
+ wxSplashScreen* splash = nullptr;
try {
wxInitAllImageHandlers ();
_frame->Maximize ();
if (splash) {
splash->Destroy ();
- splash = 0;
+ splash = nullptr;
}
_frame->Show ();
- PlayServer* server = new PlayServer (_frame);
- new thread (boost::bind (&PlayServer::run, server));
+ try {
+ auto server = new PlayServer (_frame);
+ new thread (boost::bind (&PlayServer::run, server));
+ } catch (std::exception& e) {
+ /* This is not the end of the world; probably a failure to bind the server socket
+ * because there's already another player running.
+ */
+ LOG_DEBUG_PLAYER ("Failed to start play server (%1)", e.what());
+ }
if (!_dcp_to_load.empty() && boost::filesystem::is_directory (_dcp_to_load)) {
try {
return true;
}
- void OnInitCmdLine (wxCmdLineParser& parser)
+ void OnInitCmdLine (wxCmdLineParser& parser) override
{
parser.SetDesc (command_line_description);
parser.SetSwitchChars (wxT ("-"));
}
- bool OnCmdLineParsed (wxCmdLineParser& parser)
+ bool OnCmdLineParsed (wxCmdLineParser& parser) override
{
if (parser.GetParamCount() > 0) {
_dcp_to_load = wx_to_std (parser.GetParam (0));
}
/* An unhandled exception has occurred inside the main event loop */
- bool OnExceptionInMainLoop ()
+ bool OnExceptionInMainLoop () override
{
report_exception ();
/* This will terminate the program */
return false;
}
- void OnUnhandledException ()
+ void OnUnhandledException () override
{
report_exception ();
}
message_dialog (_frame, std_to_wx (m));
}
- DOMFrame* _frame;
+ DOMFrame* _frame = nullptr;
string _dcp_to_load;
boost::optional<string> _stress;
};