diff --git a/src/SubtitleOctopus.cpp b/src/SubtitleOctopus.cpp index a9f47d0d..9fbc98e9 100644 --- a/src/SubtitleOctopus.cpp +++ b/src/SubtitleOctopus.cpp @@ -18,6 +18,7 @@ #endif int log_level = 3; +int *is_event_animated; typedef struct { void *buffer; @@ -62,6 +63,7 @@ void buffer_init(buffer_t *buf) { void buffer_free(buffer_t *buf) { free(buf->buffer); + buffer_init(buf); } void msg_callback(int level, const char *fmt, va_list va, void *data) { @@ -102,20 +104,96 @@ double libassjs_find_next_event_start(double tm) { return closest / 1000.0; } -int _is_event_complex(ASS_Event *event) { +static int _is_move_tag_animated(char *begin, char *end) { + int params[6]; + int count = 0, value = 0, num_digits = 0; + for (; begin < end; begin++) { + switch (*begin) { + case ' ': // fallthrough + case '\t': + break; + case ',': + params[count] = value; + count++; + value = 0; + num_digits = 0; + break; + default: { + int digit = *begin - '0'; + if (digit < 0 || digit > 9) return 0; // invalid move + value = value * 10 + digit; + num_digits++; + break; + } + } + } + if (num_digits > 0) { + params[count] = value; + count++; + } + if (count < 4) return 0; // invalid move + + // move is animated if (x1,y1) != (x2,y2) + return params[0] != params[2] || params[1] != params[3]; +} + +static int _is_animated_tag(char *begin, char *end) { + // strip whitespaces around the tag + while (begin < end && (*begin == ' ' || *begin == '\t')) begin++; + while (end > begin && (end[-1] == ' ' || end[-1] == '\t')) end--; + + int length = end - begin; + if (length < 3 || *begin != '\\') return 0; // too short to be animated or not a command + + switch (begin[1]) { + case 'k': // fallthrough + case 'K': + // \kXX is karaoke + return 1; + case 't': + // \t(...) is transition + return length >= 4 && begin[2] == '(' && end[-1] == ')'; + case 'm': + if (length >=7 && end[-1] == ')' && strcmp(begin, "\\move(") == 0) { + return _is_move_tag_animated(begin + 6, end - 1); + } + break; + case 'f': + // \fad() or \fade() are fades + return (length >= 7 && end[-1] == ')' && + (strcmp(begin, "\\fad(") == 0 || strcmp(begin, "\\fade(") == 0)); + } + + return 0; +} + +static int _is_event_animated(ASS_Event *event) { // event is complex if it's animated in any way, // either by having non-empty Effect or // by having tags (enclosed in '{}' in Text) if (event->Effect && event->Effect[0] != '\0') return 1; int escaped = 0; + char *tagStart = NULL; for (char *p = event->Text; *p != '\0'; p++) { switch (*p) { case '\\': escaped = !escaped; break; case '{': - if (escaped) return 1; + if (!escaped && tagStart == NULL) tagStart = p + 1; + break; + case '}': + if (!escaped && tagStart != NULL) { + if (_is_animated_tag(tagStart, p)) return 1; + tagStart = NULL; + } + break; + case ';': + if (tagStart != NULL) { + if (_is_animated_tag(tagStart, p)) return 1; + } + tagStart = p + 1; break; } } @@ -123,10 +201,18 @@ int _is_event_complex(ASS_Event *event) { return 0; } -int libassjs_find_event_stop_times(double tm, double *eventFinish, double *emptyFinish) { +static void detect_animated_events() { + ASS_Event *cur = track->events; + int *animated = is_animated_events; + for (int i = 0; i < track->n_events; i++, cur++, animated++) { + *animated = _is_event_animated(cur); + } +} + +void libassjs_find_event_stop_times(double tm, double *eventFinish, double *emptyFinish, int *is_animated) { if (!track || track->n_events == 0) { *eventFinish = *emptyFinish = -1; - return 0; + return; } ASS_Event *cur = track->events; @@ -146,12 +232,13 @@ int libassjs_find_event_stop_times(double tm, double *eventFinish, double *empty if (finish > maxFinish) { maxFinish = finish; } - if (!current_animated && _is_event_complex(cur)) current_animated = 1; + if (!current_animated) current_animated = is_event_animated[i]; } } else if (start < minStart || minStart == -1) { minStart = start; } } + *is_animated = current_animated; if (minFinish != -1) { // some event is going on, so we need to re-draw either when it stops @@ -169,8 +256,6 @@ int libassjs_find_event_stop_times(double tm, double *eventFinish, double *empty // there's no empty space after eventFinish happens *emptyFinish = *eventFinish; } - - return current_animated; } class SubtitleOctopus { @@ -216,6 +301,7 @@ class SubtitleOctopus { reloadFonts(); buffer_init(&m_blend); + is_event_animated = NULL; } /* TRACK */ @@ -226,6 +312,14 @@ class SubtitleOctopus { printf("Failed to start a track\n"); exit(4); } + + free(is_event_animated); + is_event_animated = (int*)malloc(sizeof(int) * track->n_events); + if (is_event_animated == NULL) { + printf("cannot parse animated events\n"); + exit(5); + } + detect_animated_events(); } void createTrackMem(char *buf, unsigned long bufsize) { @@ -242,6 +336,8 @@ class SubtitleOctopus { ass_free_track(track); track = NULL; } + free(is_event_animated); + is_event_animated = NULL; } /* TRACK */ @@ -262,6 +358,8 @@ class SubtitleOctopus { ass_renderer_done(ass_renderer); ass_library_done(ass_library); buffer_free(&m_blend); + free(is_event_animated); + is_event_animated = NULL; } void reloadLibrary() { quitLibrary(); diff --git a/src/post-worker.js b/src/post-worker.js index a8b4c657..7428450a 100644 --- a/src/post-worker.js +++ b/src/post-worker.js @@ -251,9 +251,10 @@ self.oneshotRender = function (lastRenderedTime, renderNow, iteration) { var eventFinish = -1.0, emptyFinish = -1.0, animated = false; var rendered = {}; if (eventStart >= 0) { - animated = self._find_event_stop_times(eventStart, self.eventFinish, self.emptyFinish) != 0; + self._find_event_stop_times(eventStart, self.eventFinish, self.emptyFinish, self.isAnimated); eventFinish = Module.getValue(self.eventFinish, 'double'); emptyFinish = Module.getValue(self.emptyFinish, 'double'); + animated = Module.getValue(self.isAnimated, 'i32') != 0; rendered = self.blendRenderTiming(eventStart, true); } diff --git a/src/pre-worker.js b/src/pre-worker.js index cb62aaf4..67322f01 100644 --- a/src/pre-worker.js +++ b/src/pre-worker.js @@ -93,7 +93,7 @@ Module['onRuntimeInitialized'] = function () { self.octObj = new Module.SubtitleOctopus(); self._find_next_event_start = Module['cwrap']('libassjs_find_next_event_start', 'number', ['number']); - self._find_event_stop_times = Module['cwrap']('libassjs_find_event_stop_times', null, ['number', 'number', 'number']); + self._find_event_stop_times = Module['cwrap']('libassjs_find_event_stop_times', null, ['number', 'number', 'number', 'number']); self.changed = Module._malloc(4); self.blendTime = Module._malloc(8); @@ -104,6 +104,7 @@ Module['onRuntimeInitialized'] = function () { self.eventFinish = Module._malloc(8); self.emptyFinish = Module._malloc(8); + self.isAnimated = Module._malloc(4); self.octObj.initLibrary(screen.width, screen.height); self.octObj.createTrack("/sub.ass"); diff --git a/src/subtitles-octopus.js b/src/subtitles-octopus.js index 120bd01a..239a48a0 100644 --- a/src/subtitles-octopus.js +++ b/src/subtitles-octopus.js @@ -318,7 +318,6 @@ var SubtitlesOctopus = function (options) { if (size <= self.renderAhead) { lastRendered = currentTime - (renderNow ? 0 : 0.001); - console.info('requesting new frame because current prerender size is less than limit (start=' + lastRendered + ')'); if (!self.oneshotState.renderRequested) { self.oneshotState.renderRequested = true; self.worker.postMessage({ @@ -331,8 +330,6 @@ var SubtitlesOctopus = function (options) { console.info('worker busy, requesting to seek'); self.oneshotState.requestNextTimestamp = lastRendered; } - } else { - console.debug('not requesting new frame yet as prerender size is over limit'); } } @@ -389,14 +386,13 @@ var SubtitlesOctopus = function (options) { tryRequestOneshot(currentTime, true); } } else if (_cleanPastRendered(currentTime) && finishTime >= 0) { - console.debug('some prerendered frame retired, requesting new'); tryRequestOneshot(finishTime, animated); } } function resetRenderAheadCache() { if (self.renderAhead > 0) { - console.debug('resetting prerender cache'); + console.info('resetting prerender cache'); self.renderedItems = []; self.oneshotState.eventStart = null; self.oneshotState.iteration++; @@ -537,9 +533,11 @@ var SubtitlesOctopus = function (options) { return; } - console.info('oneshot received (start=' + - data.eventStart + ', empty=' + data.emptyFinish + - '), render: ' + Math.round(data.spentTime) + ' ms'); + if (self.debug) { + console.info('oneshot received (start=' + + data.eventStart + ', empty=' + data.emptyFinish + + '), render: ' + Math.round(data.spentTime) + ' ms'); + } self.oneshotState.renderRequested = false; if (Math.abs(data.lastRenderedTime - self.oneshotState.requestNextTimestamp) < 0.01) { self.oneshotState.requestNextTimestamp = -1; @@ -594,12 +592,12 @@ var SubtitlesOctopus = function (options) { }); if (self.oneshotState.requestNextTimestamp >= 0) { - console.debug("requesting out of order event at " + self.oneshotState.requestNextTimestamp); + // requesting an out of order event render tryRequestOneshot(self.oneshotState.requestNextTimestamp, true); } else if (data.eventStart < 0) { console.info('oneshot received "end of frames" event'); } else if (data.emptyFinish >= 0) { - console.debug("there's some more event to render, try requesting next event"); + // there's some more event to render, try requesting next event tryRequestOneshot(data.emptyFinish, data.animated); } else { console.info('there are no more events to prerender');