diff options
| author | Carl Hetherington <cth@carlh.net> | 2025-05-31 02:40:50 +0200 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2025-06-17 00:04:03 +0200 |
| commit | dce8911cbc577f9f8f272d78c455e708c0b47aa9 (patch) | |
| tree | 7884dc6b40bd88c3bef6e4b354799c7044e32756 | |
| parent | ec06811bae7ed4fc6bd80c3154fd473028ee8e13 (diff) | |
Support cropping of content on playback (#3041).
This is to allow, for example, pillarboxed 1.78:1 DCPs to be played nicely
on 1.78:1 projectors. DCP-o-matic can now crop the pillarboxing before
display, rather than putting the pillarboxed 1.85:1 onto a 1.78:1 monitor,
thereby adding letterboxing.
| -rw-r--r-- | src/lib/config.cc | 6 | ||||
| -rw-r--r-- | src/lib/config.h | 18 | ||||
| -rw-r--r-- | src/tools/dcpomatic_player.cc | 24 | ||||
| -rw-r--r-- | src/wx/player_config_dialog.cc | 103 |
4 files changed, 149 insertions, 2 deletions
diff --git a/src/lib/config.cc b/src/lib/config.cc index d8402e2ec..5f3f2f6fe 100644 --- a/src/lib/config.cc +++ b/src/lib/config.cc @@ -614,6 +614,8 @@ try _player_restricted_menus = f.optional_bool_child("PlayerRestrictedMenus").get_value_or(false); _playlist_editor_restricted_menus = f.optional_bool_child("PlaylistEditorRestrictedMenus").get_value_or(false); + _player_crop_output_ratio = f.optional_number_child<float>("PlayerCropOutputRatio"); + _image_display = f.optional_number_child<int>("ImageDisplay").get_value_or(0); auto vc = f.optional_string_child("VideoViewType"); if (vc && *vc == "opengl") { @@ -1081,6 +1083,10 @@ Config::write_config() const cxml::add_text_child(root, "PlaylistEditorRestrictedMenus", "1"); } + if (_player_crop_output_ratio) { + cxml::add_text_child(root, "PlayerCropOutputRatio", fmt::to_string(*_player_crop_output_ratio)); + } + /* [XML] ImageDisplay Screen number to put image on in dual-screen player mode. */ cxml::add_text_child(root, "ImageDisplay", fmt::to_string(_image_display)); switch (_video_view_type) { diff --git a/src/lib/config.h b/src/lib/config.h index 9f6d1c743..2bc700e86 100644 --- a/src/lib/config.h +++ b/src/lib/config.h @@ -554,6 +554,10 @@ public: return _playlist_editor_restricted_menus; } + boost::optional<float> player_crop_output_ratio() const { + return _player_crop_output_ratio; + } + int image_display() const { return _image_display; } @@ -1101,6 +1105,19 @@ public: maybe_set(_player_mode, m); } + void set_player_crop_output_ratio(float ratio) { + maybe_set(_player_crop_output_ratio, ratio); + } + + void unset_player_crop_output_ratio() { + if (!_player_crop_output_ratio) { + return; + } + + _player_crop_output_ratio = boost::none; + changed(OTHER); + } + void set_image_display(int n) { maybe_set(_image_display, n); } @@ -1459,6 +1476,7 @@ private: PlayerMode _player_mode; bool _player_restricted_menus = false; bool _playlist_editor_restricted_menus = false; + boost::optional<float> _player_crop_output_ratio; int _image_display; VideoViewType _video_view_type; bool _respect_kdm_validity_periods; diff --git a/src/tools/dcpomatic_player.cc b/src/tools/dcpomatic_player.cc index ffb4f5dbd..60d6911ef 100644 --- a/src/tools/dcpomatic_player.cc +++ b/src/tools/dcpomatic_player.cc @@ -494,7 +494,7 @@ public: } /* Start off as Flat */ - _film->set_container (Ratio::from_id("185")); + auto auto_ratio = Ratio::from_id("185"); _film->set_audio_channels (MAX_DCP_AUDIO_CHANNELS); @@ -509,7 +509,7 @@ public: auto const r = Ratio::nearest_from_ratio(i->video->size()->ratio()); if (r.id() == "239") { /* Any scope content means we use scope */ - _film->set_container(r); + auto_ratio = r; } } @@ -539,6 +539,8 @@ public: _cpl_menu->Remove (i); } + auto const crop = Config::instance()->player_crop_output_ratio(); + if (_film->content().size() == 1) { /* Offer a CPL menu */ auto first = dynamic_pointer_cast<DCPContent>(_film->content().front()); @@ -553,6 +555,24 @@ public: ++id; } } + + if (crop) { + auto size = _film->content()[0]->video->size().get_value_or({1998, 1080}); + int pixels = 0; + if (*crop > (2048.0 / 1080.0)) { + pixels = (size.height - (size.width / *crop)) / 2; + _film->content()[0]->video->set_crop(Crop{0, 0, pixels, pixels}); + } else { + pixels = (size.width - (size.height * *crop)) / 2; + _film->content()[0]->video->set_crop(Crop{pixels, pixels, 0, 0}); + } + } + } + + if (crop) { + _film->set_container(Ratio(*crop, "custom", "custom", {}, "custom")); + } else { + _film->set_container(auto_ratio); } } diff --git a/src/wx/player_config_dialog.cc b/src/wx/player_config_dialog.cc index b853503c6..affe96bf0 100644 --- a/src/wx/player_config_dialog.cc +++ b/src/wx/player_config_dialog.cc @@ -25,6 +25,7 @@ #include "check_box.h" +#include "dcpomatic_choice.h" #include "dir_picker_ctrl.h" #include "editable_list.h" #include "email_dialog.h" @@ -53,6 +54,7 @@ #include <dcp/certificate_chain.h> #include <dcp/exceptions.h> #include <dcp/locale_convert.h> +#include <dcp/scope_guard.h> #include <dcp/warnings.h> LIBDCP_DISABLE_WARNINGS #include <wx/filepicker.h> @@ -299,6 +301,22 @@ private: table->AddGrowableCol(1, 1); _panel->GetSizer()->Add(table, 1, wxALL | wxEXPAND, _border); + _crop_output = new CheckBox(_panel, _("Crop output to")); + table->Add(_crop_output, 0, wxEXPAND); + auto s = new wxBoxSizer(wxHORIZONTAL); + _crop_output_ratio_preset = new Choice(_panel); + _crop_output_ratio_custom = new wxTextCtrl(_panel, wxID_ANY); + + s->Add(_crop_output_ratio_preset, 0, wxEXPAND | wxRIGHT, DCPOMATIC_SIZER_X_GAP); + s->Add(_crop_output_ratio_custom, 0, wxEXPAND | wxRIGHT, DCPOMATIC_SIZER_X_GAP); + add_label_to_sizer(s, _panel, _(":1"), false, 0, wxALIGN_CENTER_VERTICAL); + + for (auto ratio: Ratio::all()) { + _crop_output_ratio_preset->add_entry(ratio.image_nickname(), ratio.id()); + } + _crop_output_ratio_preset->add_entry(_("Custom"), "custom"); + table->Add(s, 1, wxEXPAND); + { add_top_aligned_label_to_sizer(table, _panel, _("Log")); auto t = new wxBoxSizer(wxVERTICAL); @@ -333,6 +351,13 @@ private: #ifdef DCPOMATIC_WINDOWS _win32_console->bind(&PlayerAdvancedPage::win32_console_changed, this); #endif + _crop_output->bind(&PlayerAdvancedPage::crop_output_changed, this); + _crop_output_ratio_preset->bind(&PlayerAdvancedPage::crop_output_ratio_preset_changed, this); + _crop_output_ratio_custom->Bind(wxEVT_TEXT, boost::bind(&PlayerAdvancedPage::crop_output_ratio_custom_changed, this)); + + checked_set(_crop_output, Config::instance()->player_crop_output_ratio().has_value()); + set_crop_output_ratio_preset_from_config(); + set_crop_output_ratio_custom_from_config(); } void config_changed() override @@ -348,6 +373,36 @@ private: #ifdef DCPOMATIC_WINDOWS checked_set(_win32_console, config->win32_console()); #endif + + /* Don't update crop ratio stuff here as the controls are all interdependent + * and nobody else is going to change the values. + */ + } + + void set_crop_output_ratio_preset_from_config() + { + _ignore_crop_changes = true; + dcp::ScopeGuard sg = [this]() { _ignore_crop_changes = false; }; + + if (auto output_ratio = Config::instance()->player_crop_output_ratio()) { + auto ratio = Ratio::from_ratio(*output_ratio); + _crop_output_ratio_preset->set_by_data(ratio ? ratio->id() : "custom"); + } else { + _crop_output_ratio_preset->set_by_data("185"); + } + } + + void set_crop_output_ratio_custom_from_config() + { + _ignore_crop_changes = true; + dcp::ScopeGuard sg = [this]() { _ignore_crop_changes = false; }; + + if (auto output_ratio = Config::instance()->player_crop_output_ratio()) { + auto ratio = Ratio::from_ratio(*output_ratio); + _crop_output_ratio_custom->SetValue(wxString::Format(char_to_wx("%.2f"), ratio ? ratio->ratio() : *output_ratio)); + } else { + _crop_output_ratio_custom->SetValue(_("185")); + } } void log_changed() @@ -374,6 +429,50 @@ private: Config::instance()->set_log_types(types); } + void crop_output_changed() + { + if (_crop_output->get()) { + Config::instance()->set_player_crop_output_ratio(1.85); + set_crop_output_ratio_preset_from_config(); + set_crop_output_ratio_custom_from_config(); + } else { + Config::instance()->unset_player_crop_output_ratio(); + } + _crop_output_ratio_preset->Enable(_crop_output->get()); + _crop_output_ratio_custom->Enable(_crop_output->get()); + } + + void crop_output_ratio_preset_changed() + { + if (!_crop_output->get() || _ignore_crop_changes) { + return; + } + + optional<Ratio> ratio; + + if (auto data = _crop_output_ratio_preset->get_data()) { + ratio = Ratio::from_id_if_exists(*data); + } + + if (ratio) { + Config::instance()->set_player_crop_output_ratio(ratio->ratio()); + } else { + Config::instance()->set_player_crop_output_ratio(locale_convert<float>(wx_to_std(_crop_output_ratio_custom->GetValue()))); + } + + set_crop_output_ratio_custom_from_config(); + } + + void crop_output_ratio_custom_changed() + { + if (!_crop_output->get() || _ignore_crop_changes) { + return; + } + + Config::instance()->set_player_crop_output_ratio(locale_convert<float>(wx_to_std(_crop_output_ratio_custom->GetValue()))); + set_crop_output_ratio_preset_from_config(); + } + #ifdef DCPOMATIC_WINDOWS void win32_console_changed() { @@ -381,6 +480,10 @@ private: } #endif + CheckBox* _crop_output = nullptr; + Choice* _crop_output_ratio_preset = nullptr; + wxTextCtrl* _crop_output_ratio_custom = nullptr; + bool _ignore_crop_changes = false; CheckBox* _log_general = nullptr; CheckBox* _log_warning = nullptr; CheckBox* _log_error = nullptr; |
