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::Standard 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.9 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 v_align = dcp::VAlign::TOP;
164 case sub::TOP_OF_SCREEN:
165 /* This 0.1 is another fudge factor to bring the top line away from the top of the screen a little */
166 v_position = 0.12 + line.vertical_position.line.get() * multiplier;
167 v_align = dcp::VAlign::TOP;
169 case sub::VERTICAL_CENTRE_OF_SCREEN:
170 v_position = line.vertical_position.line.get() * multiplier;
171 v_align = dcp::VAlign::CENTER;
175 DCPOMATIC_ASSERT (line.vertical_position.reference);
176 if (line.vertical_position.proportional) {
177 v_position = line.vertical_position.proportional.get();
179 DCPOMATIC_ASSERT (line.vertical_position.line);
180 DCPOMATIC_ASSERT (line.vertical_position.lines);
181 v_position = float(*line.vertical_position.line) / *line.vertical_position.lines;
184 if (lowest_proportional) {
185 /* Adjust line spacing */
186 v_position = ((v_position - lowest_proportional.get()) * content()->line_spacing()) + lowest_proportional.get();
189 switch (line.vertical_position.reference.get()) {
190 case sub::TOP_OF_SCREEN:
191 v_align = dcp::VAlign::TOP;
193 case sub::VERTICAL_CENTRE_OF_SCREEN:
194 v_align = dcp::VAlign::CENTER;
196 case sub::BOTTOM_OF_SCREEN:
197 v_align = dcp::VAlign::BOTTOM;
200 v_align = dcp::VAlign::TOP;
206 float h_position = line.horizontal_position.proportional;
207 switch (line.horizontal_position.reference) {
208 case sub::LEFT_OF_SCREEN:
209 h_align = dcp::HAlign::LEFT;
210 h_position = max(h_position, 0.05f);
212 case sub::HORIZONTAL_CENTRE_OF_SCREEN:
213 h_align = dcp::HAlign::CENTER;
215 case sub::RIGHT_OF_SCREEN:
216 h_align = dcp::HAlign::RIGHT;
217 h_position = max(h_position, 0.05f);
220 h_align = dcp::HAlign::CENTER;
224 /* The idea here (rightly or wrongly) is that we set the appearance based on the
225 values in the libsub objects, and these are overridden with values from the
226 content by the other emit_plain_start() above.
229 auto dcp_subtitle = dcp::SubtitleString(
235 block.font_size.points (72 * 11),
237 dcp::Time (from.seconds(), 1000),
238 /* XXX: hmm; this is a bit ugly (we don't know the to time yet) */
248 block.effect_colour.get_value_or(sub::Colour(0, 0, 0)).dcp(),
249 /* Hack: we should use subtitle.fade_up and subtitle.fade_down here
250 but the times of these often don't have a frame rate associated
251 with them so the sub::Time won't convert them to milliseconds without
252 throwing an exception. Since only DCP subs fill those in (and we don't
253 use libsub for DCP subs) we can cheat by just putting 0 in here.
260 auto string_text = StringText(
262 content()->outline_width(),
263 content()->get_font(block.font.get_value_or("")),
266 set_forced_appearance(content(), string_text);
267 string_texts.push_back(string_text);
271 PlainStart(ContentStringText(from, string_texts));
272 maybe_set_position(from);
277 TextDecoder::emit_stop (ContentTime to)
284 TextDecoder::emit_plain (ContentTimePeriod period, vector<dcp::SubtitleString> subtitles, dcp::Standard valign_standard)
286 emit_plain_start (period.from, subtitles, valign_standard);
287 emit_stop (period.to);
292 TextDecoder::emit_plain (ContentTimePeriod period, sub::Subtitle const& subtitles)
294 emit_plain_start (period.from, subtitles);
295 emit_stop (period.to);
299 /* @param rect Area expressed as a fraction of the video frame that this subtitle
300 * is for (e.g. a width of 0.5 means the width of the subtitle is half the width
301 * of the video frame)
304 TextDecoder::emit_bitmap (ContentTimePeriod period, shared_ptr<const Image> image, dcpomatic::Rect<double> rect)
306 emit_bitmap_start ({ period.from, image, rect });
307 emit_stop (period.to);
314 _position = ContentTime ();
319 TextDecoder::maybe_set_position (dcpomatic::ContentTime position)
321 if (!_position || position > *_position) {
322 _position = position;