2 Copyright (C) 2013-2021 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/>.
22 #include "text_decoder.h"
23 #include "text_content.h"
26 #include "compose.hpp"
27 #include <sub/subtitle.h>
28 #include <boost/algorithm/string.hpp>
37 using std::shared_ptr;
38 using boost::optional;
40 using namespace dcpomatic;
43 TextDecoder::TextDecoder (
45 shared_ptr<const TextContent> c,
48 : DecoderPart (parent)
56 /** Called by subclasses when an image subtitle is starting.
57 * @param from From time of the subtitle.
58 * @param image Subtitle image.
59 * @param rect Area expressed as a fraction of the video frame that this subtitle
60 * is for (e.g. a width of 0.5 means the width of the subtitle is half the width
64 TextDecoder::emit_bitmap_start (ContentTime from, shared_ptr<Image> image, dcpomatic::Rect<double> rect)
66 BitmapStart (ContentBitmapText (from, image, rect));
72 TextDecoder::emit_plain_start (ContentTime from, list<dcp::SubtitleString> s)
75 /* We must escape < and > in strings, otherwise they might confuse our subtitle
76 renderer (which uses some HTML-esque markup to do bold/italic etc.)
79 boost::algorithm::replace_all (t, "<", "<");
80 boost::algorithm::replace_all (t, ">", ">");
83 /* Set any forced appearance */
84 if (content()->colour()) {
85 i.set_colour (*content()->colour());
87 if (content()->effect_colour()) {
88 i.set_effect_colour (*content()->effect_colour());
90 if (content()->effect()) {
91 i.set_effect (*content()->effect());
93 if (content()->fade_in()) {
94 i.set_fade_up_time (dcp::Time(content()->fade_in()->seconds(), 1000));
96 if (content()->fade_out()) {
97 i.set_fade_down_time (dcp::Time(content()->fade_out()->seconds(), 1000));
101 PlainStart (ContentStringText (from, s));
107 TextDecoder::emit_plain_start (ContentTime from, sub::Subtitle const & subtitle)
109 /* See if our next subtitle needs to be vertically placed on screen by us */
110 bool needs_placement = false;
111 optional<int> bottom_line;
112 for (auto i: subtitle.lines) {
113 if (!i.vertical_position.reference || (i.vertical_position.line && !i.vertical_position.lines) || i.vertical_position.reference.get() == sub::TOP_OF_SUBTITLE) {
114 needs_placement = true;
115 if (!bottom_line || bottom_line.get() < i.vertical_position.line.get()) {
116 bottom_line = i.vertical_position.line.get();
121 /* Find the lowest proportional position */
122 optional<float> lowest_proportional;
123 for (auto i: subtitle.lines) {
124 if (i.vertical_position.proportional) {
125 if (!lowest_proportional) {
126 lowest_proportional = i.vertical_position.proportional;
128 lowest_proportional = min (lowest_proportional.get(), i.vertical_position.proportional.get());
133 list<dcp::SubtitleString> out;
134 for (auto i: subtitle.lines) {
135 for (auto j: i.blocks) {
137 if (!j.font_size.specified()) {
138 /* Fallback default font size if no other has been specified */
139 j.font_size.set_points (48);
144 if (needs_placement) {
145 DCPOMATIC_ASSERT (i.vertical_position.line);
146 double const multiplier = 1.2 * content()->line_spacing() * content()->y_scale() * j.font_size.proportional (72 * 11);
147 switch (i.vertical_position.reference.get_value_or(sub::BOTTOM_OF_SCREEN)) {
148 case sub::BOTTOM_OF_SCREEN:
149 case sub::TOP_OF_SUBTITLE:
150 /* This 1.015 is an arbitrary value to lift the bottom sub off the bottom
151 of the screen a bit to a pleasing degree.
154 (1 + bottom_line.get() - i.vertical_position.line.get()) * multiplier;
156 v_align = dcp::VAlign::TOP;
158 case sub::TOP_OF_SCREEN:
159 /* This 0.1 is another fudge factor to bring the top line away from the top of the screen a little */
160 v_position = 0.12 + i.vertical_position.line.get() * multiplier;
161 v_align = dcp::VAlign::TOP;
163 case sub::VERTICAL_CENTRE_OF_SCREEN:
164 v_position = i.vertical_position.line.get() * multiplier;
165 v_align = dcp::VAlign::CENTER;
169 DCPOMATIC_ASSERT (i.vertical_position.reference);
170 if (i.vertical_position.proportional) {
171 v_position = i.vertical_position.proportional.get();
173 DCPOMATIC_ASSERT (i.vertical_position.line);
174 DCPOMATIC_ASSERT (i.vertical_position.lines);
175 v_position = float(*i.vertical_position.line) / *i.vertical_position.lines;
178 if (lowest_proportional) {
179 /* Adjust line spacing */
180 v_position = ((v_position - lowest_proportional.get()) * content()->line_spacing()) + lowest_proportional.get();
183 switch (i.vertical_position.reference.get()) {
184 case sub::TOP_OF_SCREEN:
185 v_align = dcp::VAlign::TOP;
187 case sub::VERTICAL_CENTRE_OF_SCREEN:
188 v_align = dcp::VAlign::CENTER;
190 case sub::BOTTOM_OF_SCREEN:
191 v_align = dcp::VAlign::BOTTOM;
194 v_align = dcp::VAlign::TOP;
200 float h_position = i.horizontal_position.proportional;
201 switch (i.horizontal_position.reference) {
202 case sub::LEFT_OF_SCREEN:
203 h_align = dcp::HAlign::LEFT;
204 h_position = max(h_position, 0.05f);
206 case sub::HORIZONTAL_CENTRE_OF_SCREEN:
207 h_align = dcp::HAlign::CENTER;
209 case sub::RIGHT_OF_SCREEN:
210 h_align = dcp::HAlign::RIGHT;
211 h_position = max(h_position, 0.05f);
214 h_align = dcp::HAlign::CENTER;
218 /* The idea here (rightly or wrongly) is that we set the appearance based on the
219 values in the libsub objects, and these are overridden with values from the
220 content by the other emit_plain_start() above.
224 dcp::SubtitleString (
225 string(TEXT_FONT_ID),
230 j.font_size.points (72 * 11),
232 dcp::Time (from.seconds(), 1000),
233 /* XXX: hmm; this is a bit ugly (we don't know the to time yet) */
242 j.effect_colour.get_value_or(sub::Colour(0, 0, 0)).dcp(),
243 /* Hack: we should use subtitle.fade_up and subtitle.fade_down here
244 but the times of these often don't have a frame rate associated
245 with them so the sub::Time won't convert them to milliseconds without
246 throwing an exception. Since only DCP subs fill those in (and we don't
247 use libsub for DCP subs) we can cheat by just putting 0 in here.
256 emit_plain_start (from, out);
261 TextDecoder::emit_stop (ContentTime to)
268 TextDecoder::emit_plain (ContentTimePeriod period, list<dcp::SubtitleString> s)
270 emit_plain_start (period.from, s);
271 emit_stop (period.to);
276 TextDecoder::emit_plain (ContentTimePeriod period, sub::Subtitle const & s)
278 emit_plain_start (period.from, s);
279 emit_stop (period.to);
283 /* @param rect Area expressed as a fraction of the video frame that this subtitle
284 * is for (e.g. a width of 0.5 means the width of the subtitle is half the width
285 * of the video frame)
288 TextDecoder::emit_bitmap (ContentTimePeriod period, shared_ptr<Image> image, dcpomatic::Rect<double> rect)
290 emit_bitmap_start (period.from, image, rect);
291 emit_stop (period.to);
298 _position = ContentTime ();