eecfce19dde7f05a65fcee9200eab9f4536317cf
[dcpomatic.git] / src / lib / subtitle_decoder.cc
1 /*
2     Copyright (C) 2013-2017 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 #include "subtitle_decoder.h"
22 #include "subtitle_content.h"
23 #include "util.h"
24 #include "log.h"
25 #include "compose.hpp"
26 #include <sub/subtitle.h>
27 #include <boost/shared_ptr.hpp>
28 #include <boost/foreach.hpp>
29 #include <boost/algorithm/string.hpp>
30 #include <iostream>
31
32 using std::list;
33 using std::cout;
34 using std::string;
35 using std::min;
36 using boost::shared_ptr;
37 using boost::optional;
38 using boost::function;
39
40 SubtitleDecoder::SubtitleDecoder (
41         Decoder* parent,
42         shared_ptr<const SubtitleContent> c,
43         shared_ptr<Log> log,
44         ContentTime first
45         )
46         : DecoderPart (parent, log)
47         , _content (c)
48         , _position (first)
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 SubtitleDecoder::emit_image_start (ContentTime from, shared_ptr<Image> image, dcpomatic::Rect<double> rect)
62 {
63         ImageStart (ContentImageSubtitle (from, image, rect));
64 }
65
66 void
67 SubtitleDecoder::emit_text_start (ContentTime from, list<dcp::SubtitleString> s)
68 {
69         BOOST_FOREACH (dcp::SubtitleString& i, s) {
70                 /* We must escape < and > in strings, otherwise they might confuse our subtitle
71                    renderer (which uses some HTML-esque markup to do bold/italic etc.)
72                 */
73                 string t = i.text ();
74                 boost::algorithm::replace_all (t, "<", "&lt;");
75                 boost::algorithm::replace_all (t, ">", "&gt;");
76                 i.set_text (t);
77
78                 /* Set any forced appearance */
79                 if (content()->colour()) {
80                         i.set_colour (*content()->colour());
81                 }
82                 i.set_effect_colour (content()->effect_colour());
83                 if (content()->outline()) {
84                         i.set_effect (dcp::BORDER);
85                 } else if (content()->shadow()) {
86                         i.set_effect (dcp::SHADOW);
87                 }
88                 i.set_fade_up_time (dcp::Time(content()->fade_in().seconds(), 1000));
89                 i.set_fade_down_time (dcp::Time(content()->fade_out().seconds(), 1000));
90         }
91
92         TextStart (ContentTextSubtitle (from, s));
93         _position = from;
94 }
95
96 void
97 SubtitleDecoder::emit_text_start (ContentTime from, sub::Subtitle const & subtitle)
98 {
99         /* See if our next subtitle needs to be vertically placed on screen by us */
100         bool needs_placement = false;
101         optional<int> bottom_line;
102         BOOST_FOREACH (sub::Line i, subtitle.lines) {
103                 if (!i.vertical_position.reference || i.vertical_position.reference.get() == sub::TOP_OF_SUBTITLE) {
104                         needs_placement = true;
105                         DCPOMATIC_ASSERT (i.vertical_position.line);
106                         if (!bottom_line || bottom_line.get() < i.vertical_position.line.get()) {
107                                 bottom_line = i.vertical_position.line.get();
108                         }
109                 }
110         }
111
112         /* Find the lowest proportional position */
113         optional<float> lowest_proportional;
114         BOOST_FOREACH (sub::Line i, subtitle.lines) {
115                 if (i.vertical_position.proportional) {
116                         if (!lowest_proportional) {
117                                 lowest_proportional = i.vertical_position.proportional;
118                         } else {
119                                 lowest_proportional = min (lowest_proportional.get(), i.vertical_position.proportional.get());
120                         }
121                 }
122         }
123
124         list<dcp::SubtitleString> out;
125         BOOST_FOREACH (sub::Line i, subtitle.lines) {
126                 BOOST_FOREACH (sub::Block j, i.blocks) {
127
128                         if (!j.font_size.specified()) {
129                                 /* Fallback default font size if no other has been specified */
130                                 j.font_size.set_points (48);
131                         }
132
133                         float v_position;
134                         dcp::VAlign v_align;
135                         if (needs_placement) {
136                                 DCPOMATIC_ASSERT (i.vertical_position.line);
137                                 /* This 1.015 is an arbitrary value to lift the bottom sub off the bottom
138                                    of the screen a bit to a pleasing degree.
139                                 */
140                                 v_position = 1.015 -
141                                         (1 + bottom_line.get() - i.vertical_position.line.get())
142                                         * 1.2 * content()->line_spacing() * content()->y_scale() * j.font_size.proportional (72 * 11);
143
144                                 v_align = dcp::VALIGN_TOP;
145                         } else {
146                                 DCPOMATIC_ASSERT (i.vertical_position.proportional);
147                                 DCPOMATIC_ASSERT (i.vertical_position.reference);
148                                 v_position = i.vertical_position.proportional.get();
149
150                                 if (lowest_proportional) {
151                                         /* Adjust line spacing */
152                                         v_position = ((v_position - lowest_proportional.get()) * content()->line_spacing()) + lowest_proportional.get();
153                                 }
154
155                                 switch (i.vertical_position.reference.get()) {
156                                 case sub::TOP_OF_SCREEN:
157                                         v_align = dcp::VALIGN_TOP;
158                                         break;
159                                 case sub::VERTICAL_CENTRE_OF_SCREEN:
160                                         v_align = dcp::VALIGN_CENTER;
161                                         break;
162                                 case sub::BOTTOM_OF_SCREEN:
163                                         v_align = dcp::VALIGN_BOTTOM;
164                                         break;
165                                 default:
166                                         v_align = dcp::VALIGN_TOP;
167                                         break;
168                                 }
169                         }
170
171                         dcp::HAlign h_align;
172                         switch (i.horizontal_position.reference) {
173                         case sub::LEFT_OF_SCREEN:
174                                 h_align = dcp::HALIGN_LEFT;
175                                 break;
176                         case sub::HORIZONTAL_CENTRE_OF_SCREEN:
177                                 h_align = dcp::HALIGN_CENTER;
178                                 break;
179                         case sub::RIGHT_OF_SCREEN:
180                                 h_align = dcp::HALIGN_RIGHT;
181                                 break;
182                         default:
183                                 h_align = dcp::HALIGN_CENTER;
184                                 break;
185                         }
186
187                         /* The idea here (rightly or wrongly) is that we set the appearance based on the
188                            values in the libsub objects, and these are overridden with values from the
189                            content by the other emit_text_start() above.
190                         */
191
192                         out.push_back (
193                                 dcp::SubtitleString (
194                                         string(TEXT_FONT_ID),
195                                         j.italic,
196                                         j.bold,
197                                         j.underline,
198                                         j.colour.dcp(),
199                                         j.font_size.points (72 * 11),
200                                         1.0,
201                                         dcp::Time (from.seconds(), 1000),
202                                         /* XXX: hmm; this is a bit ugly (we don't know the to time yet) */
203                                         dcp::Time (),
204                                         i.horizontal_position.proportional,
205                                         h_align,
206                                         v_position,
207                                         v_align,
208                                         dcp::DIRECTION_LTR,
209                                         j.text,
210                                         dcp::NONE,
211                                         j.effect_colour.get_value_or(sub::Colour(0, 0, 0)).dcp(),
212                                         /* Hack: we should use subtitle.fade_up and subtitle.fade_down here
213                                            but the times of these often don't have a frame rate associated
214                                            with them so the sub::Time won't convert them to milliseconds without
215                                            throwing an exception.  Since only DCP subs fill those in (and we don't
216                                            use libsub for DCP subs) we can cheat by just putting 0 in here.
217                                         */
218                                         dcp::Time (),
219                                         dcp::Time ()
220                                         )
221                                 );
222                 }
223         }
224
225         emit_text_start (from, out);
226 }
227
228 void
229 SubtitleDecoder::emit_stop (ContentTime to)
230 {
231         Stop (to);
232 }
233
234 void
235 SubtitleDecoder::emit_text (ContentTimePeriod period, list<dcp::SubtitleString> s)
236 {
237         emit_text_start (period.from, s);
238         emit_stop (period.to);
239 }
240
241 void
242 SubtitleDecoder::emit_text (ContentTimePeriod period, sub::Subtitle const & s)
243 {
244         emit_text_start (period.from, s);
245         emit_stop (period.to);
246 }
247
248 void
249 SubtitleDecoder::seek ()
250 {
251         _position = ContentTime ();
252 }