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_colour = [](sub::Colour const& c) {
230 return dcp::Colour(lrintf(c.r * 255), lrintf(c.g * 255), lrintf(c.b * 255));
233 auto dcp_subtitle = dcp::SubtitleString(
238 dcp_colour(block.colour),
239 block.font_size.points (72 * 11),
241 dcp::Time (from.seconds(), 1000),
242 /* XXX: hmm; this is a bit ugly (we don't know the to time yet) */
252 dcp_colour(block.effect_colour.get_value_or(sub::Colour(0, 0, 0))),
253 /* Hack: we should use subtitle.fade_up and subtitle.fade_down here
254 but the times of these often don't have a frame rate associated
255 with them so the sub::Time won't convert them to milliseconds without
256 throwing an exception. Since only DCP subs fill those in (and we don't
257 use libsub for DCP subs) we can cheat by just putting 0 in here.
264 auto string_text = StringText(
266 content()->outline_width(),
267 content()->get_font(block.font.get_value_or("")),
270 set_forced_appearance(content(), string_text);
271 string_texts.push_back(string_text);
275 PlainStart(ContentStringText(from, string_texts));
276 maybe_set_position(from);
281 TextDecoder::emit_stop (ContentTime to)
288 TextDecoder::emit_plain (ContentTimePeriod period, vector<dcp::SubtitleString> subtitles, dcp::Standard valign_standard)
290 emit_plain_start (period.from, subtitles, valign_standard);
291 emit_stop (period.to);
296 TextDecoder::emit_plain (ContentTimePeriod period, sub::Subtitle const& subtitles)
298 emit_plain_start (period.from, subtitles);
299 emit_stop (period.to);
303 /* @param rect Area expressed as a fraction of the video frame that this subtitle
304 * is for (e.g. a width of 0.5 means the width of the subtitle is half the width
305 * of the video frame)
308 TextDecoder::emit_bitmap (ContentTimePeriod period, shared_ptr<const Image> image, dcpomatic::Rect<double> rect)
310 emit_bitmap_start ({ period.from, image, rect });
311 emit_stop (period.to);
318 _position = ContentTime ();
323 TextDecoder::maybe_set_position (dcpomatic::ContentTime position)
325 if (!_position || position > *_position) {
326 _position = position;