Allow <Font> nodes inside <Subtitle> nodes.
[libdcp.git] / src / subtitle_asset.cc
1 /*
2     Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <boost/lexical_cast.hpp>
21 #include "subtitle_asset.h"
22
23 using namespace std;
24 using namespace boost;
25 using namespace libdcp;
26
27 SubtitleAsset::SubtitleAsset (string directory, string xml)
28         : Asset (directory, xml)
29         , XMLFile (path().string(), "DCSubtitle")
30 {
31         _subtitle_id = string_node ("SubtitleID");
32         _movie_title = string_node ("MovieTitle");
33         _reel_number = int64_node ("ReelNumber");
34         _language = string_node ("Language");
35
36         ignore_node ("LoadFont");
37
38         list<shared_ptr<FontNode> > font_nodes = sub_nodes<FontNode> ("Font");
39         _load_font_nodes = sub_nodes<LoadFontNode> ("LoadFont");
40
41         /* Now make Subtitle objects to represent the raw XML nodes
42            in a sane way.
43         */
44
45         list<shared_ptr<FontNode> > current_font_nodes;
46         list<shared_ptr<SubtitleNode> > current_subtitle_nodes;
47         current_subtitle_nodes.push_back (shared_ptr<SubtitleNode> ());
48         examine_font_nodes (font_nodes, current_font_nodes, current_subtitle_nodes);
49 }
50
51 void
52 SubtitleAsset::examine_font_nodes (
53         list<shared_ptr<FontNode> > const & font_nodes,
54         list<shared_ptr<FontNode> >& current_font_nodes,
55         list<shared_ptr<SubtitleNode> >& current_subtitle_nodes
56         )
57 {
58         for (list<shared_ptr<FontNode> >::const_iterator i = font_nodes.begin(); i != font_nodes.end(); ++i) {
59                 
60                 current_font_nodes.push_back (*i);
61
62                 for (list<shared_ptr<SubtitleNode> >::iterator j = (*i)->subtitle_nodes.begin(); j != (*i)->subtitle_nodes.end(); ++j) {
63                         current_subtitle_nodes.push_back (*j);
64                         examine_text_nodes (*j, (*j)->text_nodes, current_font_nodes);
65                         examine_font_nodes ((*j)->font_nodes, current_font_nodes, current_subtitle_nodes);
66                         current_subtitle_nodes.pop_back ();
67                 }
68         
69                 examine_font_nodes ((*i)->font_nodes, current_font_nodes, current_subtitle_nodes);
70                 examine_text_nodes (current_subtitle_nodes.back (), (*i)->text_nodes, current_font_nodes);
71                 
72                 current_font_nodes.pop_back ();
73         }
74 }
75
76 void
77 SubtitleAsset::examine_text_nodes (
78         shared_ptr<SubtitleNode> subtitle_node,
79         list<shared_ptr<TextNode> > const & text_nodes,
80         list<shared_ptr<FontNode> >& current_font_nodes
81         )
82 {
83         for (list<shared_ptr<TextNode> >::const_iterator i = text_nodes.begin(); i != text_nodes.end(); ++i) {
84                 FontNode effective (current_font_nodes);
85                 _subtitles.push_back (
86                         shared_ptr<Subtitle> (
87                                 new Subtitle (
88                                         font_id_to_name (effective.id),
89                                         effective.italic.get(),
90                                         effective.color.get(),
91                                         effective.size,
92                                         subtitle_node->in,
93                                         subtitle_node->out,
94                                         (*i)->v_position,
95                                         (*i)->v_align,
96                                         (*i)->text,
97                                         effective.effect.get(),
98                                         effective.effect_color.get(),
99                                         (*i)->fade_up_time,
100                                         (*i)->fade_down_time
101                                         )
102                                 )
103                         );
104         }
105 }
106
107 FontNode::FontNode (xmlpp::Node const * node)
108         : XMLNode (node)
109 {
110         id = optional_string_attribute ("Id");
111         size = optional_int64_attribute ("Size");
112         italic = optional_bool_attribute ("Italic");
113         color = optional_color_attribute ("Color");
114         string const e = optional_string_attribute ("Effect");
115         if (e == "none") {
116                 effect = NONE;
117         } else if (e == "border") {
118                 effect = BORDER;
119         } else if (e == "shadow") {
120                 effect = SHADOW;
121         } else if (!e.empty ()) {
122                 throw DCPReadError ("unknown subtitle effect type");
123         }
124         effect_color = optional_color_attribute ("EffectColor");
125         subtitle_nodes = sub_nodes<SubtitleNode> ("Subtitle");
126         font_nodes = sub_nodes<FontNode> ("Font");
127         text_nodes = sub_nodes<TextNode> ("Text");
128 }
129
130 FontNode::FontNode (list<shared_ptr<FontNode> > const & font_nodes)
131         : size (0)
132         , italic (false)
133         , color ("FFFFFFFF")
134         , effect_color ("FFFFFFFF")
135 {
136         for (list<shared_ptr<FontNode> >::const_iterator i = font_nodes.begin(); i != font_nodes.end(); ++i) {
137                 if (!(*i)->id.empty ()) {
138                         id = (*i)->id;
139                 }
140                 if ((*i)->size != 0) {
141                         size = (*i)->size;
142                 }
143                 if ((*i)->italic) {
144                         italic = (*i)->italic.get ();
145                 }
146                 if ((*i)->color) {
147                         color = (*i)->color.get ();
148                 }
149                 if ((*i)->effect) {
150                         effect = (*i)->effect.get ();
151                 }
152                 if ((*i)->effect_color) {
153                         effect_color = (*i)->effect_color.get ();
154                 }
155         }
156 }
157
158 LoadFontNode::LoadFontNode (xmlpp::Node const * node)
159         : XMLNode (node)
160 {
161         id = string_attribute ("Id");
162         uri = string_attribute ("URI");
163 }
164         
165
166 SubtitleNode::SubtitleNode (xmlpp::Node const * node)
167         : XMLNode (node)
168 {
169         in = time_attribute ("TimeIn");
170         out = time_attribute ("TimeOut");
171         font_nodes = sub_nodes<FontNode> ("Font");
172         text_nodes = sub_nodes<TextNode> ("Text");
173 }
174
175 TextNode::TextNode (xmlpp::Node const * node)
176         : XMLNode (node)
177         , v_align (CENTER)
178 {
179         text = content ();
180         v_position = float_attribute ("VPosition");
181         string const v = optional_string_attribute ("VAlign");
182         if (v == "top") {
183                 v_align = TOP;
184         } else if (v == "center") {
185                 v_align = CENTER;
186         } else if (v == "bottom") {
187                 v_align = BOTTOM;
188         }
189
190         fade_up_time = fade_time ("FadeUpTime");
191         fade_down_time = fade_time ("FadeUpTime");
192 }
193
194
195 Time
196 TextNode::fade_time (string name)
197 {
198         string const u = optional_string_attribute (name);
199         Time t;
200         
201         if (u.empty ()) {
202                 t = Time (0, 0, 0, 20);
203         } else if (u.find (":") != string::npos) {
204                 t = Time (u);
205         } else {
206                 t = Time (0, 0, 0, lexical_cast<int> (u));
207         }
208
209         if (t > Time (0, 0, 8, 0)) {
210                 t = Time (0, 0, 8, 0);
211         }
212
213         return t;
214 }
215
216 list<shared_ptr<Subtitle> >
217 SubtitleAsset::subtitles_at (Time t) const
218 {
219         list<shared_ptr<Subtitle> > s;
220         for (list<shared_ptr<Subtitle> >::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
221                 if ((*i)->in() <= t && t <= (*i)->out ()) {
222                         s.push_back (*i);
223                 }
224         }
225
226         return s;
227 }
228
229 std::string
230 SubtitleAsset::font_id_to_name (string id) const
231 {
232         list<shared_ptr<LoadFontNode> >::const_iterator i = _load_font_nodes.begin();
233         while (i != _load_font_nodes.end() && (*i)->id != id) {
234                 ++i;
235         }
236
237         if (i == _load_font_nodes.end ()) {
238                 return "";
239         }
240
241         if ((*i)->uri == "arial.ttf") {
242                 return "Arial";
243         }
244
245         return "";
246 }
247
248 Subtitle::Subtitle (
249         string font,
250         bool italic,
251         Color color,
252         int size,
253         Time in,
254         Time out,
255         float v_position,
256         VAlign v_align,
257         string text,
258         Effect effect,
259         Color effect_color,
260         Time fade_up_time,
261         Time fade_down_time
262         )
263         : _font (font)
264         , _italic (italic)
265         , _color (color)
266         , _size (size)
267         , _in (in)
268         , _out (out)
269         , _v_position (v_position)
270         , _v_align (v_align)
271         , _text (text)
272         , _effect (effect)
273         , _effect_color (effect_color)
274         , _fade_up_time (fade_up_time)
275         , _fade_down_time (fade_down_time)
276 {
277
278 }
279
280 int
281 Subtitle::size_in_pixels (int screen_height) const
282 {
283         /* Size in the subtitle file is given in points as if the screen
284            height is 11 inches, so a 72pt font would be 1/11th of the screen
285            height.
286         */
287         
288         return _size * screen_height / (11 * 72);
289 }