More subs tests.
[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                                         subtitle_node->fade_up_time,
100                                         subtitle_node->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         fade_up_time = fade_time ("FadeUpTime");
174         fade_down_time = fade_time ("FadeDownTime");
175 }
176
177 Time
178 SubtitleNode::fade_time (string name)
179 {
180         string const u = optional_string_attribute (name);
181         Time t;
182         
183         if (u.empty ()) {
184                 t = Time (0, 0, 0, 20);
185         } else if (u.find (":") != string::npos) {
186                 t = Time (u);
187         } else {
188                 t = Time (0, 0, 0, lexical_cast<int> (u));
189         }
190
191         if (t > Time (0, 0, 8, 0)) {
192                 t = Time (0, 0, 8, 0);
193         }
194
195         return t;
196 }
197
198 TextNode::TextNode (xmlpp::Node const * node)
199         : XMLNode (node)
200         , v_align (CENTER)
201 {
202         text = content ();
203         v_position = float_attribute ("VPosition");
204         string const v = optional_string_attribute ("VAlign");
205         if (v == "top") {
206                 v_align = TOP;
207         } else if (v == "center") {
208                 v_align = CENTER;
209         } else if (v == "bottom") {
210                 v_align = BOTTOM;
211         }
212 }
213
214
215 list<shared_ptr<Subtitle> >
216 SubtitleAsset::subtitles_at (Time t) const
217 {
218         list<shared_ptr<Subtitle> > s;
219         for (list<shared_ptr<Subtitle> >::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
220                 if ((*i)->in() <= t && t <= (*i)->out ()) {
221                         s.push_back (*i);
222                 }
223         }
224
225         return s;
226 }
227
228 std::string
229 SubtitleAsset::font_id_to_name (string id) const
230 {
231         list<shared_ptr<LoadFontNode> >::const_iterator i = _load_font_nodes.begin();
232         while (i != _load_font_nodes.end() && (*i)->id != id) {
233                 ++i;
234         }
235
236         if (i == _load_font_nodes.end ()) {
237                 return "";
238         }
239
240         if ((*i)->uri == "arial.ttf") {
241                 return "Arial";
242         }
243
244         return "";
245 }
246
247 Subtitle::Subtitle (
248         string font,
249         bool italic,
250         Color color,
251         int size,
252         Time in,
253         Time out,
254         float v_position,
255         VAlign v_align,
256         string text,
257         Effect effect,
258         Color effect_color,
259         Time fade_up_time,
260         Time fade_down_time
261         )
262         : _font (font)
263         , _italic (italic)
264         , _color (color)
265         , _size (size)
266         , _in (in)
267         , _out (out)
268         , _v_position (v_position)
269         , _v_align (v_align)
270         , _text (text)
271         , _effect (effect)
272         , _effect_color (effect_color)
273         , _fade_up_time (fade_up_time)
274         , _fade_down_time (fade_down_time)
275 {
276
277 }
278
279 int
280 Subtitle::size_in_pixels (int screen_height) const
281 {
282         /* Size in the subtitle file is given in points as if the screen
283            height is 11 inches, so a 72pt font would be 1/11th of the screen
284            height.
285         */
286         
287         return _size * screen_height / (11 * 72);
288 }
289
290 bool
291 libdcp::operator== (Subtitle const & a, Subtitle const & b)
292 {
293         return (
294                 a.font() == b.font() &&
295                 a.italic() == b.italic() &&
296                 a.color() == b.color() &&
297                 a.size() == b.size() &&
298                 a.in() == b.in() &&
299                 a.out() == b.out() &&
300                 a.v_position() == b.v_position() &&
301                 a.v_align() == b.v_align() &&
302                 a.text() == b.text() &&
303                 a.effect() == b.effect() &&
304                 a.effect_color() == b.effect_color() &&
305                 a.fade_up_time() == b.fade_up_time() &&
306                 a.fade_down_time() == b.fade_down_time()
307                 );
308 }
309
310 ostream&
311 libdcp::operator<< (ostream& s, Subtitle const & sub)
312 {
313         s << "\n`" << sub.text() << "' from " << sub.in() << " to " << sub.out() << ";\n"
314           << "fade up " << sub.fade_up_time() << ", fade down " << sub.fade_down_time() << ";\n"
315           << "font " << sub.font() << ", ";
316
317         if (sub.italic()) {
318                 s << "italic";
319         } else {
320                 s << "non-italic";
321         }
322         
323         s << ", size " << sub.size() << ", color " << sub.color() << ", vpos " << sub.v_position() << ", valign " << ((int) sub.v_align()) << ";\n"
324           << "effect " << ((int) sub.effect()) << ", effect color " << sub.effect_color();
325
326         return s;
327 }