dcpomatic::Rect<double> rectangle;
};
+extern bool operator== (BitmapText const & a, BitmapText const & b);
+
#endif
PresetColourConversion::all().front().conversion,
VIDEO_RANGE_FULL,
boost::weak_ptr<Content>(),
- boost::optional<Frame>()
+ boost::optional<DCPTime>(),
+ _film->video_frame_rate()
)
);
}
return done;
}
-/** @return Open subtitles for the frame at the given time, converted to images */
-optional<PositionImage>
-Player::open_subtitles_for_frame (DCPTime time) const
-{
- list<PositionImage> captions;
- int const vfr = _film->video_frame_rate();
-
- BOOST_FOREACH (
- PlayerText j,
- _active_texts[TEXT_OPEN_SUBTITLE].get_burnt(DCPTimePeriod(time, time + DCPTime::from_frames(1, vfr)), _always_burn_open_subtitles)
- ) {
-
- /* Bitmap subtitles */
- BOOST_FOREACH (BitmapText i, j.bitmap) {
- if (!i.image) {
- continue;
- }
-
- /* i.image will already have been scaled to fit _video_container_size */
- dcp::Size scaled_size (i.rectangle.width * _video_container_size.width, i.rectangle.height * _video_container_size.height);
-
- captions.push_back (
- PositionImage (
- i.image,
- Position<int> (
- lrint (_video_container_size.width * i.rectangle.x),
- lrint (_video_container_size.height * i.rectangle.y)
- )
- )
- );
- }
-
- /* String subtitles (rendered to an image) */
- if (!j.string.empty ()) {
- list<PositionImage> s = render_text (j.string, j.fonts, _video_container_size, time, vfr);
- copy (s.begin(), s.end(), back_inserter (captions));
- }
- }
-
- if (captions.empty ()) {
- return optional<PositionImage> ();
- }
-
- return merge (captions);
-}
-
void
Player::video (weak_ptr<Piece> wp, ContentVideo video)
{
piece->content->video->colour_conversion(),
piece->content->video->range(),
piece->content,
- video.frame
+ time,
+ _film->video_frame_rate()
)
);
subtitle.sub.rectangle.width *= text->x_scale ();
subtitle.sub.rectangle.height *= text->y_scale ();
- PlayerText ps;
shared_ptr<Image> image = subtitle.sub.image;
/* We will scale the subtitle up to fit _video_container_size */
dcp::Size scaled_size (subtitle.sub.rectangle.width * _video_container_size.width, subtitle.sub.rectangle.height * _video_container_size.height);
+ PlayerText ps;
+ ps.content = text;
ps.bitmap.push_back (BitmapText(image->scale(scaled_size, dcp::YUV_TO_RGB_REC601, image->pixel_format(), true, _fast), subtitle.sub.rectangle));
DCPTime from (content_time_to_dcp (piece, subtitle.from()));
}
PlayerText ps;
+ ps.content = text;
DCPTime const from (content_time_to_dcp (piece, subtitle.from()));
if (from > piece->content->end(_film)) {
}
BOOST_FOREACH (dcp::SubtitleString s, subtitle.subs) {
- s.set_h_position (s.h_position() + text->x_offset ());
- s.set_v_position (s.v_position() + text->y_offset ());
+// s.set_h_position (s.h_position() + text->x_offset ());
+// s.set_v_position (s.v_position() + text->y_offset ());
float const xs = text->x_scale();
float const ys = text->y_scale();
float size = s.size();
}
}
- optional<PositionImage> subtitles = open_subtitles_for_frame (time);
- if (subtitles) {
- pv->set_text (subtitles.get ());
- }
+ int const vfr = _film->video_frame_rate();
+ list<PlayerText> subtitles = _active_texts[TEXT_OPEN_SUBTITLE].get_burnt(DCPTimePeriod(time, time + DCPTime::from_frames(1, vfr)), _always_burn_open_subtitles);
+ pv->set_text (subtitles);
Video (pv, time);
}
std::pair<boost::shared_ptr<AudioBuffers>, dcpomatic::DCPTime> discard_audio (
boost::shared_ptr<const AudioBuffers> audio, dcpomatic::DCPTime time, dcpomatic::DCPTime discard_to
) const;
- boost::optional<PositionImage> open_subtitles_for_frame (dcpomatic::DCPTime time) const;
void emit_video (boost::shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime time);
void do_emit_video (boost::shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime time);
void emit_audio (boost::shared_ptr<AudioBuffers> data, dcpomatic::DCPTime time);
}
}
}
+
+bool
+operator== (PlayerText const & a, PlayerText const & b)
+{
+ if (a.fonts.size() != b.fonts.size()) {
+ return false;
+ }
+
+ {
+ list<shared_ptr<Font> >::const_iterator i = a.fonts.begin();
+ list<shared_ptr<Font> >::const_iterator j = b.fonts.begin();
+ while (i != a.fonts.end()) {
+ if (**i != **j) {
+ return false;
+ }
+ ++i;
+ ++j;
+ }
+ }
+
+ return a.bitmap == b.bitmap && a.string == b.string;
+}
+
+bool
+operator!= (PlayerText const & a, PlayerText const & b)
+{
+ return !(a == b);
+}
*/
-#ifndef DCPOMATIC_PLAYER_CAPTION_H
-#define DCPOMATIC_PLAYER_CAPTION_H
+#ifndef DCPOMATIC_PLAYER_TEXT_H
+#define DCPOMATIC_PLAYER_TEXT_H
#include "bitmap_text.h"
#include "dcpomatic_time.h"
#include "string_text.h"
+#include <boost/weak_ptr.hpp>
namespace dcpomatic {
class Font;
}
+class TextContent;
+
/** A set of text (subtitle/CCAP) which span the same time period */
class PlayerText
{
void add_fonts (std::list<boost::shared_ptr<dcpomatic::Font> > fonts_);
std::list<boost::shared_ptr<dcpomatic::Font> > fonts;
+ boost::weak_ptr<const TextContent> content;
/** BitmapTexts, with their rectangles transformed as specified by their content */
std::list<BitmapText> bitmap;
std::list<StringText> string;
};
+extern bool operator== (PlayerText const & a, PlayerText const & b);
+extern bool operator!= (PlayerText const & a, PlayerText const & b);
+
#endif
/*
- Copyright (C) 2013-2018 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2013-2019 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
#include "image_proxy.h"
#include "j2k_image_proxy.h"
#include "film.h"
+#include "render_text.h"
+#include "text_content.h"
#include <dcp/raw_convert.h>
extern "C" {
#include <libavutil/pixfmt.h>
using std::string;
using std::cout;
using std::pair;
+using std::list;
using boost::shared_ptr;
using boost::weak_ptr;
using boost::dynamic_pointer_cast;
optional<ColourConversion> colour_conversion,
VideoRange video_range,
weak_ptr<Content> content,
- optional<Frame> video_frame
+ optional<dcpomatic::DCPTime> time,
+ int video_frame_rate
)
: _in (in)
, _crop (crop)
, _colour_conversion (colour_conversion)
, _video_range (video_range)
, _content (content)
- , _video_frame (video_frame)
+ , _time (time)
+ , _video_frame_rate (video_frame_rate)
+ , _image_dirty (false)
{
}
PlayerVideo::PlayerVideo (shared_ptr<cxml::Node> node, shared_ptr<Socket> socket)
+ : _image_dirty (false)
{
_crop = Crop (node);
_fade = node->optional_number_child<double> ("Fade");
image->read_from_socket (socket);
+ /* XXX_b
_text = PositionImage (image, Position<int> (node->number_child<int> ("SubtitleX"), node->number_child<int> ("SubtitleY")));
+ */
}
}
void
-PlayerVideo::set_text (PositionImage image)
+PlayerVideo::set_text (list<PlayerText> text)
{
- _text = image;
+ _text = text;
}
shared_ptr<Image>
/* XXX: this assumes that image() and prepare() are only ever called with the same parameters (except crop, inter size, out size, fade) */
boost::mutex::scoped_lock lm (_mutex);
- if (!_image || _crop != _image_crop || _inter_size != _image_inter_size || _out_size != _image_out_size || _fade != _image_fade) {
+ if (!_image || _image_dirty) {
make_image (pixel_format, aligned, fast);
}
return _image;
total_crop, _inter_size, _out_size, yuv_to_rgb, _video_range, pixel_format (im->pixel_format()), aligned, fast
);
- if (_text) {
- _image->alpha_blend (Image::ensure_aligned (_text->image), _text->position);
+ list<PositionImage> subtitles;
+
+ BOOST_FOREACH (PlayerText i, _text) {
+
+ /* Bitmap subtitles */
+ BOOST_FOREACH (BitmapText j, i.bitmap) {
+ if (!j.image) {
+ continue;
+ }
+
+ /* j.image will already have been scaled to fit _out_size */
+ dcp::Size scaled_size (j.rectangle.width * _out_size.width, j.rectangle.height * _out_size.height);
+
+ subtitles.push_back (
+ PositionImage (
+ j.image,
+ Position<int> (
+ lrint (_out_size.width * j.rectangle.x),
+ lrint (_out_size.height * j.rectangle.y)
+ )
+ )
+ );
+ }
+
+ /* String subtitles (rendered to an image) */
+ if (!i.string.empty ()) {
+ DCPOMATIC_ASSERT (_time);
+ shared_ptr<const TextContent> content = i.content.lock ();
+ DCPOMATIC_ASSERT (content);
+ list<PositionImage> s = render_text (
+ i.string, i.fonts, _out_size, *_time, _video_frame_rate, content->x_offset(), content->y_offset()
+ );
+ copy (s.begin(), s.end(), back_inserter (subtitles));
+ }
+ }
+
+ if (!subtitles.empty()) {
+ PositionImage pi = merge (subtitles);
+ _image->alpha_blend (Image::ensure_aligned(pi.image), pi.position);
}
if (_fade) {
_image->fade (_fade.get ());
}
+
+ _image_dirty = false;
}
void
if (_colour_conversion) {
_colour_conversion.get().as_xml (node);
}
+ /* XXX_b
if (_text) {
node->add_child ("SubtitleWidth")->add_child_text (raw_convert<string> (_text->image->size().width));
node->add_child ("SubtitleHeight")->add_child_text (raw_convert<string> (_text->image->size().height));
node->add_child ("SubtitleX")->add_child_text (raw_convert<string> (_text->position.x));
node->add_child ("SubtitleY")->add_child_text (raw_convert<string> (_text->position.y));
}
+ */
}
void
PlayerVideo::send_binary (shared_ptr<Socket> socket) const
{
_in->send_binary (socket);
+ /* XXX_b
if (_text) {
_text->image->write_to_socket (socket);
}
+ */
}
bool
return false;
}
- return _crop == Crop () && _out_size == j2k->size() && !_text && !_fade && !_colour_conversion;
+ return _crop == Crop() && _out_size == j2k->size() && _text.empty() && !_fade && !_colour_conversion;
}
Data
return false;
}
- if ((!_text && other->_text) || (_text && !other->_text)) {
- /* One has a text and the other doesn't */
+ if (_text.size() != other->_text.size()) {
+ /* Different text counts */
return false;
}
- if (_text && other->_text && !_text->same (other->_text.get ())) {
- /* They both have texts but they are different */
- return false;
+ list<PlayerText>::const_iterator i = _text.begin();
+ list<PlayerText>::const_iterator j = other->_text.begin();
+ while (i != _text.end()) {
+ if (*i != *j) {
+ /* Same number of texts but one differs */
+ return false;
+ }
+ ++i;
+ ++j;
}
- /* Now neither has subtitles */
-
return _in->same (other->_in);
}
_colour_conversion,
_video_range,
_content,
- _video_frame
+ _time,
+ _video_frame_rate
)
);
}
PlayerVideo::reset_metadata (shared_ptr<const Film> film, dcp::Size video_container_size, dcp::Size film_frame_size)
{
shared_ptr<Content> content = _content.lock();
- if (!content || !_video_frame) {
+ if (!content || !_time) {
return false;
}
_crop = content->video->crop();
- _fade = content->video->fade(film, _video_frame.get());
+ _fade = content->video->fade(film, _time->frames_round(_video_frame_rate));
_inter_size = content->video->scale().size(content->video, video_container_size, film_frame_size);
_out_size = video_container_size;
_colour_conversion = content->video->colour_conversion();
_video_range = content->video->range();
+ /* XXX_b _text looks into content directly itself, as maybe the other parameters should; we could just
+ have a pointer to the content and get stuff from there.
+ */
+
+ /* XXX_b thread safety of _image_dirty? */
+ _image_dirty = true;
+
return true;
}
#include "dcpomatic_time.h"
#include "colour_conversion.h"
#include "position_image.h"
+#include "player_text.h"
extern "C" {
#include <libavutil/pixfmt.h>
}
boost::optional<ColourConversion>,
VideoRange video_range,
boost::weak_ptr<Content>,
- boost::optional<Frame>
+ boost::optional<dcpomatic::DCPTime>,
+ int video_frame_rate
);
PlayerVideo (boost::shared_ptr<cxml::Node>, boost::shared_ptr<Socket>);
boost::shared_ptr<PlayerVideo> shallow_copy () const;
- void set_text (PositionImage);
+ void set_text (std::list<PlayerText>);
void prepare (boost::function<AVPixelFormat (AVPixelFormat)> pixel_format, bool aligned, bool fast);
boost::shared_ptr<Image> image (boost::function<AVPixelFormat (AVPixelFormat)> pixel_format, bool aligned, bool fast) const;
Part _part;
boost::optional<ColourConversion> _colour_conversion;
VideoRange _video_range;
- boost::optional<PositionImage> _text;
+ std::list<PlayerText> _text;
/** Content that we came from. This is so that reset_metadata() can work, and also
* for variant:swaroop's non-skippable ads.
*/
boost::weak_ptr<Content> _content;
- /** Video frame that we came from. Again, this is for reset_metadata() */
- boost::optional<Frame> _video_frame;
+ /** Time that we came from. Again, this is for reset_metadata() */
+ boost::optional<dcpomatic::DCPTime> _time;
+ int _video_frame_rate;
mutable boost::mutex _mutex;
mutable boost::shared_ptr<Image> _image;
mutable dcp::Size _image_out_size;
/** _fade that was used to make _image */
mutable boost::optional<double> _image_fade;
+ mutable bool _image_dirty;
};
#endif
#include "position.h"
#include <boost/optional.hpp>
#include <algorithm>
+#include <cmath>
/* Put this inside a namespace as Apple put a Rect in the global namespace */
}
};
+extern bool operator== (Rect<double> const & a, Rect<double> const & b);
+
}
#endif
* at the same time and with the same fade in/out.
*/
static PositionImage
-render_line (list<StringText> subtitles, list<shared_ptr<Font> > fonts, dcp::Size target, DCPTime time, int frame_rate)
+render_line (list<StringText> subtitles, list<shared_ptr<Font> > fonts, dcp::Size target, DCPTime time, int frame_rate, double x_offset, double y_offset)
{
/* XXX: this method can only handle italic / bold changes mid-line,
nothing else yet.
/* Shuffle the subtitle over very slightly if it has a border so that the left-hand
side of the first character's border is not cut off.
*/
- int const x_offset = subtitles.front().effect() == dcp::BORDER ? (target.width / 600.0) : 0;
+ int const xoff = subtitles.front().effect() == dcp::BORDER ? (target.width / 600.0) : 0;
/* Move down a bit so that accents on capital letters can be seen */
- int const y_offset = target.height / 100.0;
+ int const yoff = target.height / 100.0;
if (subtitles.front().effect() == dcp::SHADOW) {
/* Drop-shadow effect */
set_source_rgba (context, subtitles.front().effect_colour(), fade_factor);
- context->move_to (x_offset + 4, y_offset + 4);
+ context->move_to (xoff + 4, yoff + 4);
layout->add_to_cairo_context (context);
context->fill ();
}
set_source_rgba (context, subtitles.front().effect_colour(), fade_factor);
context->set_line_width (subtitles.front().outline_width * target.width / 2048.0);
context->set_line_join (Cairo::LINE_JOIN_ROUND);
- context->move_to (x_offset, y_offset);
+ context->move_to (xoff, yoff);
layout->add_to_cairo_context (context);
context->stroke ();
}
layout_width *= xscale;
layout_height *= yscale;
+ double const h_pos = subtitles.front().h_position() + x_offset;
+ double const v_pos = subtitles.front().v_position() + y_offset;
+
int x = 0;
- switch (subtitles.front().h_align ()) {
+ switch (subtitles.front().h_align()) {
case dcp::HALIGN_LEFT:
- /* h_position is distance between left of frame and left of subtitle */
- x = subtitles.front().h_position() * target.width;
+ /* h_pos is distance between left of frame and left of subtitle */
+ x = h_pos * target.width;
break;
case dcp::HALIGN_CENTER:
- /* h_position is distance between centre of frame and centre of subtitle */
- x = (0.5 + subtitles.front().h_position()) * target.width - layout_width / 2;
+ /* h_pos is distance between centre of frame and centre of subtitle */
+ x = (0.5 + h_pos) * target.width - layout_width / 2;
break;
case dcp::HALIGN_RIGHT:
- /* h_position is distance between right of frame and right of subtitle */
- x = (1.0 - subtitles.front().h_position()) * target.width - layout_width;
+ /* h_pos is distance between right of frame and right of subtitle */
+ x = (1.0 - h_pos) * target.width - layout_width;
break;
}
int y = 0;
- switch (subtitles.front().v_align ()) {
+ switch (subtitles.front().v_align()) {
case dcp::VALIGN_TOP:
- /* SMPTE says that v_position is the distance between top
+ /* SMPTE says that v_pos is the distance between top
of frame and top of subtitle, but this doesn't always seem to be
the case in practice; Gunnar Ásgeirsson's Dolby server appears
to put VALIGN_TOP subs with v_position as the distance between top
of frame and bottom of subtitle.
*/
- y = subtitles.front().v_position() * target.height - layout_height;
+ y = v_pos * target.height - layout_height;
break;
case dcp::VALIGN_CENTER:
/* v_position is distance between centre of frame and centre of subtitle */
- y = (0.5 + subtitles.front().v_position()) * target.height - layout_height / 2;
+ y = (0.5 + v_pos) * target.height - layout_height / 2;
break;
case dcp::VALIGN_BOTTOM:
/* v_position is distance between bottom of frame and bottom of subtitle */
- y = (1.0 - subtitles.front().v_position()) * target.height - layout_height;
+ y = (1.0 - v_pos) * target.height - layout_height;
break;
}
- return PositionImage (image, Position<int> (max (0, x), max (0, y)));
+ return PositionImage (image, Position<int>(max (0, x), max(0, y)));
}
/** @param time Time of the frame that these subtitles are going on.
* @param frame_rate DCP frame rate.
*/
list<PositionImage>
-render_text (list<StringText> subtitles, list<shared_ptr<Font> > fonts, dcp::Size target, DCPTime time, int frame_rate)
+render_text (list<StringText> subtitles, list<shared_ptr<Font> > fonts, dcp::Size target, DCPTime time, int frame_rate, double x_offset, double y_offset)
{
list<StringText> pending;
list<PositionImage> images;
BOOST_FOREACH (StringText const & i, subtitles) {
if (!pending.empty() && (i.v_align() != pending.back().v_align() || fabs(i.v_position() - pending.back().v_position()) > 1e-4)) {
- images.push_back (render_line (pending, fonts, target, time, frame_rate));
+ images.push_back (render_line(pending, fonts, target, time, frame_rate, x_offset, y_offset));
pending.clear ();
}
pending.push_back (i);
}
if (!pending.empty ()) {
- images.push_back (render_line (pending, fonts, target, time, frame_rate));
+ images.push_back (render_line(pending, fonts, target, time, frame_rate, x_offset, y_offset));
}
return images;
std::string marked_up (std::list<StringText> subtitles, int target_height, float fade_factor);
std::list<PositionImage> render_text (
- std::list<StringText>, std::list<boost::shared_ptr<dcpomatic::Font> > fonts, dcp::Size, dcpomatic::DCPTime, int
+ std::list<StringText> subtitles,
+ std::list<boost::shared_ptr<dcpomatic::Font> > fonts,
+ dcp::Size target,
+ dcpomatic::DCPTime time,
+ int frame_rate,
+ double x_offset,
+ double y_offset
);
int outline_width;
};
+extern bool operator== (StringText const & a, StringText const & b);
+
#endif
audio_processor.cc
audio_ring_buffers.cc
audio_stream.cc
+ bitmap_text.cc
butler.cc
text_content.cc
text_decoder.cc
position_image.cc
ratio.cc
raw_image_proxy.cc
+ rect.cc
reel_writer.cc
render_text.cc
resampler.cc
spl.cc
spl_entry.cc
string_log_entry.cc
+ string_text.cc
string_text_file.cc
string_text_file_content.cc
string_text_file_decoder.cc
#include "lib/config.h"
#include "lib/compose.hpp"
#include "lib/dcpomatic_log.h"
+#include "lib/text_content.h"
extern "C" {
#include <libavutil/pixfmt.h>
}
property == VideoContentProperty::FADE_OUT ||
property == VideoContentProperty::COLOUR_CONVERSION ||
property == PlayerProperty::VIDEO_CONTAINER_SIZE ||
- property == PlayerProperty::FILM_CONTAINER
+ property == PlayerProperty::FILM_CONTAINER ||
+ property == TextContentProperty::X_OFFSET ||
+ property == TextContentProperty::Y_OFFSET
) {
refreshed = quick_refresh ();
}
ColourConversion(),
VIDEO_RANGE_FULL,
weak_ptr<Content>(),
- optional<Frame>()
+ optional<dcpomatic::DCPTime>(),
+ 24
)
);
+ /* XXX_b
pvf->set_text (PositionImage (sub_image, Position<int> (50, 60)));
+ */
shared_ptr<DCPVideo> frame (
new DCPVideo (
ColourConversion(),
VIDEO_RANGE_FULL,
weak_ptr<Content>(),
- optional<Frame>()
+ optional<dcpomatic::DCPTime>(),
+ 24
)
);
+ /* XXX_b
pvf->set_text (PositionImage (sub_image, Position<int> (50, 60)));
+ */
shared_ptr<DCPVideo> frame (
new DCPVideo (
ColourConversion(),
VIDEO_RANGE_FULL,
weak_ptr<Content>(),
- optional<Frame>()
+ optional<dcpomatic::DCPTime>(),
+ 24
)
);
PresetColourConversion::all().front().conversion,
VIDEO_RANGE_FULL,
weak_ptr<Content>(),
- optional<Frame>()
+ optional<dcpomatic::DCPTime>(),
+ 24
)
);