summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2025-05-31 02:40:50 +0200
committerCarl Hetherington <cth@carlh.net>2025-06-17 00:04:03 +0200
commitdce8911cbc577f9f8f272d78c455e708c0b47aa9 (patch)
tree7884dc6b40bd88c3bef6e4b354799c7044e32756
parentec06811bae7ed4fc6bd80c3154fd473028ee8e13 (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.cc6
-rw-r--r--src/lib/config.h18
-rw-r--r--src/tools/dcpomatic_player.cc24
-rw-r--r--src/wx/player_config_dialog.cc103
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;