Skip to content

Commit bcdb656

Browse files
committed
Properly parse SRT subtitles in MKV files
Fixes #436.
1 parent e600e47 commit bcdb656

3 files changed

Lines changed: 153 additions & 145 deletions

File tree

src/mkv_wrap.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include "dialog_progress.h"
4141
#include "MatroskaParser.h"
4242
#include "options.h"
43+
#include "subtitle_format_srt.h"
4344

4445
#include <libaegisub/ass/time.h>
4546
#include <libaegisub/file_mapping.h>
@@ -123,6 +124,8 @@ static bool read_subtitles(agi::ProgressSink *ps, MatroskaFile *file, MkvStdIO *
123124

124125
std::vector<char> uncompBuf(cs ? 256 : 0);
125126

127+
SrtTagParser srtParser;
128+
126129
while (mkv_ReadFrame(file, 0, &rt, &startTime, &endTime, &filePos, &frameSize, &frameFlags) == 0) {
127130
if (ps->IsCancelled()) return true;
128131
if (frameSize == 0) continue;
@@ -179,7 +182,7 @@ static bool read_subtitles(agi::ProgressSink *ps, MatroskaFile *file, MkvStdIO *
179182
auto line = agi::format("Dialogue: 0,%s,%s,Default,,0,0,0,,%s"
180183
, subStart.GetAssFormatted()
181184
, subEnd.GetAssFormatted()
182-
, readBuf);
185+
, srtParser.ToAss(std::string(readBuf)));
183186
boost::replace_all(line, "\r\n", "\\N");
184187
boost::replace_all(line, "\r", "\\N");
185188
boost::replace_all(line, "\n", "\\N");

src/subtitle_format_srt.cpp

Lines changed: 128 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
#include <boost/algorithm/string/predicate.hpp>
4949
#include <boost/algorithm/string/replace.hpp>
5050
#include <boost/algorithm/string/trim.hpp>
51-
#include <boost/regex.hpp>
5251

5352
DEFINE_EXCEPTION(SRTParseError, SubtitleFormatParseError);
5453

@@ -119,169 +118,154 @@ struct ToggleTag {
119118
}
120119
};
121120

122-
class SrtTagParser {
123-
struct FontAttribs {
124-
std::string face;
125-
std::string size;
126-
std::string color;
127-
};
121+
std::string WriteSRTTime(agi::Time const& ts)
122+
{
123+
return ts.GetSrtFormatted();
124+
}
128125

129-
const boost::regex tag_matcher;
130-
const boost::regex attrib_matcher;
131-
const boost::regex is_quoted;
126+
}
132127

133-
public:
134-
SrtTagParser()
135-
: tag_matcher("^(.*?)<(/?b|/?i|/?u|/?s|/?font)([^>]*)>(.*)$", boost::regex::icase)
136-
, attrib_matcher(R"(^[[:space:]]+(face|size|color)=('[^']*'|"[^"]*"|[^[:space:]]+))", boost::regex::icase)
137-
, is_quoted(R"(^(['"]).*\1$)")
138-
{
139-
}
128+
SrtTagParser::SrtTagParser()
129+
: tag_matcher("^(.*?)<(/?b|/?i|/?u|/?s|/?font)([^>]*)>(.*)$", boost::regex::icase)
130+
, attrib_matcher(R"(^[[:space:]]+(face|size|color)=('[^']*'|"[^"]*"|[^[:space:]]+))", boost::regex::icase)
131+
, is_quoted(R"(^(['"]).*\1$)")
132+
{}
133+
134+
std::string SrtTagParser::ToAss(std::string srt) {
135+
ToggleTag bold('b');
136+
ToggleTag italic('i');
137+
ToggleTag underline('u');
138+
ToggleTag strikeout('s');
139+
std::vector<FontAttribs> font_stack;
140+
141+
std::string ass; // result to be built
140142

141-
std::string ToAss(std::string srt)
143+
while (!srt.empty())
142144
{
143-
ToggleTag bold('b');
144-
ToggleTag italic('i');
145-
ToggleTag underline('u');
146-
ToggleTag strikeout('s');
147-
std::vector<FontAttribs> font_stack;
145+
boost::smatch result;
146+
if (!regex_match(srt, result, tag_matcher))
147+
{
148+
// no more tags could be matched, end of string
149+
ass.append(srt);
150+
break;
151+
}
148152

149-
std::string ass; // result to be built
153+
// we found a tag, translate it
154+
std::string pre_text = result.str(1);
155+
std::string tag_name = result.str(2);
156+
std::string tag_attrs = result.str(3);
157+
std::string post_text = result.str(4);
150158

151-
while (!srt.empty())
159+
// the text before the tag goes through unchanged
160+
ass.append(pre_text);
161+
// the text after the tag is the input for next iteration
162+
srt = post_text;
163+
164+
boost::to_lower(tag_name);
165+
switch (type_from_name(tag_name))
152166
{
153-
boost::smatch result;
154-
if (!regex_match(srt, result, tag_matcher))
167+
case TagType::BOLD_OPEN: bold.Open(ass); break;
168+
case TagType::BOLD_CLOSE: bold.Close(ass); break;
169+
case TagType::ITALICS_OPEN: italic.Open(ass); break;
170+
case TagType::ITALICS_CLOSE: italic.Close(ass); break;
171+
case TagType::UNDERLINE_OPEN: underline.Open(ass); break;
172+
case TagType::UNDERLINE_CLOSE: underline.Close(ass); break;
173+
case TagType::STRIKEOUT_OPEN: strikeout.Open(ass); break;
174+
case TagType::STRIKEOUT_CLOSE: strikeout.Close(ass); break;
175+
case TagType::FONT_OPEN:
155176
{
156-
// no more tags could be matched, end of string
157-
ass.append(srt);
158-
break;
159-
}
160-
161-
// we found a tag, translate it
162-
std::string pre_text = result.str(1);
163-
std::string tag_name = result.str(2);
164-
std::string tag_attrs = result.str(3);
165-
std::string post_text = result.str(4);
177+
// new attributes to fill in
178+
FontAttribs new_attribs;
179+
FontAttribs old_attribs;
180+
// start out with any previous ones on stack
181+
if (font_stack.size() > 0)
182+
old_attribs = font_stack.back();
183+
new_attribs = old_attribs;
184+
// now find all attributes on this font tag
185+
boost::smatch result;
186+
while (regex_search(tag_attrs, result, attrib_matcher))
187+
{
188+
// get attribute name and values
189+
std::string attr_name = result.str(1);
190+
std::string attr_value = result.str(2);
191+
192+
// clean them
193+
boost::to_lower(attr_name);
194+
if (regex_match(attr_value, is_quoted))
195+
attr_value = attr_value.substr(1, attr_value.size() - 2);
196+
197+
// handle the attributes
198+
if (attr_name == "face")
199+
new_attribs.face = agi::format("{\\fn%s}", attr_value);
200+
else if (attr_name == "size")
201+
new_attribs.size = agi::format("{\\fs%s}", attr_value);
202+
else if (attr_name == "color")
203+
new_attribs.color = agi::format("{\\c%s}", agi::Color(attr_value).GetAssOverrideFormatted());
204+
205+
// remove this attribute to prepare for the next
206+
tag_attrs = result.suffix().str();
207+
}
166208

167-
// the text before the tag goes through unchanged
168-
ass.append(pre_text);
169-
// the text after the tag is the input for next iteration
170-
srt = post_text;
209+
// the attributes changed from old are then written out
210+
if (new_attribs.face != old_attribs.face)
211+
ass.append(new_attribs.face);
212+
if (new_attribs.size != old_attribs.size)
213+
ass.append(new_attribs.size);
214+
if (new_attribs.color != old_attribs.color)
215+
ass.append(new_attribs.color);
171216

172-
boost::to_lower(tag_name);
173-
switch (type_from_name(tag_name))
217+
// lastly dump the new attributes state onto the stack
218+
font_stack.push_back(new_attribs);
219+
}
220+
break;
221+
case TagType::FONT_CLOSE:
174222
{
175-
case TagType::BOLD_OPEN: bold.Open(ass); break;
176-
case TagType::BOLD_CLOSE: bold.Close(ass); break;
177-
case TagType::ITALICS_OPEN: italic.Open(ass); break;
178-
case TagType::ITALICS_CLOSE: italic.Close(ass); break;
179-
case TagType::UNDERLINE_OPEN: underline.Open(ass); break;
180-
case TagType::UNDERLINE_CLOSE: underline.Close(ass); break;
181-
case TagType::STRIKEOUT_OPEN: strikeout.Open(ass); break;
182-
case TagType::STRIKEOUT_CLOSE: strikeout.Close(ass); break;
183-
case TagType::FONT_OPEN:
223+
// this requires a font stack entry
224+
if (font_stack.empty())
225+
break;
226+
// get the current attribs
227+
FontAttribs cur_attribs = font_stack.back();
228+
// remove them from the stack
229+
font_stack.pop_back();
230+
// grab the old attributes if there are any
231+
FontAttribs old_attribs;
232+
if (font_stack.size() > 0)
233+
old_attribs = font_stack.back();
234+
// then restore the attributes to previous settings
235+
if (cur_attribs.face != old_attribs.face)
184236
{
185-
// new attributes to fill in
186-
FontAttribs new_attribs;
187-
FontAttribs old_attribs;
188-
// start out with any previous ones on stack
189-
if (font_stack.size() > 0)
190-
old_attribs = font_stack.back();
191-
new_attribs = old_attribs;
192-
// now find all attributes on this font tag
193-
boost::smatch result;
194-
while (regex_search(tag_attrs, result, attrib_matcher))
195-
{
196-
// get attribute name and values
197-
std::string attr_name = result.str(1);
198-
std::string attr_value = result.str(2);
199-
200-
// clean them
201-
boost::to_lower(attr_name);
202-
if (regex_match(attr_value, is_quoted))
203-
attr_value = attr_value.substr(1, attr_value.size() - 2);
204-
205-
// handle the attributes
206-
if (attr_name == "face")
207-
new_attribs.face = agi::format("{\\fn%s}", attr_value);
208-
else if (attr_name == "size")
209-
new_attribs.size = agi::format("{\\fs%s}", attr_value);
210-
else if (attr_name == "color")
211-
new_attribs.color = agi::format("{\\c%s}", agi::Color(attr_value).GetAssOverrideFormatted());
212-
213-
// remove this attribute to prepare for the next
214-
tag_attrs = result.suffix().str();
215-
}
216-
217-
// the attributes changed from old are then written out
218-
if (new_attribs.face != old_attribs.face)
219-
ass.append(new_attribs.face);
220-
if (new_attribs.size != old_attribs.size)
221-
ass.append(new_attribs.size);
222-
if (new_attribs.color != old_attribs.color)
223-
ass.append(new_attribs.color);
224-
225-
// lastly dump the new attributes state onto the stack
226-
font_stack.push_back(new_attribs);
237+
if (old_attribs.face.empty())
238+
ass.append("{\\fn}");
239+
else
240+
ass.append(old_attribs.face);
227241
}
228-
break;
229-
case TagType::FONT_CLOSE:
242+
if (cur_attribs.size != old_attribs.size)
230243
{
231-
// this requires a font stack entry
232-
if (font_stack.empty())
233-
break;
234-
// get the current attribs
235-
FontAttribs cur_attribs = font_stack.back();
236-
// remove them from the stack
237-
font_stack.pop_back();
238-
// grab the old attributes if there are any
239-
FontAttribs old_attribs;
240-
if (font_stack.size() > 0)
241-
old_attribs = font_stack.back();
242-
// then restore the attributes to previous settings
243-
if (cur_attribs.face != old_attribs.face)
244-
{
245-
if (old_attribs.face.empty())
246-
ass.append("{\\fn}");
247-
else
248-
ass.append(old_attribs.face);
249-
}
250-
if (cur_attribs.size != old_attribs.size)
251-
{
252-
if (old_attribs.size.empty())
253-
ass.append("{\\fs}");
254-
else
255-
ass.append(old_attribs.size);
256-
}
257-
if (cur_attribs.color != old_attribs.color)
258-
{
259-
if (old_attribs.color.empty())
260-
ass.append("{\\c}");
261-
else
262-
ass.append(old_attribs.color);
263-
}
244+
if (old_attribs.size.empty())
245+
ass.append("{\\fs}");
246+
else
247+
ass.append(old_attribs.size);
248+
}
249+
if (cur_attribs.color != old_attribs.color)
250+
{
251+
if (old_attribs.color.empty())
252+
ass.append("{\\c}");
253+
else
254+
ass.append(old_attribs.color);
264255
}
265-
break;
266-
default:
267-
// unknown tag, replicate it in the output
268-
ass.append("<").append(tag_name).append(tag_attrs).append(">");
269-
break;
270256
}
257+
break;
258+
default:
259+
// unknown tag, replicate it in the output
260+
ass.append("<").append(tag_name).append(tag_attrs).append(">");
261+
break;
271262
}
272-
273-
// make it a little prettier, join tag groups
274-
boost::replace_all(ass, "}{", "");
275-
276-
return ass;
277263
}
278-
};
279264

280-
std::string WriteSRTTime(agi::Time const& ts)
281-
{
282-
return ts.GetSrtFormatted();
283-
}
265+
// make it a little prettier, join tag groups
266+
boost::replace_all(ass, "}{", "");
284267

268+
return ass;
285269
}
286270

287271
SRTSubtitleFormat::SRTSubtitleFormat()

src/subtitle_format_srt.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,31 @@
3232
/// @ingroup subtitle_io
3333
///
3434

35+
#include <string>
36+
3537
#include "subtitle_format.h"
3638

39+
#include <boost/regex.hpp>
40+
3741
class AssDialogue;
3842

43+
class SrtTagParser {
44+
struct FontAttribs {
45+
std::string face;
46+
std::string size;
47+
std::string color;
48+
};
49+
50+
const boost::regex tag_matcher;
51+
const boost::regex attrib_matcher;
52+
const boost::regex is_quoted;
53+
54+
public:
55+
SrtTagParser();
56+
57+
std::string ToAss(std::string srt);
58+
};
59+
3960
class SRTSubtitleFormat final : public SubtitleFormat {
4061
std::string ConvertTags(const AssDialogue *diag) const;
4162
public:

0 commit comments

Comments
 (0)