2 Copyright (C) 2013-2017 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
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.
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.
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/>.
21 #include "subtitle_decoder.h"
22 #include "subtitle_content.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>
36 using boost::shared_ptr;
37 using boost::optional;
38 using boost::function;
40 SubtitleDecoder::SubtitleDecoder (
42 shared_ptr<const SubtitleContent> c,
46 : DecoderPart (parent, log)
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
61 SubtitleDecoder::emit_image_start (ContentTime from, shared_ptr<Image> image, dcpomatic::Rect<double> rect)
63 ImageStart (ContentImageSubtitle (from, image, rect));
67 SubtitleDecoder::emit_text_start (ContentTime from, list<dcp::SubtitleString> s)
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.)
74 boost::algorithm::replace_all (t, "<", "<");
75 boost::algorithm::replace_all (t, ">", ">");
78 /* Set any forced appearance */
79 if (content()->colour()) {
80 i.set_colour (*content()->colour());
82 if (content()->effect_colour()) {
83 i.set_effect_colour (*content()->effect_colour());
85 if (content()->effect()) {
86 i.set_effect (*content()->effect());
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));
92 TextStart (ContentTextSubtitle (from, s));
97 SubtitleDecoder::emit_text_start (ContentTime from, sub::Subtitle const & subtitle)
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();
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;
119 lowest_proportional = min (lowest_proportional.get(), i.vertical_position.proportional.get());
124 list<dcp::SubtitleString> out;
125 BOOST_FOREACH (sub::Line i, subtitle.lines) {
126 BOOST_FOREACH (sub::Block j, i.blocks) {
128 if (!j.font_size.specified()) {
129 /* Fallback default font size if no other has been specified */
130 j.font_size.set_points (48);
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.
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);
144 v_align = dcp::VALIGN_TOP;
146 DCPOMATIC_ASSERT (i.vertical_position.proportional);
147 DCPOMATIC_ASSERT (i.vertical_position.reference);
148 v_position = i.vertical_position.proportional.get();
150 if (lowest_proportional) {
151 /* Adjust line spacing */
152 v_position = ((v_position - lowest_proportional.get()) * content()->line_spacing()) + lowest_proportional.get();
155 switch (i.vertical_position.reference.get()) {
156 case sub::TOP_OF_SCREEN:
157 v_align = dcp::VALIGN_TOP;
159 case sub::VERTICAL_CENTRE_OF_SCREEN:
160 v_align = dcp::VALIGN_CENTER;
162 case sub::BOTTOM_OF_SCREEN:
163 v_align = dcp::VALIGN_BOTTOM;
166 v_align = dcp::VALIGN_TOP;
172 switch (i.horizontal_position.reference) {
173 case sub::LEFT_OF_SCREEN:
174 h_align = dcp::HALIGN_LEFT;
176 case sub::HORIZONTAL_CENTRE_OF_SCREEN:
177 h_align = dcp::HALIGN_CENTER;
179 case sub::RIGHT_OF_SCREEN:
180 h_align = dcp::HALIGN_RIGHT;
183 h_align = dcp::HALIGN_CENTER;
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.
193 dcp::SubtitleString (
194 string(TEXT_FONT_ID),
199 j.font_size.points (72 * 11),
201 dcp::Time (from.seconds(), 1000),
202 /* XXX: hmm; this is a bit ugly (we don't know the to time yet) */
204 i.horizontal_position.proportional,
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.
225 emit_text_start (from, out);
229 SubtitleDecoder::emit_stop (ContentTime to)
235 SubtitleDecoder::emit_text (ContentTimePeriod period, list<dcp::SubtitleString> s)
237 emit_text_start (period.from, s);
238 emit_stop (period.to);
242 SubtitleDecoder::emit_text (ContentTimePeriod period, sub::Subtitle const & s)
244 emit_text_start (period.from, s);
245 emit_stop (period.to);
249 SubtitleDecoder::seek ()
251 _position = ContentTime ();