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 /* Force our configured appearance */
79 i.set_colour (content()->colour());
80 i.set_effect_colour (content()->effect_colour());
81 if (content()->outline()) {
82 i.set_effect (dcp::BORDER);
83 } else if (content()->shadow()) {
84 i.set_effect (dcp::SHADOW);
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));
90 TextStart (ContentTextSubtitle (from, s));
95 SubtitleDecoder::emit_text_start (ContentTime from, sub::Subtitle const & subtitle)
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();
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;
117 lowest_proportional = min (lowest_proportional.get(), i.vertical_position.proportional.get());
122 list<dcp::SubtitleString> out;
123 BOOST_FOREACH (sub::Line i, subtitle.lines) {
124 BOOST_FOREACH (sub::Block j, i.blocks) {
126 if (!j.font_size.specified()) {
127 /* Fallback default font size if no other has been specified */
128 j.font_size.set_points (48);
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.
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);
142 v_align = dcp::VALIGN_TOP;
144 DCPOMATIC_ASSERT (i.vertical_position.proportional);
145 DCPOMATIC_ASSERT (i.vertical_position.reference);
146 v_position = i.vertical_position.proportional.get();
148 if (lowest_proportional) {
149 /* Adjust line spacing */
150 v_position = ((v_position - lowest_proportional.get()) * content()->line_spacing()) + lowest_proportional.get();
153 switch (i.vertical_position.reference.get()) {
154 case sub::TOP_OF_SCREEN:
155 v_align = dcp::VALIGN_TOP;
157 case sub::VERTICAL_CENTRE_OF_SCREEN:
158 v_align = dcp::VALIGN_CENTER;
160 case sub::BOTTOM_OF_SCREEN:
161 v_align = dcp::VALIGN_BOTTOM;
164 v_align = dcp::VALIGN_TOP;
170 switch (i.horizontal_position.reference) {
171 case sub::LEFT_OF_SCREEN:
172 h_align = dcp::HALIGN_LEFT;
174 case sub::HORIZONTAL_CENTRE_OF_SCREEN:
175 h_align = dcp::HALIGN_CENTER;
177 case sub::RIGHT_OF_SCREEN:
178 h_align = dcp::HALIGN_RIGHT;
181 h_align = dcp::HALIGN_CENTER;
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.
191 dcp::SubtitleString (
192 string(TEXT_FONT_ID),
197 j.font_size.points (72 * 11),
199 dcp::Time (from.seconds(), 1000),
200 /* XXX: hmm; this is a bit ugly (we don't know the to time yet) */
202 i.horizontal_position.proportional,
209 j.effect_colour.get_value_or(sub::Colour(0, 0, 0)).dcp(),
210 dcp::Time (subtitle.fade_up.get_value_or(sub::Time()).all_as_seconds(), 1000),
211 dcp::Time (subtitle.fade_down.get_value_or(sub::Time()).all_as_seconds(), 1000)
217 emit_text_start (from, out);
221 SubtitleDecoder::emit_stop (ContentTime to)
227 SubtitleDecoder::emit_text (ContentTimePeriod period, list<dcp::SubtitleString> s)
229 emit_text_start (period.from, s);
230 emit_stop (period.to);
234 SubtitleDecoder::emit_text (ContentTimePeriod period, sub::Subtitle const & s)
236 emit_text_start (period.from, s);
237 emit_stop (period.to);
241 SubtitleDecoder::seek ()
243 _position = ContentTime ();