/** @return Open subtitles for the frame at the given time, converted to images */
optional<PositionImage>
-Player::open_subtitles_for_frame (DCPTime time) const
+Player::open_subtitles_for_frame (DCPTime time, Eyes eyes) const
{
list<PositionImage> captions;
int const vfr = _film->video_frame_rate();
for (
- auto j:
+ auto burnt:
_active_texts[static_cast<int>(TextType::OPEN_SUBTITLE)].get_burnt(DCPTimePeriod(time, time + DCPTime::from_frames(1, vfr)), _always_burn_open_subtitles)
) {
/* Bitmap subtitles */
- for (auto i: j.bitmap) {
+ for (auto i: burnt.bitmap) {
if (!i.image) {
continue;
}
}
/* String subtitles (rendered to an image) */
- if (!j.string.empty()) {
- auto s = render_text(j.string, _video_container_size, time, vfr);
- copy (s.begin(), s.end(), back_inserter (captions));
+ if (!burnt.string.empty()) {
+ int stereo_offset = 0;
+ if (eyes == Eyes::LEFT) {
+ stereo_offset = -burnt.z_position;
+ } else if (eyes == Eyes::RIGHT) {
+ stereo_offset = burnt.z_position;
+ }
+ for (auto subtitle: render_text(burnt.string, _video_container_size, time, vfr)) {
+ subtitle.position.x += stereo_offset;
+ captions.push_back (subtitle);
+ }
}
}
ps.string.push_back (s);
}
+ ps.z_position = text->z_position();
_active_texts[static_cast<int>(content->type())].add_from(weak_content, ps, from);
}
auto to_do = _delay.front();
_delay.pop_front();
- do_emit_video (to_do.first, to_do.second);
+ if (_film->three_d() && to_do.first->eyes() == Eyes::BOTH) {
+ /* Duplicate BOTH to LEFT and RIGHT here so that we can to 3D
+ * subtitles over 2D content.
+ */
+ auto left = to_do.first->shallow_copy();
+ left->set_eyes (Eyes::LEFT);
+ do_emit_video (left, to_do.second);
+ auto right = to_do.first->shallow_copy();
+ right->set_eyes (Eyes::RIGHT);
+ do_emit_video (right, to_do.second);
+ } else {
+ do_emit_video (to_do.first, to_do.second);
+ }
}
}
}
- auto subtitles = open_subtitles_for_frame (time);
+ auto subtitles = open_subtitles_for_frame (time, pv->eyes());
if (subtitles) {
pv->set_text (subtitles.get ());
}
std::pair<std::shared_ptr<AudioBuffers>, dcpomatic::DCPTime> discard_audio (
std::shared_ptr<const AudioBuffers> audio, dcpomatic::DCPTime time, dcpomatic::DCPTime discard_to
) const;
- boost::optional<PositionImage> open_subtitles_for_frame (dcpomatic::DCPTime time) const;
+ boost::optional<PositionImage> open_subtitles_for_frame (dcpomatic::DCPTime time, Eyes eyes) const;
void emit_video (std::shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime time);
void do_emit_video (std::shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime time);
void emit_audio (std::shared_ptr<AudioBuffers> data, dcpomatic::DCPTime time);
/** BitmapTexts, with their rectangles transformed as specified by their content */
std::list<BitmapText> bitmap;
std::list<StringText> string;
+
+ /* XXX: this should probably be somewhere else */
+ int z_position = 0;
};
/*
- Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2022 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
*/
-#include "text_content.h"
-#include "util.h"
+#include "content.h"
#include "exceptions.h"
#include "font.h"
-#include "content.h"
+#include "text_content.h"
+#include "util.h"
#include <dcp/raw_convert.h>
#include <libcxml/cxml.h>
#include <libxml++/libxml++.h>
int const TextContentProperty::EFFECT = 508;
int const TextContentProperty::EFFECT_COLOUR = 509;
int const TextContentProperty::LINE_SPACING = 510;
-int const TextContentProperty::FADE_IN = 511;
-int const TextContentProperty::FADE_OUT = 512;
-int const TextContentProperty::OUTLINE_WIDTH = 513;
-int const TextContentProperty::TYPE = 514;
-int const TextContentProperty::DCP_TRACK = 515;
-int const TextContentProperty::LANGUAGE = 516;
-int const TextContentProperty::LANGUAGE_IS_ADDITIONAL = 517;
+int const TextContentProperty::Z_POSITION = 511;
+int const TextContentProperty::FADE_IN = 512;
+int const TextContentProperty::FADE_OUT = 513;
+int const TextContentProperty::OUTLINE_WIDTH = 514;
+int const TextContentProperty::TYPE = 515;
+int const TextContentProperty::DCP_TRACK = 516;
+int const TextContentProperty::LANGUAGE = 517;
+int const TextContentProperty::LANGUAGE_IS_ADDITIONAL = 518;
TextContent::TextContent (Content* parent, TextType type, TextType original_type)
, _x_scale (1)
, _y_scale (1)
, _line_spacing (node->optional_number_child<double>("LineSpacing").get_value_or(1))
+ , _z_position (node->optional_number_child<int>("ZPosition").get_value_or(0))
, _outline_width (node->optional_number_child<int>("OutlineWidth").get_value_or(4))
, _type (TextType::OPEN_SUBTITLE)
, _original_type (TextType::OPEN_SUBTITLE)
throw JoinError (_("Content to be joined must have the same subtitle line spacing."));
}
+ if (c[i]->only_text()->z_position() != ref->z_position()) {
+ throw JoinError (_("Content to be joined must have the same subtitle Z position."));
+ }
+
if ((c[i]->only_text()->fade_in() != ref->fade_in()) || (c[i]->only_text()->fade_out() != ref->fade_out())) {
throw JoinError (_("Content to be joined must have the same subtitle fades."));
}
_y_scale = ref->y_scale ();
_fonts = ref_fonts;
_line_spacing = ref->line_spacing ();
+ _z_position = ref->z_position ();
_fade_in = ref->fade_in ();
_fade_out = ref->fade_out ();
_outline_width = ref->outline_width ();
text->add_child("EffectBlue")->add_child_text (raw_convert<string> (_effect_colour->b));
}
text->add_child("LineSpacing")->add_child_text (raw_convert<string> (_line_spacing));
+ text->add_child("ZPosition")->add_child_text(raw_convert<string>(_z_position));
if (_fade_in) {
text->add_child("FadeIn")->add_child_text (raw_convert<string> (_fade_in->get()));
}
+ "_" + raw_convert<string> (x_offset())
+ "_" + raw_convert<string> (y_offset())
+ "_" + raw_convert<string> (line_spacing())
+ + "_" + raw_convert<string> (z_position())
+ "_" + raw_convert<string> (fade_in().get_value_or(ContentTime()).get())
+ "_" + raw_convert<string> (fade_out().get_value_or(ContentTime()).get())
+ "_" + raw_convert<string> (outline_width())
maybe_set (_line_spacing, s, TextContentProperty::LINE_SPACING);
}
+void
+TextContent::set_z_position (int z)
+{
+ maybe_set (_z_position, z, TextContentProperty::Z_POSITION);
+}
+
+
void
TextContent::set_fade_in (ContentTime t)
{
unset_effect_colour ();
}
set_line_spacing (c->_line_spacing);
+ set_z_position (c->_z_position);
if (c->_fade_in) {
set_fade_in (*c->_fade_in);
}
static int const EFFECT;
static int const EFFECT_COLOUR;
static int const LINE_SPACING;
+ static int const Z_POSITION;
static int const FADE_IN;
static int const FADE_OUT;
static int const OUTLINE_WIDTH;
void set_effect_colour (dcp::Colour);
void unset_effect_colour ();
void set_line_spacing (double s);
+ void set_z_position (int z);
void set_fade_in (dcpomatic::ContentTime);
void unset_fade_in ();
void set_fade_out (dcpomatic::ContentTime);
return _line_spacing;
}
+ int z_position () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _z_position;
+ }
+
boost::optional<dcpomatic::ContentTime> fade_in () const {
boost::mutex::scoped_lock lm (_mutex);
return _fade_in;
boost::optional<dcp::Colour> _effect_colour;
/** scaling factor for line spacing; 1 is "standard", < 1 is closer together, > 1 is further apart */
double _line_spacing;
+ int _z_position = 0;
boost::optional<dcpomatic::ContentTime> _fade_in;
boost::optional<dcpomatic::ContentTime> _fade_out;
int _outline_width;
qi.frame = frame - _reels[qi.reel].start ();
if (film()->three_d() && eyes == Eyes::BOTH) {
- /* 2D material in a 3D DCP; fake the 3D */
+ /* 2D material in a 3D DCP; fake the 3D.
+ * XXX: this shouldn't really be necessary any more but something is still sending BOTH
+ */
qi.eyes = Eyes::LEFT;
_queue.push_back (qi);
++_queued_full_in_memory;
_video_view->set_video_frame_rate (_film->video_frame_rate());
} else if (p == Film::Property::THREE_D) {
_video_view->set_three_d (_film->three_d());
+ slow_refresh ();
} else if (p == Film::Property::CONTENT) {
_closed_captions_dialog->update_tracks (_film);
}
_line_spacing = new SpinCtrl (this, DCPOMATIC_SPIN_CTRL_WIDTH);
_line_spacing_pc_label = new StaticText (this, _("%"));
+ _z_position_label = create_label (this, _("Z position"), true);
+ _z_position = new SpinCtrl (this, DCPOMATIC_SPIN_CTRL_WIDTH);
+
_stream_label = create_label (this, _("Stream"), true);
_stream = new wxChoice (this, wxID_ANY);
_x_scale->Bind (wxEVT_SPINCTRL, boost::bind (&TextPanel::x_scale_changed, this));
_y_scale->Bind (wxEVT_SPINCTRL, boost::bind (&TextPanel::y_scale_changed, this));
_line_spacing->Bind (wxEVT_SPINCTRL, boost::bind (&TextPanel::line_spacing_changed, this));
+ _z_position->Bind (wxEVT_SPINCTRL, boost::bind (&TextPanel::z_position_changed, this));
_stream->Bind (wxEVT_CHOICE, boost::bind (&TextPanel::stream_changed, this));
_text_view_button->Bind (wxEVT_BUTTON, boost::bind (&TextPanel::text_view_clicked, this));
_fonts_dialog_button->Bind (wxEVT_BUTTON, boost::bind (&TextPanel::fonts_dialog_clicked, this));
++r;
}
+ add_label_to_sizer (_grid, _z_position_label, true, wxGBPosition(r, 0));
+ _grid->Add (_z_position, wxGBPosition(r, 1));
+ ++r;
+
_ccap_track_or_language_row = r;
++r;
} else if (property == TextContentProperty::LINE_SPACING) {
checked_set (_line_spacing, text ? lrint (text->line_spacing() * 100) : 100);
clear_outline_subtitles ();
+ } else if (property == TextContentProperty::Z_POSITION) {
+ checked_set (_z_position, text ? text->z_position() : 0);
} else if (property == TextContentProperty::DCP_TRACK) {
if (_dcp_track) {
update_dcp_track_selection ();
_x_scale->Enable (!reference && any_subs > 0 && use && type == TextType::OPEN_SUBTITLE);
_y_scale->Enable (!reference && any_subs > 0 && use && type == TextType::OPEN_SUBTITLE);
_line_spacing->Enable (!reference && use && type == TextType::OPEN_SUBTITLE && dcp_subs < any_subs);
+ _z_position->Enable (!reference && use && type == TextType::OPEN_SUBTITLE);
_stream->Enable (!reference && ffmpeg_subs == 1);
/* Ideally we would check here to see if the FFmpeg content has "string" subs (i.e. not bitmaps) */
_text_view_button->Enable (!reference && any_subs > 0 && ffmpeg_subs == 0);
}
+void
+TextPanel::z_position_changed ()
+{
+ for (auto i: _parent->selected_text()) {
+ i->text_of_original_type(_original_type)->set_z_position (_z_position->GetValue());
+ }
+}
+
+
void
TextPanel::content_selection_changed ()
{
film_content_changed (TextContentProperty::X_SCALE);
film_content_changed (TextContentProperty::Y_SCALE);
film_content_changed (TextContentProperty::LINE_SPACING);
+ film_content_changed (TextContentProperty::Z_POSITION);
film_content_changed (TextContentProperty::FONTS);
film_content_changed (TextContentProperty::TYPE);
film_content_changed (TextContentProperty::DCP_TRACK);
void x_scale_changed ();
void y_scale_changed ();
void line_spacing_changed ();
+ void z_position_changed ();
void dcp_track_changed ();
void stream_changed ();
void text_view_clicked ();
wxStaticText* _line_spacing_label;
wxStaticText* _line_spacing_pc_label;
SpinCtrl* _line_spacing;
+ wxStaticText* _z_position_label;
+ SpinCtrl* _z_position;
wxStaticText* _dcp_track_label = nullptr;
wxChoice* _dcp_track = nullptr;
wxStaticText* _stream_label;