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/foreach.hpp>
28 #include <boost/algorithm/string.hpp>
36 using std::shared_ptr;
37 using boost::optional;
38 using boost::function;
39 using namespace dcpomatic;
41 TextDecoder::TextDecoder (
43 shared_ptr<const TextContent> c,
46 : DecoderPart (parent)
53 /** Called by subclasses when an image subtitle is starting.
54 * @param from From time of the subtitle.
55 * @param image Subtitle image.
56 * @param rect Area expressed as a fraction of the video frame that this subtitle
57 * is for (e.g. a width of 0.5 means the width of the subtitle is half the width
61 TextDecoder::emit_bitmap_start (ContentTime from, shared_ptr<Image> image, dcpomatic::Rect<double> rect)
63 BitmapStart (ContentBitmapText (from, image, rect));
68 TextDecoder::emit_plain_start (ContentTime from, list<dcp::SubtitleString> s)
70 BOOST_FOREACH (dcp::SubtitleString& i, s) {
71 /* We must escape < and > in strings, otherwise they might confuse our subtitle
72 renderer (which uses some HTML-esque markup to do bold/italic etc.)
75 boost::algorithm::replace_all (t, "<", "<");
76 boost::algorithm::replace_all (t, ">", ">");
79 /* Set any forced appearance */
80 if (content()->colour()) {
81 i.set_colour (*content()->colour());
83 if (content()->effect_colour()) {
84 i.set_effect_colour (*content()->effect_colour());
86 if (content()->effect()) {
87 i.set_effect (*content()->effect());
89 if (content()->fade_in()) {
90 i.set_fade_up_time (dcp::Time(content()->fade_in()->seconds(), 1000));
92 if (content()->fade_out()) {
93 i.set_fade_down_time (dcp::Time(content()->fade_out()->seconds(), 1000));
97 PlainStart (ContentStringText (from, s));
102 TextDecoder::emit_plain_start (ContentTime from, sub::Subtitle const & subtitle)
104 /* See if our next subtitle needs to be vertically placed on screen by us */
105 bool needs_placement = false;
106 optional<int> bottom_line;
107 BOOST_FOREACH (sub::Line i, subtitle.lines) {
108 if (!i.vertical_position.reference || (i.vertical_position.line && !i.vertical_position.lines) || i.vertical_position.reference.get() == sub::TOP_OF_SUBTITLE) {
109 needs_placement = true;
110 if (!bottom_line || bottom_line.get() < i.vertical_position.line.get()) {
111 bottom_line = i.vertical_position.line.get();
116 /* Find the lowest proportional position */
117 optional<float> lowest_proportional;
118 BOOST_FOREACH (sub::Line i, subtitle.lines) {
119 if (i.vertical_position.proportional) {
120 if (!lowest_proportional) {
121 lowest_proportional = i.vertical_position.proportional;
123 lowest_proportional = min (lowest_proportional.get(), i.vertical_position.proportional.get());
128 list<dcp::SubtitleString> out;
129 BOOST_FOREACH (sub::Line i, subtitle.lines) {
130 BOOST_FOREACH (sub::Block j, i.blocks) {
132 if (!j.font_size.specified()) {
133 /* Fallback default font size if no other has been specified */
134 j.font_size.set_points (48);
139 if (needs_placement) {
140 DCPOMATIC_ASSERT (i.vertical_position.line);
141 double const multiplier = 1.2 * content()->line_spacing() * content()->y_scale() * j.font_size.proportional (72 * 11);
142 switch (i.vertical_position.reference.get_value_or(sub::BOTTOM_OF_SCREEN)) {
143 case sub::BOTTOM_OF_SCREEN:
144 case sub::TOP_OF_SUBTITLE:
145 /* This 1.015 is an arbitrary value to lift the bottom sub off the bottom
146 of the screen a bit to a pleasing degree.
149 (1 + bottom_line.get() - i.vertical_position.line.get()) * multiplier;
151 v_align = dcp::VALIGN_TOP;
153 case sub::TOP_OF_SCREEN:
154 /* This 0.1 is another fudge factor to bring the top line away from the top of the screen a little */
155 v_position = 0.12 + i.vertical_position.line.get() * multiplier;
156 v_align = dcp::VALIGN_TOP;
158 case sub::VERTICAL_CENTRE_OF_SCREEN:
159 v_position = i.vertical_position.line.get() * multiplier;
160 v_align = dcp::VALIGN_CENTER;
164 DCPOMATIC_ASSERT (i.vertical_position.reference);
165 if (i.vertical_position.proportional) {
166 v_position = i.vertical_position.proportional.get();
168 DCPOMATIC_ASSERT (i.vertical_position.line);
169 DCPOMATIC_ASSERT (i.vertical_position.lines);
170 v_position = float(*i.vertical_position.line) / *i.vertical_position.lines;
173 if (lowest_proportional) {
174 /* Adjust line spacing */
175 v_position = ((v_position - lowest_proportional.get()) * content()->line_spacing()) + lowest_proportional.get();
178 switch (i.vertical_position.reference.get()) {
179 case sub::TOP_OF_SCREEN:
180 v_align = dcp::VALIGN_TOP;
182 case sub::VERTICAL_CENTRE_OF_SCREEN:
183 v_align = dcp::VALIGN_CENTER;
185 case sub::BOTTOM_OF_SCREEN:
186 v_align = dcp::VALIGN_BOTTOM;
189 v_align = dcp::VALIGN_TOP;
195 float h_position = i.horizontal_position.proportional;
196 switch (i.horizontal_position.reference) {
197 case sub::LEFT_OF_SCREEN:
198 h_align = dcp::HALIGN_LEFT;
199 h_position = max(h_position, 0.05f);
201 case sub::HORIZONTAL_CENTRE_OF_SCREEN:
202 h_align = dcp::HALIGN_CENTER;
204 case sub::RIGHT_OF_SCREEN:
205 h_align = dcp::HALIGN_RIGHT;
206 h_position = max(h_position, 0.05f);
209 h_align = dcp::HALIGN_CENTER;
213 /* The idea here (rightly or wrongly) is that we set the appearance based on the
214 values in the libsub objects, and these are overridden with values from the
215 content by the other emit_plain_start() above.
219 dcp::SubtitleString (
220 string(TEXT_FONT_ID),
225 j.font_size.points (72 * 11),
227 dcp::Time (from.seconds(), 1000),
228 /* XXX: hmm; this is a bit ugly (we don't know the to time yet) */
237 j.effect_colour.get_value_or(sub::Colour(0, 0, 0)).dcp(),
238 /* Hack: we should use subtitle.fade_up and subtitle.fade_down here
239 but the times of these often don't have a frame rate associated
240 with them so the sub::Time won't convert them to milliseconds without
241 throwing an exception. Since only DCP subs fill those in (and we don't
242 use libsub for DCP subs) we can cheat by just putting 0 in here.
251 emit_plain_start (from, out);
255 TextDecoder::emit_stop (ContentTime to)
261 TextDecoder::emit_plain (ContentTimePeriod period, list<dcp::SubtitleString> s)
263 emit_plain_start (period.from, s);
264 emit_stop (period.to);
268 TextDecoder::emit_plain (ContentTimePeriod period, sub::Subtitle const & s)
270 emit_plain_start (period.from, s);
271 emit_stop (period.to);
274 /* @param rect Area expressed as a fraction of the video frame that this subtitle
275 * is for (e.g. a width of 0.5 means the width of the subtitle is half the width
276 * of the video frame)
279 TextDecoder::emit_bitmap (ContentTimePeriod period, shared_ptr<Image> image, dcpomatic::Rect<double> rect)
281 emit_bitmap_start (period.from, image, rect);
282 emit_stop (period.to);
288 _position = ContentTime ();