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 "compose.hpp"
24 #include "text_content.h"
25 #include "text_decoder.h"
27 #include <sub/subtitle.h>
28 #include <boost/algorithm/string.hpp>
35 using std::shared_ptr;
38 using boost::optional;
39 using namespace dcpomatic;
42 TextDecoder::TextDecoder (
44 shared_ptr<const TextContent> content
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 (ContentBitmapText const& bitmap)
64 maybe_set_position(bitmap.from());
70 set_forced_appearance(shared_ptr<const TextContent> content, StringText& subtitle)
72 if (content->colour()) {
73 subtitle.set_colour(*content->colour());
75 if (content->effect_colour()) {
76 subtitle.set_effect_colour(*content->effect_colour());
78 if (content->effect()) {
79 subtitle.set_effect(*content->effect());
81 if (content->fade_in()) {
82 subtitle.set_fade_up_time(dcp::Time(content->fade_in()->seconds(), 1000));
84 if (content->fade_out()) {
85 subtitle.set_fade_down_time (dcp::Time(content->fade_out()->seconds(), 1000));
91 TextDecoder::emit_plain_start(ContentTime from, vector<dcp::SubtitleString> subtitles, dcp::SubtitleStandard valign_standard)
93 vector<StringText> string_texts;
95 for (auto& subtitle: subtitles) {
96 auto string_text = StringText(
98 content()->outline_width(),
99 content()->get_font(subtitle.font().get_value_or("")),
102 string_text.set_text(string_text.text());
103 set_forced_appearance(content(), string_text);
104 string_texts.push_back(string_text);
107 PlainStart(ContentStringText(from, string_texts));
108 maybe_set_position(from);
113 TextDecoder::emit_plain_start (ContentTime from, sub::Subtitle const & sub_subtitle)
115 /* See if our next subtitle needs to be vertically placed on screen by us */
116 bool needs_placement = false;
117 optional<int> bottom_line;
118 for (auto line: sub_subtitle.lines) {
119 if (!line.vertical_position.reference || (line.vertical_position.line && !line.vertical_position.lines) || line.vertical_position.reference.get() == sub::TOP_OF_SUBTITLE) {
120 needs_placement = true;
121 if (!bottom_line || bottom_line.get() < line.vertical_position.line.get()) {
122 bottom_line = line.vertical_position.line.get();
127 /* Find the lowest proportional position */
128 optional<float> lowest_proportional;
129 for (auto line: sub_subtitle.lines) {
130 if (line.vertical_position.proportional) {
131 if (!lowest_proportional) {
132 lowest_proportional = line.vertical_position.proportional;
134 lowest_proportional = min(lowest_proportional.get(), line.vertical_position.proportional.get());
139 vector<StringText> string_texts;
140 for (auto line: sub_subtitle.lines) {
141 for (auto block: line.blocks) {
143 if (!block.font_size.specified()) {
144 /* Fallback default font size if no other has been specified */
145 block.font_size.set_points (48);
150 if (needs_placement) {
151 DCPOMATIC_ASSERT (line.vertical_position.line);
152 double const multiplier = 1.2 * content()->line_spacing() * content()->y_scale() * block.font_size.proportional (72 * 11);
153 switch (line.vertical_position.reference.get_value_or(sub::BOTTOM_OF_SCREEN)) {
154 case sub::BOTTOM_OF_SCREEN:
155 case sub::TOP_OF_SUBTITLE:
156 /* This 0.1 is an arbitrary value to lift the bottom sub off the bottom
157 of the screen a bit to a pleasing degree.
160 (1 + bottom_line.get() - line.vertical_position.line.get()) * multiplier;
162 /* Align our subtitles to the bottom of the screen, because if we are making a SMPTE
163 * DCP and the projection system uses the wrong standard to interpret vertical position,
164 * a bottom-aligned subtitle will be less wrong than a top-aligned one. This is because
165 * in the top-aligned case the difference will be the distance between bbox top an
166 * baseline, but in the bottom-aligned case the difference will be between bbox bottom
167 * and baseline (which is shorter).
169 v_align = dcp::VAlign::BOTTOM;
171 case sub::TOP_OF_SCREEN:
172 /* This 0.1 is another fudge factor to bring the top line away from the top of the screen a little */
173 v_position = 0.12 + line.vertical_position.line.get() * multiplier;
174 v_align = dcp::VAlign::TOP;
176 case sub::VERTICAL_CENTRE_OF_SCREEN:
177 v_position = line.vertical_position.line.get() * multiplier;
178 v_align = dcp::VAlign::CENTER;
182 DCPOMATIC_ASSERT (line.vertical_position.reference);
183 if (line.vertical_position.proportional) {
184 v_position = line.vertical_position.proportional.get();
186 DCPOMATIC_ASSERT (line.vertical_position.line);
187 DCPOMATIC_ASSERT (line.vertical_position.lines);
188 v_position = float(*line.vertical_position.line) / *line.vertical_position.lines;
191 if (lowest_proportional) {
192 /* Adjust line spacing */
193 v_position = ((v_position - lowest_proportional.get()) * content()->line_spacing()) + lowest_proportional.get();
196 switch (line.vertical_position.reference.get()) {
197 case sub::TOP_OF_SCREEN:
198 v_align = dcp::VAlign::TOP;
200 case sub::VERTICAL_CENTRE_OF_SCREEN:
201 v_align = dcp::VAlign::CENTER;
203 case sub::BOTTOM_OF_SCREEN:
204 v_align = dcp::VAlign::BOTTOM;
207 v_align = dcp::VAlign::TOP;
213 float h_position = line.horizontal_position.proportional;
214 switch (line.horizontal_position.reference) {
215 case sub::LEFT_OF_SCREEN:
216 h_align = dcp::HAlign::LEFT;
217 h_position = max(h_position, 0.05f);
219 case sub::HORIZONTAL_CENTRE_OF_SCREEN:
220 h_align = dcp::HAlign::CENTER;
222 case sub::RIGHT_OF_SCREEN:
223 h_align = dcp::HAlign::RIGHT;
224 h_position = max(h_position, 0.05f);
227 h_align = dcp::HAlign::CENTER;
231 /* The idea here (rightly or wrongly) is that we set the appearance based on the
232 values in the libsub objects, and these are overridden with values from the
233 content by the other emit_plain_start() above.
236 auto dcp_colour = [](sub::Colour const& c) {
237 return dcp::Colour(lrintf(c.r * 255), lrintf(c.g * 255), lrintf(c.b * 255));
240 auto dcp_subtitle = dcp::SubtitleString(
245 dcp_colour(block.colour),
246 block.font_size.points (72 * 11),
248 dcp::Time (from.seconds(), 1000),
249 /* XXX: hmm; this is a bit ugly (we don't know the to time yet) */
259 dcp_colour(block.effect_colour.get_value_or(sub::Colour(0, 0, 0))),
260 /* Hack: we should use subtitle.fade_up and subtitle.fade_down here
261 but the times of these often don't have a frame rate associated
262 with them so the sub::Time won't convert them to milliseconds without
263 throwing an exception. Since only DCP subs fill those in (and we don't
264 use libsub for DCP subs) we can cheat by just putting 0 in here.
271 auto string_text = StringText(
273 content()->outline_width(),
274 content()->get_font(block.font.get_value_or("")),
275 dcp::SubtitleStandard::SMPTE_2014
277 set_forced_appearance(content(), string_text);
278 string_texts.push_back(string_text);
282 PlainStart(ContentStringText(from, string_texts));
283 maybe_set_position(from);
288 TextDecoder::emit_stop (ContentTime to)
295 TextDecoder::emit_plain(ContentTimePeriod period, vector<dcp::SubtitleString> subtitles, dcp::SubtitleStandard valign_standard)
297 emit_plain_start (period.from, subtitles, valign_standard);
298 emit_stop (period.to);
303 TextDecoder::emit_plain (ContentTimePeriod period, sub::Subtitle const& subtitles)
305 emit_plain_start (period.from, subtitles);
306 emit_stop (period.to);
310 /* @param rect Area expressed as a fraction of the video frame that this subtitle
311 * is for (e.g. a width of 0.5 means the width of the subtitle is half the width
312 * of the video frame)
315 TextDecoder::emit_bitmap (ContentTimePeriod period, shared_ptr<const Image> image, dcpomatic::Rect<double> rect)
317 emit_bitmap_start ({ period.from, image, rect });
318 emit_stop (period.to);
325 _position = ContentTime ();
330 TextDecoder::maybe_set_position (dcpomatic::ContentTime position)
332 if (!_position || position > *_position) {
333 _position = position;