2016-08-16 c.hetherington <cth@carlh.net>
+ * Basic support for fading subtitles in and out (#923).
+
* Fix error on seeking through imported mulit-reel DCPs.
* Simple information on mouse position in the video waveform (part of #932).
/* Text subtitles (rendered to an image) */
if (!ps.text.empty ()) {
- list<PositionImage> s = render_subtitles (ps.text, ps.fonts, _video_container_size);
+ list<PositionImage> s = render_subtitles (ps.text, ps.fonts, _video_container_size, time);
copy (s.begin (), s.end (), back_inserter (sub_images));
}
return out;
}
-/** @param subtitles A list of subtitles that are all on the same line */
+/** @param subtitles A list of subtitles that are all on the same line,
+ * at the same time and with the same fade in/out.
+ */
static PositionImage
-render_line (list<dcp::SubtitleString> subtitles, list<shared_ptr<Font> > fonts, dcp::Size target)
+render_line (list<dcp::SubtitleString> subtitles, list<shared_ptr<Font> > fonts, dcp::Size target, DCPTime time)
{
/* XXX: this method can only handle italic / bold changes mid-line,
nothing else yet.
}
}
- FcPattern* pattern = FcPatternBuild (0, FC_FILE, FcTypeString, font_files.get(FontFiles::NORMAL).get().string().c_str(), static_cast<char *> (0));
+ FcPattern* pattern = FcPatternBuild (
+ 0, FC_FILE, FcTypeString, font_files.get(FontFiles::NORMAL).get().string().c_str(), static_cast<char *> (0)
+ );
FcObjectSet* object_set = FcObjectSetBuild (FC_FAMILY, FC_STYLE, FC_LANG, FC_FILE, static_cast<char *> (0));
FcFontSet* font_set = FcFontList (fc_config, pattern, object_set);
if (font_set) {
layout->set_markup (marked_up (subtitles));
/* Compute fade factor */
- /* XXX */
float fade_factor = 1;
+ DCPTime const fade_in_start = DCPTime::from_seconds (subtitles.front().in().as_seconds ());
+ DCPTime const fade_in_end = fade_in_start + DCPTime::from_seconds (subtitles.front().fade_up_time().as_seconds ());
+ DCPTime const fade_out_end = DCPTime::from_seconds (subtitles.front().out().as_seconds ());
+ DCPTime const fade_out_start = fade_out_end - DCPTime::from_seconds (subtitles.front().fade_down_time().as_seconds ());
+ if (fade_in_start <= time && time <= fade_in_end && fade_in_start != fade_in_end) {
+ fade_factor = DCPTime(time - fade_in_start).seconds() / DCPTime(fade_in_end - fade_in_start).seconds();
+ } else if (fade_out_start <= time && time <= fade_out_end && fade_out_start != fade_out_end) {
+ fade_factor = 1 - DCPTime(time - fade_out_start).seconds() / DCPTime(fade_out_end - fade_out_start).seconds();
+ }
+
context->scale (xscale, yscale);
layout->update_from_cairo_context (context);
return PositionImage (image, Position<int> (max (0, x), max (0, y)));
}
+/** @param time Time of the frame that these subtitles are going on */
list<PositionImage>
-render_subtitles (list<dcp::SubtitleString> subtitles, list<shared_ptr<Font> > fonts, dcp::Size target)
+render_subtitles (list<dcp::SubtitleString> subtitles, list<shared_ptr<Font> > fonts, dcp::Size target, DCPTime time)
{
list<dcp::SubtitleString> pending;
list<PositionImage> images;
BOOST_FOREACH (dcp::SubtitleString const & i, subtitles) {
if (!pending.empty() && fabs (i.v_position() - pending.back().v_position()) > 1e-4) {
- images.push_back (render_line (pending, fonts, target));
+ images.push_back (render_line (pending, fonts, target, time));
pending.clear ();
}
pending.push_back (i);
}
if (!pending.empty ()) {
- images.push_back (render_line (pending, fonts, target));
+ images.push_back (render_line (pending, fonts, target, time));
}
return images;
*/
#include "position_image.h"
+#include "dcpomatic_time.h"
#include <dcp/subtitle_string.h>
#include <dcp/util.h>
class Font;
std::string marked_up (std::list<dcp::SubtitleString> subtitles);
-std::list<PositionImage> render_subtitles (std::list<dcp::SubtitleString>, std::list<boost::shared_ptr<Font> > fonts, dcp::Size);
+std::list<PositionImage> render_subtitles (
+ std::list<dcp::SubtitleString>, std::list<boost::shared_ptr<Font> > fonts, dcp::Size, DCPTime
+ );
int const SubtitleContentProperty::SHADOW = 510;
int const SubtitleContentProperty::EFFECT_COLOUR = 511;
int const SubtitleContentProperty::LINE_SPACING = 512;
+int const SubtitleContentProperty::FADE_IN = 513;
+int const SubtitleContentProperty::FADE_OUT = 514;
SubtitleContent::SubtitleContent (Content* parent)
: ContentPart (parent)
, _outline (node->optional_bool_child("Outline").get_value_or(false))
, _shadow (node->optional_bool_child("Shadow").get_value_or(false))
, _line_spacing (node->optional_number_child<double>("LineSpacing").get_value_or (1))
+ , _fade_in (node->optional_number_child<Frame>("SubtitleFadeIn").get_value_or (0))
+ , _fade_out (node->optional_number_child<Frame>("SubtitleFadeOut").get_value_or (0))
{
if (version >= 32) {
_use = node->bool_child ("UseSubtitles");
throw JoinError (_("Content to be joined must have the same subtitle line spacing."));
}
+ if ((c[i]->subtitle->fade_in() != ref->fade_in()) || (c[i]->subtitle->fade_out() != ref->fade_out())) {
+ throw JoinError (_("Content to be joined must have the same subtitle fades."));
+ }
+
list<shared_ptr<Font> > fonts = c[i]->subtitle->fonts ();
if (fonts.size() != ref_fonts.size()) {
throw JoinError (_("Content to be joined must use the same fonts."));
_language = ref->language ();
_fonts = ref_fonts;
_line_spacing = ref->line_spacing ();
+ _fade_in = ref->fade_in ();
+ _fade_out = ref->fade_out ();
connect_to_fonts ();
}
root->add_child("EffectGreen")->add_child_text (raw_convert<string> (_effect_colour.g));
root->add_child("EffectBlue")->add_child_text (raw_convert<string> (_effect_colour.b));
root->add_child("LineSpacing")->add_child_text (raw_convert<string> (_line_spacing));
+ root->add_child("SubtitleFadeIn")->add_child_text (raw_convert<string> (_fade_in.get()));
+ root->add_child("SubtitleFadeOut")->add_child_text (raw_convert<string> (_fade_out.get()));
for (list<shared_ptr<Font> >::const_iterator i = _fonts.begin(); i != _fonts.end(); ++i) {
(*i)->as_xml (root->add_child("Font"));
+ "_" + raw_convert<string> (y_scale())
+ "_" + raw_convert<string> (x_offset())
+ "_" + raw_convert<string> (y_offset())
- + "_" + raw_convert<string> (line_spacing());
+ + "_" + raw_convert<string> (line_spacing())
+ + "_" + raw_convert<string> (fade_in().get())
+ + "_" + raw_convert<string> (fade_out().get());
/* XXX: I suppose really _fonts shouldn't be in here, since not all
types of subtitle content involve fonts.
{
maybe_set (_line_spacing, s, SubtitleContentProperty::LINE_SPACING);
}
+
+void
+SubtitleContent::set_fade_in (ContentTime t)
+{
+ maybe_set (_fade_in, t, SubtitleContentProperty::FADE_IN);
+}
+
+void
+SubtitleContent::set_fade_out (ContentTime t)
+{
+ maybe_set (_fade_out, t, SubtitleContentProperty::FADE_OUT);
+}
static int const SHADOW;
static int const EFFECT_COLOUR;
static int const LINE_SPACING;
+ static int const FADE_IN;
+ static int const FADE_OUT;
};
/** @class SubtitleContent
void set_x_scale (double);
void set_y_scale (double);
void set_language (std::string language);
+ void set_colour (dcp::Colour);
+ void set_outline (bool);
+ void set_shadow (bool);
+ void set_effect_colour (dcp::Colour);
+ void set_line_spacing (double s);
+ void set_fade_in (ContentTime);
+ void set_fade_out (ContentTime);
bool use () const {
boost::mutex::scoped_lock lm (_mutex);
return _language;
}
- void set_colour (dcp::Colour);
-
dcp::Colour colour () const {
boost::mutex::scoped_lock lm (_mutex);
return _colour;
}
- void set_outline (bool);
-
bool outline () const {
boost::mutex::scoped_lock lm (_mutex);
return _outline;
}
- void set_shadow (bool);
-
bool shadow () const {
boost::mutex::scoped_lock lm (_mutex);
return _shadow;
}
- void set_effect_colour (dcp::Colour);
-
dcp::Colour effect_colour () const {
boost::mutex::scoped_lock lm (_mutex);
return _effect_colour;
}
- void set_line_spacing (double s);
-
double line_spacing () const {
boost::mutex::scoped_lock lm (_mutex);
return _line_spacing;
}
+ ContentTime fade_in () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _fade_in;
+ }
+
+ ContentTime fade_out () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _fade_out;
+ }
+
static boost::shared_ptr<SubtitleContent> from_xml (Content* parent, cxml::ConstNodePtr, int version);
protected:
void font_changed ();
void connect_to_fonts ();
+ std::list<boost::signals2::connection> _font_connections;
+
bool _use;
bool _burn;
/** x offset for placing subtitles, as a proportion of the container width;
bool _outline;
bool _shadow;
dcp::Colour _effect_colour;
- std::list<boost::signals2::connection> _font_connections;
/** scaling factor for line spacing; 1 is "standard", < 1 is closer together, > 1 is further apart */
double _line_spacing;
+ ContentTime _fade_in;
+ ContentTime _fade_out;
};
#endif
j.text,
effect,
content()->effect_colour(),
- dcp::Time (0, 1000),
- dcp::Time (0, 1000)
+ dcp::Time (content()->fade_in().seconds(), 1000),
+ dcp::Time (content()->fade_out().seconds(), 1000)
)
);
}
}
if (text) {
- TextSubtitleAppearanceDialog* d = new TextSubtitleAppearanceDialog (this, c.front()->subtitle);
+ TextSubtitleAppearanceDialog* d = new TextSubtitleAppearanceDialog (this, c.front());
if (d->ShowModal () == wxID_OK) {
d->apply ();
}
using boost::shared_ptr;
-TextSubtitleAppearanceDialog::TextSubtitleAppearanceDialog (wxWindow* parent, shared_ptr<SubtitleContent> content)
+TextSubtitleAppearanceDialog::TextSubtitleAppearanceDialog (wxWindow* parent, shared_ptr<Content> content)
: TableDialog (parent, _("Subtitle appearance"), 2, 1, true)
, _content (content)
{
_effect_colour = new wxColourPickerCtrl (this, wxID_ANY);
add (_effect_colour);
+ add (_("Fade in time"), true);
+ _fade_in = new Timecode<ContentTime> (this);
+ add (_fade_in);
+
+ add (_("Fade out time"), true);
+ _fade_out = new Timecode<ContentTime> (this);
+ add (_fade_out);
+
layout ();
- _colour->SetColour (wxColour (_content->colour().r, _content->colour().g, _content->colour().b));
- _outline->SetValue (_content->outline ());
- _shadow->SetValue (_content->shadow ());
+ _colour->SetColour (wxColour (_content->subtitle->colour().r, _content->subtitle->colour().g, _content->subtitle->colour().b));
+ _outline->SetValue (_content->subtitle->outline ());
+ _shadow->SetValue (_content->subtitle->shadow ());
_effect_colour->SetColour (
- wxColour (_content->effect_colour().r, _content->effect_colour().g, _content->effect_colour().b)
+ wxColour (_content->subtitle->effect_colour().r, _content->subtitle->effect_colour().g, _content->subtitle->effect_colour().b)
);
+ _fade_in->set (_content->subtitle->fade_in(), _content->active_video_frame_rate ());
+ _fade_out->set (_content->subtitle->fade_out(), _content->active_video_frame_rate ());
}
void
TextSubtitleAppearanceDialog::apply ()
{
wxColour const c = _colour->GetColour ();
- _content->set_colour (dcp::Colour (c.Red(), c.Green(), c.Blue()));
- _content->set_outline (_outline->GetValue ());
- _content->set_shadow (_shadow->GetValue ());
+ _content->subtitle->set_colour (dcp::Colour (c.Red(), c.Green(), c.Blue()));
+ _content->subtitle->set_outline (_outline->GetValue ());
+ _content->subtitle->set_shadow (_shadow->GetValue ());
wxColour const ec = _effect_colour->GetColour ();
- _content->set_effect_colour (dcp::Colour (ec.Red(), ec.Green(), ec.Blue()));
+ _content->subtitle->set_effect_colour (dcp::Colour (ec.Red(), ec.Green(), ec.Blue()));
+ _content->subtitle->set_fade_in (_fade_in->get (_content->active_video_frame_rate ()));
+ _content->subtitle->set_fade_out (_fade_out->get (_content->active_video_frame_rate ()));
}
*/
#include "table_dialog.h"
+#include "timecode.h"
#include <boost/shared_ptr.hpp>
class wxRadioButton;
class wxColourPickerCtrl;
-class SubtitleContent;
+class Content;
class TextSubtitleAppearanceDialog : public TableDialog
{
public:
- TextSubtitleAppearanceDialog (wxWindow* parent, boost::shared_ptr<SubtitleContent> content);
+ TextSubtitleAppearanceDialog (wxWindow* parent, boost::shared_ptr<Content> content);
void apply ();
wxRadioButton* _outline;
wxRadioButton* _shadow;
wxColourPickerCtrl* _effect_colour;
+ Timecode<ContentTime>* _fade_in;
+ Timecode<ContentTime>* _fade_out;
- boost::shared_ptr<SubtitleContent> _content;
+ boost::shared_ptr<Content> _content;
};