750deb9b30602b952e88876ca27c20f1683d1178
[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_subtitle = dcp::SubtitleString(
230                                 optional<string>(),
231                                 block.italic,
232                                 block.bold,
233                                 block.underline,
234                                 block.colour.dcp(),
235                                 block.font_size.points (72 * 11),
236                                 1.0,
237                                 dcp::Time (from.seconds(), 1000),
238                                 /* XXX: hmm; this is a bit ugly (we don't know the to time yet) */
239                                 dcp::Time (),
240                                 h_position,
241                                 h_align,
242                                 v_position,
243                                 v_align,
244                                 0,
245                                 dcp::Direction::LTR,
246                                 block.text,
247                                 dcp::Effect::NONE,
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.
254                                 */
255                                 dcp::Time (),
256                                 dcp::Time (),
257                                 0
258                                 );
259
260                         auto string_text = StringText(
261                                 dcp_subtitle,
262                                 content()->outline_width(),
263                                 content()->get_font(block.font.get_value_or("")),
264                                 dcp::Standard::SMPTE
265                                 );
266                         set_forced_appearance(content(), string_text);
267                         string_texts.push_back(string_text);
268                 }
269         }
270
271         PlainStart(ContentStringText(from, string_texts));
272         maybe_set_position(from);
273 }
274
275
276 void
277 TextDecoder::emit_stop (ContentTime to)
278 {
279         Stop (to);
280 }
281
282
283 void
284 TextDecoder::emit_plain (ContentTimePeriod period, vector<dcp::SubtitleString> subtitles, dcp::Standard valign_standard)
285 {
286         emit_plain_start (period.from, subtitles, valign_standard);
287         emit_stop (period.to);
288 }
289
290
291 void
292 TextDecoder::emit_plain (ContentTimePeriod period, sub::Subtitle const& subtitles)
293 {
294         emit_plain_start (period.from, subtitles);
295         emit_stop (period.to);
296 }
297
298
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)
302  */
303 void
304 TextDecoder::emit_bitmap (ContentTimePeriod period, shared_ptr<const Image> image, dcpomatic::Rect<double> rect)
305 {
306         emit_bitmap_start ({ period.from, image, rect });
307         emit_stop (period.to);
308 }
309
310
311 void
312 TextDecoder::seek ()
313 {
314         _position = ContentTime ();
315 }
316
317
318 void
319 TextDecoder::maybe_set_position (dcpomatic::ContentTime position)
320 {
321         if (!_position || position > *_position) {
322                 _position = position;
323         }
324 }
325