s/use_template/take_settings_from/g
[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         /* We must escape < and > in strings, otherwise they might confuse our subtitle
70            renderer (which uses some HTML-esque markup to do bold/italic etc.)
71         */
72         BOOST_FOREACH (dcp::SubtitleString& i, s) {
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
79         TextStart (ContentTextSubtitle (from, s));
80         _position = from;
81 }
82
83 void
84 SubtitleDecoder::emit_text_start (ContentTime from, sub::Subtitle const & subtitle)
85 {
86         /* See if our next subtitle needs to be vertically placed on screen by us */
87         bool needs_placement = false;
88         optional<int> bottom_line;
89         BOOST_FOREACH (sub::Line i, subtitle.lines) {
90                 if (!i.vertical_position.reference || i.vertical_position.reference.get() == sub::TOP_OF_SUBTITLE) {
91                         needs_placement = true;
92                         DCPOMATIC_ASSERT (i.vertical_position.line);
93                         if (!bottom_line || bottom_line.get() < i.vertical_position.line.get()) {
94                                 bottom_line = i.vertical_position.line.get();
95                         }
96                 }
97         }
98
99         /* Find the lowest proportional position */
100         optional<float> lowest_proportional;
101         BOOST_FOREACH (sub::Line i, subtitle.lines) {
102                 if (i.vertical_position.proportional) {
103                         if (!lowest_proportional) {
104                                 lowest_proportional = i.vertical_position.proportional;
105                         } else {
106                                 lowest_proportional = min (lowest_proportional.get(), i.vertical_position.proportional.get());
107                         }
108                 }
109         }
110
111         list<dcp::SubtitleString> out;
112         BOOST_FOREACH (sub::Line i, subtitle.lines) {
113                 BOOST_FOREACH (sub::Block j, i.blocks) {
114
115                         if (!j.font_size.specified()) {
116                                 /* Fallback default font size if no other has been specified */
117                                 j.font_size.set_points (48);
118                         }
119
120                         float v_position;
121                         dcp::VAlign v_align;
122                         if (needs_placement) {
123                                 DCPOMATIC_ASSERT (i.vertical_position.line);
124                                 /* This 1.015 is an arbitrary value to lift the bottom sub off the bottom
125                                    of the screen a bit to a pleasing degree.
126                                 */
127                                 v_position = 1.015 -
128                                         (1 + bottom_line.get() - i.vertical_position.line.get())
129                                         * 1.2 * content()->line_spacing() * content()->y_scale() * j.font_size.proportional (72 * 11);
130
131                                 v_align = dcp::VALIGN_TOP;
132                         } else {
133                                 DCPOMATIC_ASSERT (i.vertical_position.proportional);
134                                 DCPOMATIC_ASSERT (i.vertical_position.reference);
135                                 v_position = i.vertical_position.proportional.get();
136
137                                 if (lowest_proportional) {
138                                         /* Adjust line spacing */
139                                         v_position = ((v_position - lowest_proportional.get()) * content()->line_spacing()) + lowest_proportional.get();
140                                 }
141
142                                 switch (i.vertical_position.reference.get()) {
143                                 case sub::TOP_OF_SCREEN:
144                                         v_align = dcp::VALIGN_TOP;
145                                         break;
146                                 case sub::VERTICAL_CENTRE_OF_SCREEN:
147                                         v_align = dcp::VALIGN_CENTER;
148                                         break;
149                                 case sub::BOTTOM_OF_SCREEN:
150                                         v_align = dcp::VALIGN_BOTTOM;
151                                         break;
152                                 default:
153                                         v_align = dcp::VALIGN_TOP;
154                                         break;
155                                 }
156                         }
157
158                         dcp::Effect effect = dcp::NONE;
159                         if (content()->outline()) {
160                                 effect = dcp::BORDER;
161                         } else if (content()->shadow()) {
162                                 effect = dcp::SHADOW;
163                         }
164
165                         dcp::HAlign h_align;
166                         switch (i.horizontal_position.reference) {
167                         case sub::LEFT_OF_SCREEN:
168                                 h_align = dcp::HALIGN_LEFT;
169                                 break;
170                         case sub::HORIZONTAL_CENTRE_OF_SCREEN:
171                                 h_align = dcp::HALIGN_CENTER;
172                                 break;
173                         case sub::RIGHT_OF_SCREEN:
174                                 h_align = dcp::HALIGN_RIGHT;
175                                 break;
176                         default:
177                                 h_align = dcp::HALIGN_CENTER;
178                                 break;
179                         }
180
181                         out.push_back (
182                                 dcp::SubtitleString (
183                                         string(TEXT_FONT_ID),
184                                         j.italic,
185                                         j.bold,
186                                         j.underline,
187                                         /* force the colour to whatever is configured */
188                                         content()->colour(),
189                                         j.font_size.points (72 * 11),
190                                         1.0,
191                                         dcp::Time (from.seconds(), 1000),
192                                         /* XXX: hmm; this is a bit ugly (we don't know the to time yet) */
193                                         dcp::Time (),
194                                         i.horizontal_position.proportional,
195                                         h_align,
196                                         v_position,
197                                         v_align,
198                                         dcp::DIRECTION_LTR,
199                                         j.text,
200                                         effect,
201                                         content()->effect_colour(),
202                                         dcp::Time (content()->fade_in().seconds(), 1000),
203                                         dcp::Time (content()->fade_out().seconds(), 1000)
204                                         )
205                                 );
206                 }
207         }
208
209         emit_text_start (from, out);
210 }
211
212 void
213 SubtitleDecoder::emit_stop (ContentTime to)
214 {
215         Stop (to);
216 }
217
218 void
219 SubtitleDecoder::emit_text (ContentTimePeriod period, list<dcp::SubtitleString> s)
220 {
221         emit_text_start (period.from, s);
222         emit_stop (period.to);
223 }
224
225 void
226 SubtitleDecoder::emit_text (ContentTimePeriod period, sub::Subtitle const & s)
227 {
228         emit_text_start (period.from, s);
229         emit_stop (period.to);
230 }
231
232 void
233 SubtitleDecoder::seek ()
234 {
235         _position = ContentTime ();
236 }