2 Copyright (C) 2016 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.
20 #include "ssa_reader.h"
22 #include "sub_assert.h"
23 #include "raw_convert.h"
25 #include <boost/algorithm/string.hpp>
26 #include <boost/bind.hpp>
27 #include <boost/foreach.hpp>
33 using std::stringstream;
38 using boost::optional;
39 using boost::function;
40 using namespace boost::algorithm;
43 /** @param s Subtitle string encoded in UTF-8 */
44 SSAReader::SSAReader (string const & s)
47 this->read (boost::bind (&get_line_stringstream, &str));
50 /** @param f Subtitle file encoded in UTF-8 */
51 SSAReader::SSAReader (FILE* f)
53 this->read (boost::bind (&get_line_file, f));
61 , primary_colour (255, 255, 255)
66 Style (string format_line, string style_line)
68 , primary_colour (255, 255, 255)
73 split (keys, format_line, is_any_of (","));
75 split (style, style_line, is_any_of (","));
77 SUB_ASSERT (!keys.empty());
78 SUB_ASSERT (!style.empty());
79 SUB_ASSERT (keys.size() == style.size());
81 for (size_t i = 0; i < style.size(); ++i) {
84 if (keys[i] == "Name") {
86 } else if (keys[i] == "Fontname") {
88 } else if (keys[i] == "Fontsize") {
89 font_size = raw_convert<int> (style[i]);
90 } else if (keys[i] == "PrimaryColour") {
91 primary_colour = colour (raw_convert<int> (style[i]));
92 } else if (keys[i] == "BackColour") {
93 back_colour = colour (raw_convert<int> (style[i]));
94 } else if (keys[i] == "Bold") {
95 bold = style[i] == "-1";
96 } else if (keys[i] == "Italic") {
97 italic = style[i] == "-1";
98 } else if (keys[i] == "BorderStyle") {
99 if (style[i] == "1") {
107 optional<string> font_name;
109 Colour primary_colour;
110 /** outline colour */
111 optional<Colour> back_colour;
114 optional<Effect> effect;
117 Colour colour (int c) const
120 ((c & 0x0000ff) >> 0) / 255.0,
121 ((c & 0x00ff00) >> 8) / 255.0,
122 ((c & 0xff0000) >> 16) / 255.0
128 SSAReader::parse_time (string t) const
131 split (bits, t, is_any_of (":."));
132 SUB_ASSERT (bits.size() == 4);
133 return Time::from_hms (
134 raw_convert<int> (bits[0]),
135 raw_convert<int> (bits[1]),
136 raw_convert<int> (bits[2]),
137 raw_convert<int> (bits[3]) * 10
141 /** @param base RawSubtitle filled in with any required common values.
142 * @param line SSA line string.
143 * @return List of RawSubtitles to represent line with vertical reference TOP_OF_SUBTITLE.
146 SSAReader::parse_line (RawSubtitle base, string line)
154 list<RawSubtitle> subs;
155 RawSubtitle current = base;
158 current.vertical_position.line = 0;
160 current.vertical_position.lines = 32;
161 current.vertical_position.reference = TOP_OF_SUBTITLE;
163 for (size_t i = 0; i < line.length(); ++i) {
164 char const c = line[i];
169 } else if (c == '\\') {
171 } else if (c != '\r' && c != '\n') {
177 if (!current.text.empty ()) {
178 subs.push_back (current);
182 current.italic = true;
183 } else if (style == "i0") {
184 current.italic = false;
193 if ((c == 'n' || c == 'N') && !current.text.empty ()) {
194 subs.push_back (current);
196 current.vertical_position.line = current.vertical_position.line.get() + 1;
203 if (!current.text.empty ()) {
204 subs.push_back (current);
211 SSAReader::read (function<optional<string> ()> get_line)
219 map<string, Style> styles;
220 string style_format_line;
221 vector<string> event_format;
224 optional<string> line = get_line ();
230 remove_unicode_bom (line);
232 if (starts_with (*line, ";") || line->empty ()) {
236 if (starts_with (*line, "[")) {
237 /* Section heading */
238 if (line.get() == "[Script Info]") {
240 } else if (line.get() == "[V4 Styles]") {
242 } else if (line.get() == "[Events]") {
248 size_t const colon = line->find (":");
249 SUB_ASSERT (colon != string::npos);
250 SUB_ASSERT (line->length() > colon + 1);
251 string const type = line->substr (0, colon);
252 string const body = line->substr (colon + 2);
258 if (type == "Format") {
259 style_format_line = body;
260 } else if (type == "Style") {
261 SUB_ASSERT (!style_format_line.empty ());
262 Style s (style_format_line, body);
267 if (type == "Format") {
268 split (event_format, body, is_any_of (","));
269 BOOST_FOREACH (string& i, event_format) {
272 } else if (type == "Dialogue") {
273 SUB_ASSERT (!event_format.empty ());
274 vector<string> event;
275 split (event, body, is_any_of (","));
277 SUB_ASSERT (!event.empty());
278 SUB_ASSERT (event_format.size() == event.size());
282 for (size_t i = 0; i < event.size(); ++i) {
284 if (event_format[i] == "Start") {
285 sub.from = parse_time (event[i]);
286 } else if (event_format[i] == "End") {
287 sub.to = parse_time (event[i]);
288 } else if (event_format[i] == "Style") {
289 SUB_ASSERT (styles.find(event[i]) != styles.end());
290 Style style = styles[event[i]];
291 sub.font = style.font_name;
292 sub.font_size = FontSize::from_points (style.font_size);
293 sub.colour = style.primary_colour;
294 sub.effect_colour = style.back_colour;
295 sub.bold = style.bold;
296 sub.italic = style.italic;
297 sub.effect = style.effect;
300 sub.vertical_position.lines = 32;
301 sub.vertical_position.reference = TOP_OF_SUBTITLE;
302 sub.vertical_position.line = 0;
304 } else if (event_format[i] == "Text") {
305 BOOST_FOREACH (sub::RawSubtitle j, parse_line (sub, event[i])) {