Fix build with newer FFmpeg.
[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 "text_decoder.h"
23 #include "text_content.h"
24 #include "util.h"
25 #include "log.h"
26 #include "compose.hpp"
27 #include <sub/subtitle.h>
28 #include <boost/algorithm/string.hpp>
29 #include <iostream>
30
31
32 using std::list;
33 using std::cout;
34 using std::string;
35 using std::min;
36 using std::max;
37 using std::shared_ptr;
38 using boost::optional;
39 using std::function;
40 using namespace dcpomatic;
41
42
43 TextDecoder::TextDecoder (
44         Decoder* parent,
45         shared_ptr<const TextContent> c,
46         ContentTime first
47         )
48         : DecoderPart (parent)
49         , _content (c)
50         , _position (first)
51 {
52
53 }
54
55
56 /** Called by subclasses when an image subtitle is starting.
57  *  @param from From time of the subtitle.
58  *  @param image Subtitle image.
59  *  @param rect Area expressed as a fraction of the video frame that this subtitle
60  *  is for (e.g. a width of 0.5 means the width of the subtitle is half the width
61  *  of the video frame)
62  */
63 void
64 TextDecoder::emit_bitmap_start (ContentTime from, shared_ptr<Image> image, dcpomatic::Rect<double> rect)
65 {
66         BitmapStart (ContentBitmapText (from, image, rect));
67         _position = from;
68 }
69
70
71 void
72 TextDecoder::emit_plain_start (ContentTime from, list<dcp::SubtitleString> s)
73 {
74         for (auto& i: s) {
75                 /* We must escape some things, otherwise they might confuse our subtitle
76                    renderer (which uses entities and some HTML-esque markup to do bold/italic etc.)
77                 */
78                 string t = i.text ();
79                 boost::algorithm::replace_all (t, "&", "&amp;");
80                 boost::algorithm::replace_all (t, "<", "&lt;");
81                 boost::algorithm::replace_all (t, ">", "&gt;");
82                 i.set_text (t);
83
84                 /* Set any forced appearance */
85                 if (content()->colour()) {
86                         i.set_colour (*content()->colour());
87                 }
88                 if (content()->effect_colour()) {
89                         i.set_effect_colour (*content()->effect_colour());
90                 }
91                 if (content()->effect()) {
92                         i.set_effect (*content()->effect());
93                 }
94                 if (content()->fade_in()) {
95                         i.set_fade_up_time (dcp::Time(content()->fade_in()->seconds(), 1000));
96                 }
97                 if (content()->fade_out()) {
98                         i.set_fade_down_time (dcp::Time(content()->fade_out()->seconds(), 1000));
99                 }
100         }
101
102         PlainStart (ContentStringText (from, s));
103         _position = from;
104 }
105
106
107 void
108 TextDecoder::emit_plain_start (ContentTime from, sub::Subtitle const & subtitle)
109 {
110         /* See if our next subtitle needs to be vertically placed on screen by us */
111         bool needs_placement = false;
112         optional<int> bottom_line;
113         for (auto i: subtitle.lines) {
114                 if (!i.vertical_position.reference || (i.vertical_position.line && !i.vertical_position.lines) || i.vertical_position.reference.get() == sub::TOP_OF_SUBTITLE) {
115                         needs_placement = true;
116                         if (!bottom_line || bottom_line.get() < i.vertical_position.line.get()) {
117                                 bottom_line = i.vertical_position.line.get();
118                         }
119                 }
120         }
121
122         /* Find the lowest proportional position */
123         optional<float> lowest_proportional;
124         for (auto i: subtitle.lines) {
125                 if (i.vertical_position.proportional) {
126                         if (!lowest_proportional) {
127                                 lowest_proportional = i.vertical_position.proportional;
128                         } else {
129                                 lowest_proportional = min (lowest_proportional.get(), i.vertical_position.proportional.get());
130                         }
131                 }
132         }
133
134         list<dcp::SubtitleString> out;
135         for (auto i: subtitle.lines) {
136                 for (auto j: i.blocks) {
137
138                         if (!j.font_size.specified()) {
139                                 /* Fallback default font size if no other has been specified */
140                                 j.font_size.set_points (48);
141                         }
142
143                         float v_position;
144                         dcp::VAlign v_align;
145                         if (needs_placement) {
146                                 DCPOMATIC_ASSERT (i.vertical_position.line);
147                                 double const multiplier = 1.2 * content()->line_spacing() * content()->y_scale() * j.font_size.proportional (72 * 11);
148                                 switch (i.vertical_position.reference.get_value_or(sub::BOTTOM_OF_SCREEN)) {
149                                 case sub::BOTTOM_OF_SCREEN:
150                                 case sub::TOP_OF_SUBTITLE:
151                                         /* This 1.015 is an arbitrary value to lift the bottom sub off the bottom
152                                            of the screen a bit to a pleasing degree.
153                                            */
154                                         v_position = 1.015 -
155                                                 (1 + bottom_line.get() - i.vertical_position.line.get()) * multiplier;
156
157                                         v_align = dcp::VAlign::TOP;
158                                         break;
159                                 case sub::TOP_OF_SCREEN:
160                                         /* This 0.1 is another fudge factor to bring the top line away from the top of the screen a little */
161                                         v_position = 0.12 + i.vertical_position.line.get() * multiplier;
162                                         v_align = dcp::VAlign::TOP;
163                                         break;
164                                 case sub::VERTICAL_CENTRE_OF_SCREEN:
165                                         v_position = i.vertical_position.line.get() * multiplier;
166                                         v_align = dcp::VAlign::CENTER;
167                                         break;
168                                 }
169                         } else {
170                                 DCPOMATIC_ASSERT (i.vertical_position.reference);
171                                 if (i.vertical_position.proportional) {
172                                         v_position = i.vertical_position.proportional.get();
173                                 } else {
174                                         DCPOMATIC_ASSERT (i.vertical_position.line);
175                                         DCPOMATIC_ASSERT (i.vertical_position.lines);
176                                         v_position = float(*i.vertical_position.line) / *i.vertical_position.lines;
177                                 }
178
179                                 if (lowest_proportional) {
180                                         /* Adjust line spacing */
181                                         v_position = ((v_position - lowest_proportional.get()) * content()->line_spacing()) + lowest_proportional.get();
182                                 }
183
184                                 switch (i.vertical_position.reference.get()) {
185                                 case sub::TOP_OF_SCREEN:
186                                         v_align = dcp::VAlign::TOP;
187                                         break;
188                                 case sub::VERTICAL_CENTRE_OF_SCREEN:
189                                         v_align = dcp::VAlign::CENTER;
190                                         break;
191                                 case sub::BOTTOM_OF_SCREEN:
192                                         v_align = dcp::VAlign::BOTTOM;
193                                         break;
194                                 default:
195                                         v_align = dcp::VAlign::TOP;
196                                         break;
197                                 }
198                         }
199
200                         dcp::HAlign h_align;
201                         float h_position = i.horizontal_position.proportional;
202                         switch (i.horizontal_position.reference) {
203                         case sub::LEFT_OF_SCREEN:
204                                 h_align = dcp::HAlign::LEFT;
205                                 h_position = max(h_position, 0.05f);
206                                 break;
207                         case sub::HORIZONTAL_CENTRE_OF_SCREEN:
208                                 h_align = dcp::HAlign::CENTER;
209                                 break;
210                         case sub::RIGHT_OF_SCREEN:
211                                 h_align = dcp::HAlign::RIGHT;
212                                 h_position = max(h_position, 0.05f);
213                                 break;
214                         default:
215                                 h_align = dcp::HAlign::CENTER;
216                                 break;
217                         }
218
219                         /* The idea here (rightly or wrongly) is that we set the appearance based on the
220                            values in the libsub objects, and these are overridden with values from the
221                            content by the other emit_plain_start() above.
222                         */
223
224                         out.push_back (
225                                 dcp::SubtitleString (
226                                         string(TEXT_FONT_ID),
227                                         j.italic,
228                                         j.bold,
229                                         j.underline,
230                                         j.colour.dcp(),
231                                         j.font_size.points (72 * 11),
232                                         1.0,
233                                         dcp::Time (from.seconds(), 1000),
234                                         /* XXX: hmm; this is a bit ugly (we don't know the to time yet) */
235                                         dcp::Time (),
236                                         h_position,
237                                         h_align,
238                                         v_position,
239                                         v_align,
240                                         dcp::Direction::LTR,
241                                         j.text,
242                                         dcp::Effect::NONE,
243                                         j.effect_colour.get_value_or(sub::Colour(0, 0, 0)).dcp(),
244                                         /* Hack: we should use subtitle.fade_up and subtitle.fade_down here
245                                            but the times of these often don't have a frame rate associated
246                                            with them so the sub::Time won't convert them to milliseconds without
247                                            throwing an exception.  Since only DCP subs fill those in (and we don't
248                                            use libsub for DCP subs) we can cheat by just putting 0 in here.
249                                         */
250                                         dcp::Time (),
251                                         dcp::Time ()
252                                         )
253                                 );
254                 }
255         }
256
257         emit_plain_start (from, out);
258 }
259
260
261 void
262 TextDecoder::emit_stop (ContentTime to)
263 {
264         Stop (to);
265 }
266
267
268 void
269 TextDecoder::emit_plain (ContentTimePeriod period, list<dcp::SubtitleString> s)
270 {
271         emit_plain_start (period.from, s);
272         emit_stop (period.to);
273 }
274
275
276 void
277 TextDecoder::emit_plain (ContentTimePeriod period, sub::Subtitle const & s)
278 {
279         emit_plain_start (period.from, s);
280         emit_stop (period.to);
281 }
282
283
284 /*  @param rect Area expressed as a fraction of the video frame that this subtitle
285  *  is for (e.g. a width of 0.5 means the width of the subtitle is half the width
286  *  of the video frame)
287  */
288 void
289 TextDecoder::emit_bitmap (ContentTimePeriod period, shared_ptr<Image> image, dcpomatic::Rect<double> rect)
290 {
291         emit_bitmap_start (period.from, image, rect);
292         emit_stop (period.to);
293 }
294
295
296 void
297 TextDecoder::seek ()
298 {
299         _position = ContentTime ();
300 }