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)
55 /* Called when an image subtitle is starting.
56 * @param from From time of the subtitle.
57 * @param image Subtitle image.
58 * @param rect Area expressed as a fraction of the video frame that this subtitle
59 * is for (e.g. a width of 0.5 means the width of the subtitle is half the width
63 TextDecoder::emit_bitmap_start (ContentTime from, shared_ptr<Image> image, dcpomatic::Rect<double> rect)
65 BitmapStart (from, image, rect);
71 TextDecoder::emit_plain_start (ContentTime from, list<dcp::SubtitleString> s)
74 /* We must escape < and > in strings, otherwise they might confuse our subtitle
75 renderer (which uses some HTML-esque markup to do bold/italic etc.)
78 boost::algorithm::replace_all (t, "<", "<");
79 boost::algorithm::replace_all (t, ">", ">");
82 /* Set any forced appearance */
83 if (content()->colour()) {
84 i.set_colour (*content()->colour());
86 if (content()->effect_colour()) {
87 i.set_effect_colour (*content()->effect_colour());
89 if (content()->effect()) {
90 i.set_effect (*content()->effect());
92 if (content()->fade_in()) {
93 i.set_fade_up_time (dcp::Time(content()->fade_in()->seconds(), 1000));
95 if (content()->fade_out()) {
96 i.set_fade_down_time (dcp::Time(content()->fade_out()->seconds(), 1000));
100 StringStart (from, s);
106 TextDecoder::emit_plain_start (ContentTime from, sub::Subtitle const & subtitle)
108 /* See if our next subtitle needs to be vertically placed on screen by us */
109 bool needs_placement = false;
110 optional<int> bottom_line;
111 for (auto i: subtitle.lines) {
112 if (!i.vertical_position.reference || (i.vertical_position.line && !i.vertical_position.lines) || i.vertical_position.reference.get() == sub::TOP_OF_SUBTITLE) {
113 needs_placement = true;
114 if (!bottom_line || bottom_line.get() < i.vertical_position.line.get()) {
115 bottom_line = i.vertical_position.line.get();
120 /* Find the lowest proportional position */
121 optional<float> lowest_proportional;
122 for (auto i: subtitle.lines) {
123 if (i.vertical_position.proportional) {
124 if (!lowest_proportional) {
125 lowest_proportional = i.vertical_position.proportional;
127 lowest_proportional = min (lowest_proportional.get(), i.vertical_position.proportional.get());
132 list<dcp::SubtitleString> out;
133 for (auto i: subtitle.lines) {
134 for (auto j: i.blocks) {
136 if (!j.font_size.specified()) {
137 /* Fallback default font size if no other has been specified */
138 j.font_size.set_points (48);
143 if (needs_placement) {
144 DCPOMATIC_ASSERT (i.vertical_position.line);
145 double const multiplier = 1.2 * content()->line_spacing() * content()->y_scale() * j.font_size.proportional (72 * 11);
146 switch (i.vertical_position.reference.get_value_or(sub::BOTTOM_OF_SCREEN)) {
147 case sub::BOTTOM_OF_SCREEN:
148 case sub::TOP_OF_SUBTITLE:
149 /* This 1.015 is an arbitrary value to lift the bottom sub off the bottom
150 of the screen a bit to a pleasing degree.
153 (1 + bottom_line.get() - i.vertical_position.line.get()) * multiplier;
155 v_align = dcp::VAlign::TOP;
157 case sub::TOP_OF_SCREEN:
158 /* This 0.1 is another fudge factor to bring the top line away from the top of the screen a little */
159 v_position = 0.12 + i.vertical_position.line.get() * multiplier;
160 v_align = dcp::VAlign::TOP;
162 case sub::VERTICAL_CENTRE_OF_SCREEN:
163 v_position = i.vertical_position.line.get() * multiplier;
164 v_align = dcp::VAlign::CENTER;
168 DCPOMATIC_ASSERT (i.vertical_position.reference);
169 if (i.vertical_position.proportional) {
170 v_position = i.vertical_position.proportional.get();
172 DCPOMATIC_ASSERT (i.vertical_position.line);
173 DCPOMATIC_ASSERT (i.vertical_position.lines);
174 v_position = float(*i.vertical_position.line) / *i.vertical_position.lines;
177 if (lowest_proportional) {
178 /* Adjust line spacing */
179 v_position = ((v_position - lowest_proportional.get()) * content()->line_spacing()) + lowest_proportional.get();
182 switch (i.vertical_position.reference.get()) {
183 case sub::TOP_OF_SCREEN:
184 v_align = dcp::VAlign::TOP;
186 case sub::VERTICAL_CENTRE_OF_SCREEN:
187 v_align = dcp::VAlign::CENTER;
189 case sub::BOTTOM_OF_SCREEN:
190 v_align = dcp::VAlign::BOTTOM;
193 v_align = dcp::VAlign::TOP;
199 float h_position = i.horizontal_position.proportional;
200 switch (i.horizontal_position.reference) {
201 case sub::LEFT_OF_SCREEN:
202 h_align = dcp::HAlign::LEFT;
203 h_position = max(h_position, 0.05f);
205 case sub::HORIZONTAL_CENTRE_OF_SCREEN:
206 h_align = dcp::HAlign::CENTER;
208 case sub::RIGHT_OF_SCREEN:
209 h_align = dcp::HAlign::RIGHT;
210 h_position = max(h_position, 0.05f);
213 h_align = dcp::HAlign::CENTER;
217 /* The idea here (rightly or wrongly) is that we set the appearance based on the
218 values in the libsub objects, and these are overridden with values from the
219 content by the other emit_plain_start() above.
223 dcp::SubtitleString (
224 string(TEXT_FONT_ID),
229 j.font_size.points (72 * 11),
231 dcp::Time (from.seconds(), 1000),
232 /* XXX: hmm; this is a bit ugly (we don't know the to time yet) */
241 j.effect_colour.get_value_or(sub::Colour(0, 0, 0)).dcp(),
242 /* Hack: we should use subtitle.fade_up and subtitle.fade_down here
243 but the times of these often don't have a frame rate associated
244 with them so the sub::Time won't convert them to milliseconds without
245 throwing an exception. Since only DCP subs fill those in (and we don't
246 use libsub for DCP subs) we can cheat by just putting 0 in here.
255 emit_plain_start (from, out);
260 TextDecoder::emit_stop (ContentTime to)
267 TextDecoder::emit_plain (ContentTimePeriod period, list<dcp::SubtitleString> s)
269 emit_plain_start (period.from, s);
270 emit_stop (period.to);
275 TextDecoder::emit_plain (ContentTimePeriod period, sub::Subtitle const & s)
277 emit_plain_start (period.from, s);
278 emit_stop (period.to);
282 /* @param rect Area expressed as a fraction of the video frame that this subtitle
283 * is for (e.g. a width of 0.5 means the width of the subtitle is half the width
284 * of the video frame)
287 TextDecoder::emit_bitmap (ContentTimePeriod period, shared_ptr<Image> image, dcpomatic::Rect<double> rect)
289 emit_bitmap_start (period.from, image, rect);
290 emit_stop (period.to);
297 _position = ContentTime ();