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 <boost/algorithm/string.hpp>
23 #include "subtitle_asset.h"
30 using std::stringstream;
31 using boost::shared_ptr;
32 using boost::lexical_cast;
33 using namespace libdcp;
35 SubtitleAsset::SubtitleAsset (string directory, string xml_file)
36 : Asset (directory, xml_file)
39 read_xml (path().string());
42 SubtitleAsset::SubtitleAsset (string directory, string movie_title, string language)
44 , _movie_title (movie_title)
46 , _language (language)
53 SubtitleAsset::read_xml (string xml_file)
55 shared_ptr<XMLFile> xml (new XMLFile (xml_file, "DCSubtitle"));
57 _uuid = xml->string_child ("SubtitleID");
58 _movie_title = xml->string_child ("MovieTitle");
59 _reel_number = xml->string_child ("ReelNumber");
60 _language = xml->string_child ("Language");
62 xml->ignore_child ("LoadFont");
64 list<shared_ptr<FontNode> > font_nodes = xml->type_children<FontNode> ("Font");
65 _load_font_nodes = xml->type_children<LoadFontNode> ("LoadFont");
67 /* Now make Subtitle objects to represent the raw XML nodes
71 ParseState parse_state;
72 examine_font_nodes (xml, font_nodes, parse_state);
76 SubtitleAsset::examine_font_nodes (
77 shared_ptr<XMLFile> xml,
78 list<shared_ptr<FontNode> > const & font_nodes,
79 ParseState& parse_state
82 for (list<shared_ptr<FontNode> >::const_iterator i = font_nodes.begin(); i != font_nodes.end(); ++i) {
84 parse_state.font_nodes.push_back (*i);
85 maybe_add_subtitle ((*i)->text, parse_state);
87 for (list<shared_ptr<SubtitleNode> >::iterator j = (*i)->subtitle_nodes.begin(); j != (*i)->subtitle_nodes.end(); ++j) {
88 parse_state.subtitle_nodes.push_back (*j);
89 examine_text_nodes (xml, (*j)->text_nodes, parse_state);
90 examine_font_nodes (xml, (*j)->font_nodes, parse_state);
91 parse_state.subtitle_nodes.pop_back ();
94 examine_font_nodes (xml, (*i)->font_nodes, parse_state);
95 examine_text_nodes (xml, (*i)->text_nodes, parse_state);
97 parse_state.font_nodes.pop_back ();
102 SubtitleAsset::examine_text_nodes (
103 shared_ptr<XMLFile> xml,
104 list<shared_ptr<TextNode> > const & text_nodes,
105 ParseState& parse_state
108 for (list<shared_ptr<TextNode> >::const_iterator i = text_nodes.begin(); i != text_nodes.end(); ++i) {
109 parse_state.text_nodes.push_back (*i);
110 maybe_add_subtitle ((*i)->text, parse_state);
111 examine_font_nodes (xml, (*i)->font_nodes, parse_state);
112 parse_state.text_nodes.pop_back ();
117 SubtitleAsset::maybe_add_subtitle (string text, ParseState const & parse_state)
119 if (empty_or_white_space (text)) {
123 if (parse_state.text_nodes.empty() || parse_state.subtitle_nodes.empty ()) {
127 assert (!parse_state.text_nodes.empty ());
128 assert (!parse_state.subtitle_nodes.empty ());
130 FontNode effective_font (parse_state.font_nodes);
131 TextNode effective_text (*parse_state.text_nodes.back ());
132 SubtitleNode effective_subtitle (*parse_state.subtitle_nodes.back ());
134 _subtitles.push_back (
135 shared_ptr<Subtitle> (
137 font_id_to_name (effective_font.id),
138 effective_font.italic.get(),
139 effective_font.color.get(),
141 effective_subtitle.in,
142 effective_subtitle.out,
143 effective_text.v_position,
144 effective_text.v_align,
146 effective_font.effect ? effective_font.effect.get() : NONE,
147 effective_font.effect_color.get(),
148 effective_subtitle.fade_up_time,
149 effective_subtitle.fade_down_time
155 FontNode::FontNode (xmlpp::Node const * node)
160 id = optional_string_attribute ("Id");
161 size = optional_int64_attribute ("Size");
162 italic = optional_bool_attribute ("Italic");
163 color = optional_color_attribute ("Color");
164 string const e = optional_string_attribute ("Effect");
166 effect = string_to_effect (e);
168 effect_color = optional_color_attribute ("EffectColor");
169 subtitle_nodes = type_children<SubtitleNode> ("Subtitle");
170 font_nodes = type_children<FontNode> ("Font");
171 text_nodes = type_children<TextNode> ("Text");
174 FontNode::FontNode (list<shared_ptr<FontNode> > const & font_nodes)
178 , effect_color ("FFFFFFFF")
180 for (list<shared_ptr<FontNode> >::const_iterator i = font_nodes.begin(); i != font_nodes.end(); ++i) {
181 if (!(*i)->id.empty ()) {
184 if ((*i)->size != 0) {
188 italic = (*i)->italic.get ();
191 color = (*i)->color.get ();
194 effect = (*i)->effect.get ();
196 if ((*i)->effect_color) {
197 effect_color = (*i)->effect_color.get ();
202 LoadFontNode::LoadFontNode (xmlpp::Node const * node)
205 id = string_attribute ("Id");
206 uri = string_attribute ("URI");
210 SubtitleNode::SubtitleNode (xmlpp::Node const * node)
213 in = time_attribute ("TimeIn");
214 out = time_attribute ("TimeOut");
215 font_nodes = type_children<FontNode> ("Font");
216 text_nodes = type_children<TextNode> ("Text");
217 fade_up_time = fade_time ("FadeUpTime");
218 fade_down_time = fade_time ("FadeDownTime");
222 SubtitleNode::fade_time (string name)
224 string const u = optional_string_attribute (name);
228 t = Time (0, 0, 0, 20);
229 } else if (u.find (":") != string::npos) {
232 t = Time (0, 0, 0, lexical_cast<int> (u));
235 if (t > Time (0, 0, 8, 0)) {
236 t = Time (0, 0, 8, 0);
242 TextNode::TextNode (xmlpp::Node const * node)
247 v_position = float_attribute ("VPosition");
248 string const v = optional_string_attribute ("VAlign");
250 v_align = string_to_valign (v);
253 font_nodes = type_children<FontNode> ("Font");
256 list<shared_ptr<Subtitle> >
257 SubtitleAsset::subtitles_at (Time t) const
259 list<shared_ptr<Subtitle> > s;
260 for (list<shared_ptr<Subtitle> >::const_iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
261 if ((*i)->in() <= t && t <= (*i)->out ()) {
270 SubtitleAsset::font_id_to_name (string id) const
272 list<shared_ptr<LoadFontNode> >::const_iterator i = _load_font_nodes.begin();
273 while (i != _load_font_nodes.end() && (*i)->id != id) {
277 if (i == _load_font_nodes.end ()) {
281 if ((*i)->uri == "arial.ttf") {
309 , _v_position (v_position)
313 , _effect_color (effect_color)
314 , _fade_up_time (fade_up_time)
315 , _fade_down_time (fade_down_time)
321 Subtitle::size_in_pixels (int screen_height) const
323 /* Size in the subtitle file is given in points as if the screen
324 height is 11 inches, so a 72pt font would be 1/11th of the screen
328 return _size * screen_height / (11 * 72);
332 libdcp::operator== (Subtitle const & a, Subtitle const & b)
335 a.font() == b.font() &&
336 a.italic() == b.italic() &&
337 a.color() == b.color() &&
338 a.size() == b.size() &&
340 a.out() == b.out() &&
341 a.v_position() == b.v_position() &&
342 a.v_align() == b.v_align() &&
343 a.text() == b.text() &&
344 a.effect() == b.effect() &&
345 a.effect_color() == b.effect_color() &&
346 a.fade_up_time() == b.fade_up_time() &&
347 a.fade_down_time() == b.fade_down_time()
352 libdcp::operator<< (ostream& s, Subtitle const & sub)
354 s << "\n`" << sub.text() << "' from " << sub.in() << " to " << sub.out() << ";\n"
355 << "fade up " << sub.fade_up_time() << ", fade down " << sub.fade_down_time() << ";\n"
356 << "font " << sub.font() << ", ";
364 s << ", size " << sub.size() << ", color " << sub.color() << ", vpos " << sub.v_position() << ", valign " << ((int) sub.v_align()) << ";\n"
365 << "effect " << ((int) sub.effect()) << ", effect color " << sub.effect_color();
371 SubtitleAsset::add (shared_ptr<Subtitle> s)
373 _subtitles.push_back (s);
378 SubtitleAsset::write_to_cpl (ostream& s) const
380 /* XXX: should EditRate, Duration and IntrinsicDuration be in here? */
382 s << " <MainSubtitle>\n"
383 << " <Id>urn:uuid:" << _uuid << "</Id>\n"
384 << " <AnnotationText>" << _file_name << "</AnnotationText>\n"
385 << " <EntryPoint>0</EntryPoint>\n"
386 << " </MainSubtitle>\n";
389 struct SubtitleSorter {
390 bool operator() (shared_ptr<Subtitle> a, shared_ptr<Subtitle> b) {
391 if (a->in() != b->in()) {
392 return a->in() < b->in();
394 return a->v_position() < b->v_position();
399 SubtitleAsset::write_xml () const
401 ofstream f (path().string().c_str());
406 SubtitleAsset::write_xml (ostream& s) const
408 s << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
409 << "<DCSubtitle Version=\"1.0\">\n"
410 << " <SubtitleID>" << _uuid << "</SubtitleID>\n"
411 << " <MovieTitle>" << _movie_title << "</MovieTitle>\n"
412 << " <ReelNumber>" << _reel_number << "</ReelNumber>\n"
413 << " <Language>" << _language << "</Language>\n";
415 if (_load_font_nodes.size() > 1) {
416 throw MiscError ("multiple LoadFont nodes not supported");
419 if (!_load_font_nodes.empty ()) {
420 s << " <LoadFont Id=\"" << _load_font_nodes.front()->id << "\" URI=\"" << _load_font_nodes.front()->uri << "\"/>\n";
423 list<shared_ptr<Subtitle> > sorted = _subtitles;
425 sorted.sort (SubtitleSorter ());
428 /* XXX: multiple fonts not supported */
429 /* XXX: script, underlined, weight not supported */
435 Effect effect = NONE;
440 Time last_fade_up_time;
441 Time last_fade_down_time;
443 for (list<shared_ptr<Subtitle> >::iterator i = sorted.begin(); i != sorted.end(); ++i) {
445 /* We will start a new <Font>...</Font> whenever some font property changes.
446 I suppose should really make an optimal hierarchy of <Font> tags, but
450 bool const font_changed = first ||
451 italic != (*i)->italic() ||
452 color != (*i)->color() ||
453 size != (*i)->size() ||
454 effect != (*i)->effect() ||
455 effect_color != (*i)->effect_color();
459 italic = (*i)->italic ();
460 a << "Italic=\"" << (italic ? "yes" : "no") << "\" ";
461 color = (*i)->color ();
462 a << "Color=\"" << color.to_argb_string() << "\" ";
463 size = (*i)->size ();
464 a << "Size=\"" << size << "\" ";
465 effect = (*i)->effect ();
466 a << "Effect=\"" << effect_to_string(effect) << "\" ";
467 effect_color = (*i)->effect_color ();
468 a << "EffectColor=\"" << effect_color.to_argb_string() << "\" ";
469 a << "Script=\"normal\" Underlined=\"no\" Weight=\"normal\"";
472 if (first || font_changed ||
473 (last_in != (*i)->in() ||
474 last_out != (*i)->out() ||
475 last_fade_up_time != (*i)->fade_up_time() ||
476 last_fade_down_time != (*i)->fade_down_time()
480 s << " </Subtitle>\n";
488 string id = "theFontId";
489 if (!_load_font_nodes.empty()) {
490 id = _load_font_nodes.front()->id;
493 s << " <Font Id=\"" << id << "\" " << a.str() << ">\n";
497 << "SpotNumber=\"" << spot_number++ << "\" "
498 << "TimeIn=\"" << (*i)->in().to_string() << "\" "
499 << "TimeOut=\"" << (*i)->out().to_string() << "\" "
500 << "FadeUpTime=\"" << (*i)->fade_up_time().to_ticks() << "\" "
501 << "FadeDownTime=\"" << (*i)->fade_down_time().to_ticks() << "\""
504 last_in = (*i)->in ();
505 last_out = (*i)->out ();
506 last_fade_up_time = (*i)->fade_up_time ();
507 last_fade_down_time = (*i)->fade_down_time ();
511 << "VAlign=\"" << valign_to_string ((*i)->v_align()) << "\" "
512 << "VPosition=\"" << (*i)->v_position() << "\""
513 << ">" << escape ((*i)->text()) << "</Text>\n";
518 s << " </Subtitle>\n";
520 s << "</DCSubtitle>\n";
523 /** XXX: Another reason why we should be writing with libxml++ */
525 SubtitleAsset::escape (string s) const
527 boost::replace_all (s, "&", "&");