Use an enum for the effect in SubtitleContent.
[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                 if (content()->effect_colour()) {
83                         i.set_effect_colour (*content()->effect_colour());
84                 }
85                 i.set_effect (content()->effect());
86                 i.set_fade_up_time (dcp::Time(content()->fade_in().seconds(), 1000));
87                 i.set_fade_down_time (dcp::Time(content()->fade_out().seconds(), 1000));
88         }
89
90         TextStart (ContentTextSubtitle (from, s));
91         _position = from;
92 }
93
94 void
95 SubtitleDecoder::emit_text_start (ContentTime from, sub::Subtitle const & subtitle)
96 {
97         /* See if our next subtitle needs to be vertically placed on screen by us */
98         bool needs_placement = false;
99         optional<int> bottom_line;
100         BOOST_FOREACH (sub::Line i, subtitle.lines) {
101                 if (!i.vertical_position.reference || i.vertical_position.reference.get() == sub::TOP_OF_SUBTITLE) {
102                         needs_placement = true;
103                         DCPOMATIC_ASSERT (i.vertical_position.line);
104                         if (!bottom_line || bottom_line.get() < i.vertical_position.line.get()) {
105                                 bottom_line = i.vertical_position.line.get();
106                         }
107                 }
108         }
109
110         /* Find the lowest proportional position */
111         optional<float> lowest_proportional;
112         BOOST_FOREACH (sub::Line i, subtitle.lines) {
113                 if (i.vertical_position.proportional) {
114                         if (!lowest_proportional) {
115                                 lowest_proportional = i.vertical_position.proportional;
116                         } else {
117                                 lowest_proportional = min (lowest_proportional.get(), i.vertical_position.proportional.get());
118                         }
119                 }
120         }
121
122         list<dcp::SubtitleString> out;
123         BOOST_FOREACH (sub::Line i, subtitle.lines) {
124                 BOOST_FOREACH (sub::Block j, i.blocks) {
125
126                         if (!j.font_size.specified()) {
127                                 /* Fallback default font size if no other has been specified */
128                                 j.font_size.set_points (48);
129                         }
130
131                         float v_position;
132                         dcp::VAlign v_align;
133                         if (needs_placement) {
134                                 DCPOMATIC_ASSERT (i.vertical_position.line);
135                                 /* This 1.015 is an arbitrary value to lift the bottom sub off the bottom
136                                    of the screen a bit to a pleasing degree.
137                                 */
138                                 v_position = 1.015 -
139                                         (1 + bottom_line.get() - i.vertical_position.line.get())
140                                         * 1.2 * content()->line_spacing() * content()->y_scale() * j.font_size.proportional (72 * 11);
141
142                                 v_align = dcp::VALIGN_TOP;
143                         } else {
144                                 DCPOMATIC_ASSERT (i.vertical_position.proportional);
145                                 DCPOMATIC_ASSERT (i.vertical_position.reference);
146                                 v_position = i.vertical_position.proportional.get();
147
148                                 if (lowest_proportional) {
149                                         /* Adjust line spacing */
150                                         v_position = ((v_position - lowest_proportional.get()) * content()->line_spacing()) + lowest_proportional.get();
151                                 }
152
153                                 switch (i.vertical_position.reference.get()) {
154                                 case sub::TOP_OF_SCREEN:
155                                         v_align = dcp::VALIGN_TOP;
156                                         break;
157                                 case sub::VERTICAL_CENTRE_OF_SCREEN:
158                                         v_align = dcp::VALIGN_CENTER;
159                                         break;
160                                 case sub::BOTTOM_OF_SCREEN:
161                                         v_align = dcp::VALIGN_BOTTOM;
162                                         break;
163                                 default:
164                                         v_align = dcp::VALIGN_TOP;
165                                         break;
166                                 }
167                         }
168
169                         dcp::HAlign h_align;
170                         switch (i.horizontal_position.reference) {
171                         case sub::LEFT_OF_SCREEN:
172                                 h_align = dcp::HALIGN_LEFT;
173                                 break;
174                         case sub::HORIZONTAL_CENTRE_OF_SCREEN:
175                                 h_align = dcp::HALIGN_CENTER;
176                                 break;
177                         case sub::RIGHT_OF_SCREEN:
178                                 h_align = dcp::HALIGN_RIGHT;
179                                 break;
180                         default:
181                                 h_align = dcp::HALIGN_CENTER;
182                                 break;
183                         }
184
185                         /* The idea here (rightly or wrongly) is that we set the appearance based on the
186                            values in the libsub objects, and these are overridden with values from the
187                            content by the other emit_text_start() above.
188                         */
189
190                         out.push_back (
191                                 dcp::SubtitleString (
192                                         string(TEXT_FONT_ID),
193                                         j.italic,
194                                         j.bold,
195                                         j.underline,
196                                         j.colour.dcp(),
197                                         j.font_size.points (72 * 11),
198                                         1.0,
199                                         dcp::Time (from.seconds(), 1000),
200                                         /* XXX: hmm; this is a bit ugly (we don't know the to time yet) */
201                                         dcp::Time (),
202                                         i.horizontal_position.proportional,
203                                         h_align,
204                                         v_position,
205                                         v_align,
206                                         dcp::DIRECTION_LTR,
207                                         j.text,
208                                         dcp::NONE,
209                                         j.effect_colour.get_value_or(sub::Colour(0, 0, 0)).dcp(),
210                                         /* Hack: we should use subtitle.fade_up and subtitle.fade_down here
211                                            but the times of these often don't have a frame rate associated
212                                            with them so the sub::Time won't convert them to milliseconds without
213                                            throwing an exception.  Since only DCP subs fill those in (and we don't
214                                            use libsub for DCP subs) we can cheat by just putting 0 in here.
215                                         */
216                                         dcp::Time (),
217                                         dcp::Time ()
218                                         )
219                                 );
220                 }
221         }
222
223         emit_text_start (from, out);
224 }
225
226 void
227 SubtitleDecoder::emit_stop (ContentTime to)
228 {
229         Stop (to);
230 }
231
232 void
233 SubtitleDecoder::emit_text (ContentTimePeriod period, list<dcp::SubtitleString> s)
234 {
235         emit_text_start (period.from, s);
236         emit_stop (period.to);
237 }
238
239 void
240 SubtitleDecoder::emit_text (ContentTimePeriod period, sub::Subtitle const & s)
241 {
242         emit_text_start (period.from, s);
243         emit_stop (period.to);
244 }
245
246 void
247 SubtitleDecoder::seek ()
248 {
249         _position = ContentTime ();
250 }