Bump libsub for libdcp dependency removal.
[dcpomatic.git] / src / lib / text_decoder.cc
1 /*
2     Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
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.
10
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.
15
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/>.
18
19 */
20
21
22 #include "compose.hpp"
23 #include "log.h"
24 #include "text_content.h"
25 #include "text_decoder.h"
26 #include "util.h"
27 #include <sub/subtitle.h>
28 #include <boost/algorithm/string.hpp>
29 #include <iostream>
30
31
32 using std::cout;
33 using std::max;
34 using std::min;
35 using std::shared_ptr;
36 using std::string;
37 using std::vector;
38 using boost::optional;
39 using namespace dcpomatic;
40
41
42 TextDecoder::TextDecoder (
43         Decoder* parent,
44         shared_ptr<const TextContent> content
45         )
46         : DecoderPart (parent)
47         , _content (content)
48 {
49
50 }
51
52
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
58  *  of the video frame)
59  */
60 void
61 TextDecoder::emit_bitmap_start (ContentBitmapText const& bitmap)
62 {
63         BitmapStart (bitmap);
64         maybe_set_position(bitmap.from());
65 }
66
67
68 static
69 void
70 set_forced_appearance(shared_ptr<const TextContent> content, StringText& subtitle)
71 {
72         if (content->colour()) {
73                 subtitle.set_colour(*content->colour());
74         }
75         if (content->effect_colour()) {
76                 subtitle.set_effect_colour(*content->effect_colour());
77         }
78         if (content->effect()) {
79                 subtitle.set_effect(*content->effect());
80         }
81         if (content->fade_in()) {
82                 subtitle.set_fade_up_time(dcp::Time(content->fade_in()->seconds(), 1000));
83         }
84         if (content->fade_out()) {
85                 subtitle.set_fade_down_time (dcp::Time(content->fade_out()->seconds(), 1000));
86         }
87 }
88
89
90 void
91 TextDecoder::emit_plain_start (ContentTime from, vector<dcp::SubtitleString> subtitles, dcp::Standard valign_standard)
92 {
93         vector<StringText> string_texts;
94
95         for (auto& subtitle: subtitles) {
96                 auto string_text = StringText(
97                         subtitle,
98                         content()->outline_width(),
99                         content()->get_font(subtitle.font().get_value_or("")),
100                         valign_standard
101                         );
102                 string_text.set_text(string_text.text());
103                 set_forced_appearance(content(), string_text);
104                 string_texts.push_back(string_text);
105         }
106
107         PlainStart(ContentStringText(from, string_texts));
108         maybe_set_position(from);
109 }
110
111
112 void
113 TextDecoder::emit_plain_start (ContentTime from, sub::Subtitle const & sub_subtitle)
114 {
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();
123                         }
124                 }
125         }
126
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;
133                         } else {
134                                 lowest_proportional = min(lowest_proportional.get(), line.vertical_position.proportional.get());
135                         }
136                 }
137         }
138
139         vector<StringText> string_texts;
140         for (auto line: sub_subtitle.lines) {
141                 for (auto block: line.blocks) {
142
143                         if (!block.font_size.specified()) {
144                                 /* Fallback default font size if no other has been specified */
145                                 block.font_size.set_points (48);
146                         }
147
148                         float v_position;
149                         dcp::VAlign v_align;
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.
158                                            */
159                                         v_position = 0.9 -
160                                                 (1 + bottom_line.get() - line.vertical_position.line.get()) * multiplier;
161
162                                         v_align = dcp::VAlign::TOP;
163                                         break;
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;
168                                         break;
169                                 case sub::VERTICAL_CENTRE_OF_SCREEN:
170                                         v_position = line.vertical_position.line.get() * multiplier;
171                                         v_align = dcp::VAlign::CENTER;
172                                         break;
173                                 }
174                         } else {
175                                 DCPOMATIC_ASSERT (line.vertical_position.reference);
176                                 if (line.vertical_position.proportional) {
177                                         v_position = line.vertical_position.proportional.get();
178                                 } else {
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;
182                                 }
183
184                                 if (lowest_proportional) {
185                                         /* Adjust line spacing */
186                                         v_position = ((v_position - lowest_proportional.get()) * content()->line_spacing()) + lowest_proportional.get();
187                                 }
188
189                                 switch (line.vertical_position.reference.get()) {
190                                 case sub::TOP_OF_SCREEN:
191                                         v_align = dcp::VAlign::TOP;
192                                         break;
193                                 case sub::VERTICAL_CENTRE_OF_SCREEN:
194                                         v_align = dcp::VAlign::CENTER;
195                                         break;
196                                 case sub::BOTTOM_OF_SCREEN:
197                                         v_align = dcp::VAlign::BOTTOM;
198                                         break;
199                                 default:
200                                         v_align = dcp::VAlign::TOP;
201                                         break;
202                                 }
203                         }
204
205                         dcp::HAlign h_align;
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);
211                                 break;
212                         case sub::HORIZONTAL_CENTRE_OF_SCREEN:
213                                 h_align = dcp::HAlign::CENTER;
214                                 break;
215                         case sub::RIGHT_OF_SCREEN:
216                                 h_align = dcp::HAlign::RIGHT;
217                                 h_position = max(h_position, 0.05f);
218                                 break;
219                         default:
220                                 h_align = dcp::HAlign::CENTER;
221                                 break;
222                         }
223
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.
227                         */
228
229                         auto dcp_colour = [](sub::Colour const& c) {
230                                 return dcp::Colour(lrintf(c.r * 255), lrintf(c.g * 255), lrintf(c.b * 255));
231                                 };
232
233                         auto dcp_subtitle = dcp::SubtitleString(
234                                 optional<string>(),
235                                 block.italic,
236                                 block.bold,
237                                 block.underline,
238                                 dcp_colour(block.colour),
239                                 block.font_size.points (72 * 11),
240                                 1.0,
241                                 dcp::Time (from.seconds(), 1000),
242                                 /* XXX: hmm; this is a bit ugly (we don't know the to time yet) */
243                                 dcp::Time (),
244                                 h_position,
245                                 h_align,
246                                 v_position,
247                                 v_align,
248                                 0,
249                                 dcp::Direction::LTR,
250                                 block.text,
251                                 dcp::Effect::NONE,
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.
258                                 */
259                                 dcp::Time (),
260                                 dcp::Time (),
261                                 0
262                                 );
263
264                         auto string_text = StringText(
265                                 dcp_subtitle,
266                                 content()->outline_width(),
267                                 content()->get_font(block.font.get_value_or("")),
268                                 dcp::Standard::SMPTE
269                                 );
270                         set_forced_appearance(content(), string_text);
271                         string_texts.push_back(string_text);
272                 }
273         }
274
275         PlainStart(ContentStringText(from, string_texts));
276         maybe_set_position(from);
277 }
278
279
280 void
281 TextDecoder::emit_stop (ContentTime to)
282 {
283         Stop (to);
284 }
285
286
287 void
288 TextDecoder::emit_plain (ContentTimePeriod period, vector<dcp::SubtitleString> subtitles, dcp::Standard valign_standard)
289 {
290         emit_plain_start (period.from, subtitles, valign_standard);
291         emit_stop (period.to);
292 }
293
294
295 void
296 TextDecoder::emit_plain (ContentTimePeriod period, sub::Subtitle const& subtitles)
297 {
298         emit_plain_start (period.from, subtitles);
299         emit_stop (period.to);
300 }
301
302
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)
306  */
307 void
308 TextDecoder::emit_bitmap (ContentTimePeriod period, shared_ptr<const Image> image, dcpomatic::Rect<double> rect)
309 {
310         emit_bitmap_start ({ period.from, image, rect });
311         emit_stop (period.to);
312 }
313
314
315 void
316 TextDecoder::seek ()
317 {
318         _position = ContentTime ();
319 }
320
321
322 void
323 TextDecoder::maybe_set_position (dcpomatic::ContentTime position)
324 {
325         if (!_position || position > *_position) {
326                 _position = position;
327         }
328 }
329