Skip to content

Commit 9892dbe

Browse files
authored
Merge pull request #26 from dmitrylyzo/fix-animated-events
Fix animation detection
2 parents b221855 + 2da990e commit 9892dbe

File tree

1 file changed

+96
-82
lines changed

1 file changed

+96
-82
lines changed

src/SubtitleOctopus.cpp

Lines changed: 96 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -158,115 +158,129 @@ class BoundingBox {
158158
}
159159
};
160160

161-
static int _is_move_tag_animated(char *begin, char *end) {
162-
int params[6];
163-
int count = 0, value = 0, num_digits = 0;
164-
for (; begin < end; begin++) {
165-
switch (*begin) {
166-
case ' ': // fallthrough
167-
case '\t':
168-
break;
169-
case ',':
170-
params[count] = value;
171-
count++;
172-
value = 0;
173-
num_digits = 0;
174-
break;
175-
default: {
176-
int digit = *begin - '0';
177-
if (digit < 0 || digit > 9) return 0; // invalid move
178-
value = value * 10 + digit;
179-
num_digits++;
180-
break;
181-
}
182-
}
183-
}
184-
if (num_digits > 0) {
185-
params[count] = value;
186-
count++;
187-
}
188-
if (count < 4) return 0; // invalid move
189-
190-
// move is animated if (x1,y1) != (x2,y2)
191-
return params[0] != params[2] || params[1] != params[3];
161+
/**
162+
* \brief Overwrite tag with whitespace to nullify its effect
163+
* Boundaries are inclusive at both ends.
164+
*/
165+
static void _remove_tag(char *begin, char *end) {
166+
if (end < begin)
167+
return;
168+
memset(begin, ' ', end - begin + 1);
192169
}
193170

194-
static int _is_animated_tag(char *begin, char *end) {
195-
// strip whitespaces around the tag
196-
while (begin < end && (*begin == ' ' || *begin == '\t')) begin++;
197-
while (end > begin && (end[-1] == ' ' || end[-1] == '\t')) end--;
198-
199-
int length = end - begin;
200-
if (length < 3 || *begin != '\\') return 0; // too short to be animated or not a command
201-
202-
switch (begin[1]) {
203-
case 'k': // fallthrough
171+
/**
172+
* \param begin point to the first character of the tag name (after backslash)
173+
* \param end last character that can be read; at least the name itself
174+
and the following character if any must be included
175+
* \return true if tag may cause animations, false if it will definitely not
176+
*/
177+
static bool _is_animated_tag(char *begin, char *end) {
178+
if (end <= begin)
179+
return false;
180+
181+
size_t length = end - begin + 1;
182+
183+
#define check_simple_tag(tag) (sizeof(tag)-1 < length && !strncmp(begin, tag, sizeof(tag)-1))
184+
#define check_complex_tag(tag) (check_simple_tag(tag) && (begin[sizeof(tag)-1] == '(' \
185+
|| begin[sizeof(tag)-1] == ' ' || begin[sizeof(tag)-1] == '\t'))
186+
switch (begin[0]) {
187+
case 'k': //-fallthrough
204188
case 'K':
205-
// \kXX is karaoke
206-
return 1;
189+
// Karaoke: k, kf, ko, K and kt ; no other valid ASS-tag starts with k/K
190+
return true;
207191
case 't':
208-
// \t(...) is transition
209-
return length >= 4 && begin[2] == '(' && end[-1] == ')';
192+
// Animated transform: no other valid tag begins with t
193+
// non-nested t-tags have to be complex tags even in single argument
194+
// form, but nested t-tags (which act like independent t-tags) are allowed to be
195+
// simple-tags without parentheses due to VSF-parsing quirk.
196+
// Since all valid simple t-tags require the existence of a complex t-tag, we only check for complex tags
197+
// to avoid false positives from invalid simple t-tags. This makes animation-dropping somewhat incorrect
198+
// but as animation detection remains accurate, we consider this to be "good enough"
199+
return check_complex_tag("t");
210200
case 'm':
211-
if (length >=7 && end[-1] == ')' && strcmp(begin, "\\move(") == 0) {
212-
return _is_move_tag_animated(begin + 6, end - 1);
213-
}
214-
break;
201+
// Movement: complex tag; again no other valid tag begins with m
202+
// but ensure it's complex just to be sure
203+
return check_complex_tag("move");
215204
case 'f':
216-
// \fad() or \fade() are fades
217-
return (length >= 7 && end[-1] == ')' &&
218-
(strcmp(begin, "\\fad(") == 0 || strcmp(begin, "\\fade(") == 0));
205+
// Fade: \fad and Fade (complex): \fade; both complex
206+
// there are several other valid tags beginning with f
207+
return check_complex_tag("fad") || check_complex_tag("fade");
219208
}
220209

221-
return 0;
210+
return false;
211+
#undef check_complex_tag
212+
#undef check_simple_tag
222213
}
223214

224-
static void _remove_tag(char *begin, char *end) {
225-
// overwrite the tag with whitespace so libass won't see it
226-
for (; begin < end; begin++) *begin = ' ';
215+
/**
216+
* \param start First character after { (optionally spaces can be dropped)
217+
* \param end Last character before } (optionally spaces can be dropped)
218+
* \param drop_animations If true animation tags will be discarded
219+
* \return true if after processing the event may contain animations
220+
(i.e. when dropping animations this is always false)
221+
*/
222+
static bool _is_block_animated(char *start, char *end, bool drop_animations)
223+
{
224+
char *tag_start = NULL; // points to beginning backslash
225+
for (char *p = start; p <= end; p++) {
226+
if (*p == '\\') {
227+
// It is safe to go one before and beyond unconditionally
228+
// because the text passed in must be surronded by { }
229+
if (tag_start && _is_animated_tag(tag_start + 1, p - 1)) {
230+
if (!drop_animations)
231+
return true;
232+
// For \t transforms this will assume the final state
233+
_remove_tag(tag_start, p - 1);
234+
}
235+
tag_start = p;
236+
}
237+
}
238+
239+
if (tag_start && _is_animated_tag(tag_start + 1, end)) {
240+
if (!drop_animations)
241+
return true;
242+
_remove_tag(tag_start, end);
243+
}
244+
245+
return false;
227246
}
228247

229-
static int _is_event_animated(ASS_Event *event, bool drop_animations) {
230-
// event is complex if it's animated in any way,
231-
// either by having non-empty Effect or
232-
// by having tags (enclosed in '{}' in Text)
248+
/**
249+
* \param event ASS event to be processed
250+
* \param drop_animations If true animation tags will be discarded
251+
* \return true if after processing the event may contain animations
252+
(i.e. when dropping animations this is always false)
253+
*/
254+
static bool _is_event_animated(ASS_Event *event, bool drop_animations) {
255+
// Event is animated if it has an Effect or animated override tags
233256
if (event->Effect && event->Effect[0] != '\0') {
234257
if (!drop_animations) return 1;
235258
event->Effect[0] = '\0';
236259
}
237260

238-
int escaped = 0;
239-
char *tagStart = NULL;
261+
// Search for override blocks
262+
// Only closed {...}-blocks are parsed by VSFilters and libass
263+
char *block_start = NULL; // points to opening {
240264
for (char *p = event->Text; *p != '\0'; p++) {
241265
switch (*p) {
242-
case '\\':
243-
escaped = !escaped;
244-
break;
245266
case '{':
246-
if (!escaped && tagStart == NULL) tagStart = p + 1;
267+
// Escaping the opening curly bracket to not start an override block is
268+
// a VSFilter-incompatible libass extension. But we only use libass, so...
269+
if (!block_start && (p == event->Text || *(p-1) != '\\'))
270+
block_start = p;
247271
break;
248272
case '}':
249-
if (!escaped && tagStart != NULL) {
250-
if (_is_animated_tag(tagStart, p)) {
251-
if (!drop_animations) return 1;
252-
_remove_tag(tagStart, p);
253-
}
254-
tagStart = NULL;
255-
}
273+
if (block_start && p - block_start > 2
274+
&& _is_block_animated(block_start + 1, p - 1, drop_animations))
275+
return true;
276+
block_start = NULL;
256277
break;
257-
case ';':
258-
if (tagStart != NULL) {
259-
if (_is_animated_tag(tagStart, p)) {
260-
if (!drop_animations) return 1;
261-
_remove_tag(tagStart, p + 1 /* +1 is because we want to drop ';' as well */);
262-
}
263-
}
264-
tagStart = p + 1;
278+
default:
265279
break;
266280
}
267281
}
268282

269-
return 0;
283+
return false;
270284
}
271285

272286
class SubtitleOctopus {

0 commit comments

Comments
 (0)