2 Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
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.
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.
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.
21 #include <boost/lexical_cast.hpp>
22 #include "subtitle_asset.h"
26 using namespace boost;
27 using namespace libdcp;
29 SubtitleAsset::SubtitleAsset (string directory, string xml_file)
30 : Asset (directory, xml_file)
32 shared_ptr<XMLFile> xml (new XMLFile (path().string(), "DCSubtitle"));
34 _uuid = xml->string_child ("SubtitleID");
35 _movie_title = xml->string_child ("MovieTitle");
36 _reel_number = xml->string_child ("ReelNumber");
37 _language = xml->string_child ("Language");
39 xml->ignore_child ("LoadFont");
41 list<shared_ptr<FontNode> > font_nodes = xml->type_children<FontNode> ("Font");
42 _load_font_nodes = xml->type_children<LoadFontNode> ("LoadFont");
44 /* Now make Subtitle objects to represent the raw XML nodes
48 ParseState parse_state;
49 examine_font_nodes (xml, font_nodes, parse_state);
52 SubtitleAsset::SubtitleAsset (string directory, string movie_title, string language)
54 , _movie_title (movie_title)
56 , _language (language)
62 SubtitleAsset::examine_font_nodes (
63 shared_ptr<XMLFile> xml,
64 list<shared_ptr<FontNode> > const & font_nodes,
65 ParseState& parse_state
68 for (list<shared_ptr<FontNode> >::const_iterator i = font_nodes.begin(); i != font_nodes.end(); ++i) {
70 parse_state.font_nodes.push_back (*i);
71 maybe_add_subtitle ((*i)->text, parse_state);
73 for (list<shared_ptr<SubtitleNode> >::iterator j = (*i)->subtitle_nodes.begin(); j != (*i)->subtitle_nodes.end(); ++j) {
74 parse_state.subtitle_nodes.push_back (*j);
75 examine_text_nodes (xml, (*j)->text_nodes, parse_state);
76 examine_font_nodes (xml, (*j)->font_nodes, parse_state);
77 parse_state.subtitle_nodes.pop_back ();
80 examine_font_nodes (xml, (*i)->font_nodes, parse_state);
81 examine_text_nodes (xml, (*i)->text_nodes, parse_state);
83 parse_state.font_nodes.pop_back ();
88 SubtitleAsset::examine_text_nodes (
89 shared_ptr<XMLFile> xml,
90 list<shared_ptr<TextNode> > const & text_nodes,
91 ParseState& parse_state
94 for (list<shared_ptr<TextNode> >::const_iterator i = text_nodes.begin(); i != text_nodes.end(); ++i) {
95 parse_state.text_nodes.push_back (*i);
96 maybe_add_subtitle ((*i)->text, parse_state);
97 examine_font_nodes (xml, (*i)->font_nodes, parse_state);
98 parse_state.text_nodes.pop_back ();
103 SubtitleAsset::maybe_add_subtitle (string text, ParseState const & parse_state)
105 if (empty_or_white_space (text)) {
109 if (parse_state.text_nodes.empty() || parse_state.subtitle_nodes.empty ()) {
113 assert (!parse_state.text_nodes.empty ());
114 assert (!parse_state.subtitle_nodes.empty ());
116 FontNode effective_font (parse_state.font_nodes);
117 TextNode effective_text (*parse_state.text_nodes.back ());
118 SubtitleNode effective_subtitle (*parse_state.subtitle_nodes.back ());
120 _subtitles.push_back (
121 shared_ptr<Subtitle> (
123 font_id_to_name (effective_font.id),
124 effective_font.italic.get(),
125 effective_font.color.get(),
127 effective_subtitle.in,
128 effective_subtitle.out,
129 effective_text.v_position,
130 effective_text.v_align,
132 effective_font.effect.get(),
133 effective_font.effect_color.get(),
134 effective_subtitle.fade_up_time,
135 effective_subtitle.fade_down_time
141 FontNode::FontNode (xmlpp::Node const * node)
146 id = optional_string_attribute ("Id");
147 size = optional_int64_attribute ("Size");
148 italic = optional_bool_attribute ("Italic");
149 color = optional_color_attribute ("Color");
150 string const e = optional_string_attribute ("Effect");
152 effect = string_to_effect (e);
154 effect_color = optional_color_attribute ("EffectColor");
155 subtitle_nodes = type_children<SubtitleNode> ("Subtitle");
156 font_nodes = type_children<FontNode> ("Font");
157 text_nodes = type_children<TextNode> ("Text");
160 FontNode::FontNode (list<shared_ptr<FontNode> > const & font_nodes)
164 , effect_color ("FFFFFFFF")
166 for (list<shared_ptr<FontNode> >::const_iterator i = font_nodes.begin(); i != font_nodes.end(); ++i) {
167 if (!(*i)->id.empty ()) {
170 if ((*i)->size != 0) {
174 italic = (*i)->italic.get ();
177 color = (*i)->color.get ();
180 effect = (*i)->effect.get ();
182 if ((*i)->effect_color) {
183 effect_color = (*i)->effect_color.get ();
188 LoadFontNode::LoadFontNode (xmlpp::Node const * node)
191 id = string_attribute ("Id");
192 uri = string_attribute ("URI");
196 SubtitleNode::SubtitleNode (xmlpp::Node const * node)
199 in = time_attribute ("TimeIn");
200 out = time_attribute ("TimeOut");
201 font_nodes = type_children<FontNode> ("Font");
202 text_nodes = type_children<TextNode> ("Text");
203 fade_up_time = fade_time ("FadeUpTime");
204 fade_down_time = fade_time ("FadeDownTime");
208 SubtitleNode::fade_time (string name)
210 string const u = optional_string_attribute (name);
214 t = Time (0, 0, 0, 20);
215 } else if (u.find (":") != string::npos) {
218 t = Time (0, 0, 0, lexical_cast<int> (u));
221 if (t > Time (0, 0, 8, 0)) {
222 t = Time (0, 0, 8, 0);
228 TextNode::TextNode (xmlpp::Node const * node)
233 v_position = float_attribute ("VPosition");
234 string const v = optional_string_attribute ("VAlign");
236 v_align = string_to_valign (v);
239 font_nodes = type_children<FontNode> ("Font");
242 list<shared_ptr<Subtitle> >
243 SubtitleAsset::subtitles_at (Time t) const
245 list<shared_ptr<Subtitle> > s;
246 for (list<shared_ptr<Subtitle> >::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
247 if ((*i)->in() <= t && t <= (*i)->out ()) {
256 SubtitleAsset::font_id_to_name (string id) const
258 list<shared_ptr<LoadFontNode> >::const_iterator i = _load_font_nodes.begin();
259 while (i != _load_font_nodes.end() && (*i)->id != id) {
263 if (i == _load_font_nodes.end ()) {
267 if ((*i)->uri == "arial.ttf") {
295 , _v_position (v_position)
299 , _effect_color (effect_color)
300 , _fade_up_time (fade_up_time)
301 , _fade_down_time (fade_down_time)
307 Subtitle::size_in_pixels (int screen_height) const
309 /* Size in the subtitle file is given in points as if the screen
310 height is 11 inches, so a 72pt font would be 1/11th of the screen
314 return _size * screen_height / (11 * 72);
318 libdcp::operator== (Subtitle const & a, Subtitle const & b)
321 a.font() == b.font() &&
322 a.italic() == b.italic() &&
323 a.color() == b.color() &&
324 a.size() == b.size() &&
326 a.out() == b.out() &&
327 a.v_position() == b.v_position() &&
328 a.v_align() == b.v_align() &&
329 a.text() == b.text() &&
330 a.effect() == b.effect() &&
331 a.effect_color() == b.effect_color() &&
332 a.fade_up_time() == b.fade_up_time() &&
333 a.fade_down_time() == b.fade_down_time()
338 libdcp::operator<< (ostream& s, Subtitle const & sub)
340 s << "\n`" << sub.text() << "' from " << sub.in() << " to " << sub.out() << ";\n"
341 << "fade up " << sub.fade_up_time() << ", fade down " << sub.fade_down_time() << ";\n"
342 << "font " << sub.font() << ", ";
350 s << ", size " << sub.size() << ", color " << sub.color() << ", vpos " << sub.v_position() << ", valign " << ((int) sub.v_align()) << ";\n"
351 << "effect " << ((int) sub.effect()) << ", effect color " << sub.effect_color();
357 SubtitleAsset::add (shared_ptr<Subtitle> s)
359 _subtitles.push_back (s);
363 SubtitleAsset::write_to_cpl (ostream& s) const
365 /* XXX: should EditRate, Duration and IntrinsicDuration be in here? */
367 s << " <MainSubtitle>\n"
368 << " <Id>urn:uuid:" << _uuid << "</Id>\n"
369 << " <AnnotationText>" << _file_name << "</AnnotationText>\n"
370 << " <EntryPoint>0</EntryPoint>\n"
371 << " </MainSubtitle>\n";
374 struct SubtitleSorter {
375 bool operator() (shared_ptr<Subtitle> a, shared_ptr<Subtitle> b) {
376 return a->in() < b->in();
381 SubtitleAsset::write_xml ()
383 ofstream f (path().string().c_str());
385 f << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
386 << "<DCSubtitle Version=\"1.0\">\n"
387 << " <SubtitleID>" << _uuid << "</SubtitleID>\n"
388 << " <MovieTitle>" << _movie_title << "</MovieTitle>\n"
389 << " <ReelNumber>" << _reel_number << "</ReelNumber>\n"
390 << " <Language>" << _language << "</Language>\n"
391 << " <LoadFont Id=\"theFontId\" URI=\"arial.ttf\"/>";
393 _subtitles.sort (SubtitleSorter ());
395 /* XXX: multiple fonts not supported */
396 /* XXX: script, underlined, weight not supported */
402 Effect effect = NONE;
407 Time last_fade_up_time;
408 Time last_fade_down_time;
410 for (list<shared_ptr<Subtitle> >::iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
413 if (first || italic != (*i)->italic()) {
414 italic = (*i)->italic ();
415 a << "Italic=\"" << (italic ? "yes" : "no") << "\" ";
418 if (first || color != (*i)->color()) {
419 color = (*i)->color ();
420 a << "Color=\"" << color.to_argb_string() << "\" ";
423 if (size || size != (*i)->size()) {
424 size = (*i)->size ();
425 a << "Size=\"" << size << "\" ";
428 if (first || effect != (*i)->effect()) {
429 effect = (*i)->effect ();
430 a << "Effect=\"" << effect_to_string(effect) << "\" ";
433 if (first || effect_color != (*i)->effect_color()) {
434 effect_color = (*i)->effect_color ();
435 a << "EffectColor=\"" << effect_color.to_argb_string() << "\" ";
439 a << "Script=\"normal\" Underlined=\"no\" Weight=\"normal\">";
442 if (!a.str().empty()) {
446 f << " <Font Id=\"theFontId\" " << a << ">\n";
451 (last_in != (*i)->in() ||
452 last_out != (*i)->out() ||
453 last_fade_up_time != (*i)->fade_up_time() ||
454 last_fade_down_time != (*i)->fade_down_time()
458 f << " </Subtitle>\n";
462 << "SpotNumber=\"" << spot_number++ << "\" "
463 << "TimeIn=" << (*i)->in().to_string() << "\" "
464 << "TimeOut=\"" << (*i)->out().to_string() << "\" "
465 << "FadeUpTime=\"" << (*i)->fade_up_time().to_ticks() << "\" "
466 << "FadeDownTime=\"" << (*i)->fade_down_time().to_ticks() << "\" "
469 last_in = (*i)->in ();
470 last_out = (*i)->out ();
471 last_fade_up_time = (*i)->fade_up_time ();
472 last_fade_down_time = (*i)->fade_down_time ();
476 << "VAlign=\"" << valign_to_string ((*i)->v_align()) << "\" "
477 << "VPosition=\"" << (*i)->v_position() << "\" "
478 << ">" << (*i)->text() << "</Text>\n";
483 f << " </Subtitle>\n";