2 Copyright (C) 2013-2017 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
6 DCP-o-matic is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 DCP-o-matic is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
21 #include "text_decoder.h"
22 #include "text_content.h"
25 #include "compose.hpp"
26 #include <sub/subtitle.h>
27 #include <boost/shared_ptr.hpp>
28 #include <boost/foreach.hpp>
29 #include <boost/algorithm/string.hpp>
37 using boost::shared_ptr;
38 using boost::optional;
39 using boost::function;
40 using namespace dcpomatic;
42 TextDecoder::TextDecoder (
44 shared_ptr<const TextContent> c,
47 : DecoderPart (parent)
54 /** Called by subclasses when an image subtitle is starting.
55 * @param from From time of the subtitle.
56 * @param image Subtitle image.
57 * @param rect Area expressed as a fraction of the video frame that this subtitle
58 * is for (e.g. a width of 0.5 means the width of the subtitle is half the width
62 TextDecoder::emit_bitmap_start (ContentTime from, shared_ptr<Image> image, dcpomatic::Rect<double> rect)
64 BitmapStart (ContentBitmapText (from, image, rect));
69 TextDecoder::emit_plain_start (ContentTime from, list<dcp::SubtitleString> s)
71 BOOST_FOREACH (dcp::SubtitleString& i, s) {
72 /* We must escape < and > in strings, otherwise they might confuse our subtitle
73 renderer (which uses some HTML-esque markup to do bold/italic etc.)
76 boost::algorithm::replace_all (t, "<", "<");
77 boost::algorithm::replace_all (t, ">", ">");
80 /* Set any forced appearance */
81 if (content()->colour()) {
82 i.set_colour (*content()->colour());
84 if (content()->effect_colour()) {
85 i.set_effect_colour (*content()->effect_colour());
87 if (content()->effect()) {
88 i.set_effect (*content()->effect());
90 if (content()->fade_in()) {
91 i.set_fade_up_time (dcp::Time(content()->fade_in()->seconds(), 1000));
93 if (content()->fade_out()) {
94 i.set_fade_down_time (dcp::Time(content()->fade_out()->seconds(), 1000));
98 PlainStart (ContentStringText (from, s));
103 TextDecoder::emit_plain_start (ContentTime from, sub::Subtitle const & subtitle)
105 /* See if our next subtitle needs to be vertically placed on screen by us */
106 bool needs_placement = false;
107 optional<int> bottom_line;
108 BOOST_FOREACH (sub::Line i, subtitle.lines) {
109 if (!i.vertical_position.reference || (i.vertical_position.line && !i.vertical_position.lines) || i.vertical_position.reference.get() == sub::TOP_OF_SUBTITLE) {
110 needs_placement = true;
111 if (!bottom_line || bottom_line.get() < i.vertical_position.line.get()) {
112 bottom_line = i.vertical_position.line.get();
117 /* Find the lowest proportional position */
118 optional<float> lowest_proportional;
119 BOOST_FOREACH (sub::Line i, subtitle.lines) {
120 if (i.vertical_position.proportional) {
121 if (!lowest_proportional) {
122 lowest_proportional = i.vertical_position.proportional;
124 lowest_proportional = min (lowest_proportional.get(), i.vertical_position.proportional.get());
129 list<dcp::SubtitleString> out;
130 BOOST_FOREACH (sub::Line i, subtitle.lines) {
131 BOOST_FOREACH (sub::Block j, i.blocks) {
133 if (!j.font_size.specified()) {
134 /* Fallback default font size if no other has been specified */
135 j.font_size.set_points (48);
140 if (needs_placement) {
141 DCPOMATIC_ASSERT (i.vertical_position.line);
142 double const multiplier = 1.2 * content()->line_spacing() * content()->y_scale() * j.font_size.proportional (72 * 11);
143 switch (i.vertical_position.reference.get_value_or(sub::BOTTOM_OF_SCREEN)) {
144 case sub::BOTTOM_OF_SCREEN:
145 case sub::TOP_OF_SUBTITLE:
146 /* This 1.015 is an arbitrary value to lift the bottom sub off the bottom
147 of the screen a bit to a pleasing degree.
150 (1 + bottom_line.get() - i.vertical_position.line.get()) * multiplier;
152 v_align = dcp::VALIGN_TOP;
154 case sub::TOP_OF_SCREEN:
155 /* This 0.1 is another fudge factor to bring the top line away from the top of the screen a little */
156 v_position = 0.12 + i.vertical_position.line.get() * multiplier;
157 v_align = dcp::VALIGN_TOP;
159 case sub::VERTICAL_CENTRE_OF_SCREEN:
160 v_position = i.vertical_position.line.get() * multiplier;
161 v_align = dcp::VALIGN_CENTER;
165 DCPOMATIC_ASSERT (i.vertical_position.reference);
166 if (i.vertical_position.proportional) {
167 v_position = i.vertical_position.proportional.get();
169 DCPOMATIC_ASSERT (i.vertical_position.line);
170 DCPOMATIC_ASSERT (i.vertical_position.lines);
171 v_position = float(*i.vertical_position.line) / *i.vertical_position.lines;
174 if (lowest_proportional) {
175 /* Adjust line spacing */
176 v_position = ((v_position - lowest_proportional.get()) * content()->line_spacing()) + lowest_proportional.get();
179 switch (i.vertical_position.reference.get()) {
180 case sub::TOP_OF_SCREEN:
181 v_align = dcp::VALIGN_TOP;
183 case sub::VERTICAL_CENTRE_OF_SCREEN:
184 v_align = dcp::VALIGN_CENTER;
186 case sub::BOTTOM_OF_SCREEN:
187 v_align = dcp::VALIGN_BOTTOM;
190 v_align = dcp::VALIGN_TOP;
196 float h_position = i.horizontal_position.proportional;
197 switch (i.horizontal_position.reference) {
198 case sub::LEFT_OF_SCREEN:
199 h_align = dcp::HALIGN_LEFT;
200 h_position = max(h_position, 0.05f);
202 case sub::HORIZONTAL_CENTRE_OF_SCREEN:
203 h_align = dcp::HALIGN_CENTER;
205 case sub::RIGHT_OF_SCREEN:
206 h_align = dcp::HALIGN_RIGHT;
207 h_position = max(h_position, 0.05f);
210 h_align = dcp::HALIGN_CENTER;
214 /* The idea here (rightly or wrongly) is that we set the appearance based on the
215 values in the libsub objects, and these are overridden with values from the
216 content by the other emit_plain_start() above.
220 dcp::SubtitleString (
221 string(TEXT_FONT_ID),
226 j.font_size.points (72 * 11),
228 dcp::Time (from.seconds(), 1000),
229 /* XXX: hmm; this is a bit ugly (we don't know the to time yet) */
238 j.effect_colour.get_value_or(sub::Colour(0, 0, 0)).dcp(),
239 /* Hack: we should use subtitle.fade_up and subtitle.fade_down here
240 but the times of these often don't have a frame rate associated
241 with them so the sub::Time won't convert them to milliseconds without
242 throwing an exception. Since only DCP subs fill those in (and we don't
243 use libsub for DCP subs) we can cheat by just putting 0 in here.
252 emit_plain_start (from, out);
256 TextDecoder::emit_stop (ContentTime to)
262 TextDecoder::emit_plain (ContentTimePeriod period, list<dcp::SubtitleString> s)
264 emit_plain_start (period.from, s);
265 emit_stop (period.to);
269 TextDecoder::emit_plain (ContentTimePeriod period, sub::Subtitle const & s)
271 emit_plain_start (period.from, s);
272 emit_stop (period.to);
275 /* @param rect Area expressed as a fraction of the video frame that this subtitle
276 * is for (e.g. a width of 0.5 means the width of the subtitle is half the width
277 * of the video frame)
280 TextDecoder::emit_bitmap (ContentTimePeriod period, shared_ptr<Image> image, dcpomatic::Rect<double> rect)
282 emit_bitmap_start (period.from, image, rect);
283 emit_stop (period.to);
289 _position = ContentTime ();