@@ -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
272286class SubtitleOctopus {
0 commit comments