diff options
Diffstat (limited to 'libass/ass.c')
-rw-r--r-- | libass/ass.c | 355 |
1 files changed, 296 insertions, 59 deletions
diff --git a/libass/ass.c b/libass/ass.c index 51fa201..cf854e3 100644 --- a/libass/ass.c +++ b/libass/ass.c @@ -228,9 +228,9 @@ static void set_default_style(ASS_Style *style) static long long string2timecode(ASS_Library *library, char *p) { - int h, m, s, ms; + int32_t h, m, s, ms; long long tm; - int res = sscanf(p, "%d:%d:%d.%d", &h, &m, &s, &ms); + int res = sscanf(p, "%" SCNd32 ":%" SCNd32 ":%" SCNd32 ".%" SCNd32, &h, &m, &s, &ms); if (res < 4) { ass_msg(library, MSGL_WARN, "Bad timestamp"); return 0; @@ -239,9 +239,138 @@ static long long string2timecode(ASS_Library *library, char *p) return tm; } -#define NEXT(str,token) \ - token = next_token(&str); \ +static int read_digits(char **str, unsigned base, uint32_t *res) +{ + char *p = *str; + char *start = p; + uint32_t val = 0; + + while (1) { + unsigned digit; + if (*p >= '0' && *p < FFMIN(base, 10) + '0') + digit = *p - '0'; + else if (*p >= 'a' && *p < base - 10 + 'a') + digit = *p - 'a' + 10; + else if (*p >= 'A' && *p < base - 10 + 'A') + digit = *p - 'A' + 10; + else + break; + val = val * base + digit; + ++p; + } + + *res = val; + *str = p; + return p != start; +} + +/** + * \brief Convert a string to an integer reduced modulo 2**32 + * Follows the rules for strtoul but reduces the number modulo 2**32 + * instead of saturating it to 2**32 - 1. + */ +static int mystrtou32_modulo(char **p, unsigned base, uint32_t *res) +{ + // This emulates scanf with %d or %x format as it works on + // Windows, because that's what is used by VSFilter. In practice, + // scanf works the same way on other platforms too, but + // the standard leaves its behavior on overflow undefined. + + // Unlike scanf and like strtoul, produce 0 for invalid inputs. + + char *start = *p; + int sign = 1; + + skip_spaces(p); + + if (**p == '+') + ++*p; + else if (**p == '-') + sign = -1, ++*p; + + if (base == 16 && !ass_strncasecmp(*p, "0x", 2)) + *p += 2; + + if (read_digits(p, base, res)) { + *res *= sign; + return 1; + } else { + *p = start; + return 0; + } +} + +static int32_t parse_int_header(char *str) +{ + uint32_t val = 0; + unsigned base; + + if (!ass_strncasecmp(str, "&h", 2) || !ass_strncasecmp(str, "0x", 2)) { + str += 2; + base = 16; + } else + base = 10; + + mystrtou32_modulo(&str, base, &val); + return val; +} + +static uint32_t parse_color_header(char *str) +{ + uint32_t color = parse_int_header(str); + return ass_bswap32(color); +} + +// Return a boolean value for a string +static char parse_bool(char *str) +{ + skip_spaces(&str); + return !ass_strncasecmp(str, "yes", 3) || strtol(str, NULL, 10) > 0; +} + +static int parse_ycbcr_matrix(char *str) +{ + skip_spaces(&str); + if (*str == '\0') + return YCBCR_DEFAULT; + + char *end = str + strlen(str); + rskip_spaces(&end, str); + + // Trim a local copy of the input that we know is safe to + // modify. The buffer is larger than any valid string + NUL, + // so we can simply chop off the rest of the input. + char buffer[16]; + size_t n = FFMIN(end - str, sizeof buffer - 1); + memcpy(buffer, str, n); + buffer[n] = '\0'; + + if (!ass_strcasecmp(buffer, "none")) + return YCBCR_NONE; + if (!ass_strcasecmp(buffer, "tv.601")) + return YCBCR_BT601_TV; + if (!ass_strcasecmp(buffer, "pc.601")) + return YCBCR_BT601_PC; + if (!ass_strcasecmp(buffer, "tv.709")) + return YCBCR_BT709_TV; + if (!ass_strcasecmp(buffer, "pc.709")) + return YCBCR_BT709_PC; + if (!ass_strcasecmp(buffer, "tv.240m")) + return YCBCR_SMPTE240M_TV; + if (!ass_strcasecmp(buffer, "pc.240m")) + return YCBCR_SMPTE240M_PC; + if (!ass_strcasecmp(buffer, "tv.fcc")) + return YCBCR_FCC_TV; + if (!ass_strcasecmp(buffer, "pc.fcc")) + return YCBCR_FCC_PC; + return YCBCR_UNKNOWN; +} + +#define NEXT(str,token,rtrim) \ + token = next_token(&str, rtrim); \ if (!token) break; +#define NEXTNAME(str,token) NEXT(str, token, true) +#define NEXTVAL(str,token) NEXT(str, token, false) #define ALIAS(alias,name) \ @@ -282,7 +411,7 @@ static long long string2timecode(ASS_Library *library, char *p) } #define COLORVAL(name) ANYVAL(name,parse_color_header) -#define INTVAL(name) ANYVAL(name,atoi) +#define INTVAL(name) ANYVAL(name,parse_int_header) #define FPVAL(name) ANYVAL(name,ass_atof) #define TIMEVAL(name) \ } else if (ass_strcasecmp(tname, #name) == 0) { \ @@ -290,7 +419,7 @@ static long long string2timecode(ASS_Library *library, char *p) #define STYLEVAL(name) \ } else if (ass_strcasecmp(tname, #name) == 0) { \ - target->name = lookup_style(track, token); + target->name = ass_lookup_style(track, token); // skip spaces in str beforehand, or trim leading spaces afterwards static inline void advance_token_pos(const char **const str, @@ -301,10 +430,9 @@ static inline void advance_token_pos(const char **const str, *end = *start; while (**end != '\0' && **end != ',') ++*end; *str = *end + (**end == ','); - rskip_spaces((char**)end, (char*)*start); } -static char *next_token(char **str) +static char *next_token(char **str, bool rtrim) { char *p; char *start; @@ -317,6 +445,8 @@ static char *next_token(char **str) (const char**)&start, (const char**)&p); + if (rtrim) + rskip_spaces(&p, start); *p = '\0'; return start; } @@ -343,26 +473,27 @@ static int process_event_tail(ASS_Track *track, ASS_Event *event, char *q = format; // format scanning pointer for (i = 0; i < n_ignored; ++i) { - NEXT(q, tname); + NEXTVAL(q, tname); } while (1) { - NEXT(q, tname); + NEXTNAME(q, tname); if (ass_strcasecmp(tname, "Text") == 0) { - char *last; event->Text = strdup(p); if (event->Text && *event->Text != 0) { - last = event->Text + strlen(event->Text) - 1; - if (last >= event->Text && *last == '\r') - *last = 0; + char *end = event->Text + strlen(event->Text); + while (end > event->Text && + (end[-1] == '\r' || end[-1] == '\t' || end[-1] == ' ')) + *--end = 0; } event->Duration -= event->Start; free(format); return event->Text ? 0 : -1; // "Text" is always the last } - NEXT(p, token); + NEXTVAL(p, token); ALIAS(End, Duration) // temporarily store end timecode in event->Duration + ALIAS(Actor, Name) // both variants are used in files PARSE_START INTVAL(Layer) STYLEVAL(Style) @@ -379,6 +510,16 @@ static int process_event_tail(ASS_Track *track, ASS_Event *event, return 1; } +static void set_style_alpha(ASS_Style *style, int32_t front_alpha, int32_t back_alpha) +{ + front_alpha = FFMAX(FFMIN(front_alpha, 0xFF), 0); + back_alpha = FFMAX(FFMIN(back_alpha, 0xFF), 0); + style->PrimaryColour = (style->PrimaryColour & 0xFFFFFF00) | front_alpha; + style->SecondaryColour = (style->SecondaryColour & 0xFFFFFF00) | front_alpha; + style->OutlineColour = (style->OutlineColour & 0xFFFFFF00) | front_alpha; + style->BackColour = (style->BackColour & 0xFFFFFF00) | back_alpha; +} + /** * \brief Parse command line style overrides (--ass-force-style option) * \param track track to apply overrides to @@ -402,13 +543,17 @@ void ass_process_force_style(ASS_Track *track) token = eq + 1; if (!ass_strcasecmp(*fs, "PlayResX")) - track->PlayResX = atoi(token); + track->PlayResX = parse_int_header(token); else if (!ass_strcasecmp(*fs, "PlayResY")) - track->PlayResY = atoi(token); + track->PlayResY = parse_int_header(token); + else if (!ass_strcasecmp(*fs, "LayoutResX")) + track->LayoutResX = parse_int_header(token); + else if (!ass_strcasecmp(*fs, "LayoutResY")) + track->LayoutResY = parse_int_header(token); else if (!ass_strcasecmp(*fs, "Timer")) track->Timer = ass_atof(token); else if (!ass_strcasecmp(*fs, "WrapStyle")) - track->WrapStyle = atoi(token); + track->WrapStyle = parse_int_header(token); else if (!ass_strcasecmp(*fs, "ScaledBorderAndShadow")) track->ScaledBorderAndShadow = parse_bool(token); else if (!ass_strcasecmp(*fs, "Kerning")) @@ -435,6 +580,9 @@ void ass_process_force_style(ASS_Track *track) COLORVAL(SecondaryColour) COLORVAL(OutlineColour) COLORVAL(BackColour) + } else if (ass_strcasecmp(tname, "AlphaLevel") == 0) { + int32_t alpha = parse_int_header(token); + set_style_alpha(target, alpha, alpha); FPVAL(FontSize) INTVAL(Bold) INTVAL(Italic) @@ -471,7 +619,6 @@ void ass_process_force_style(ASS_Track *track) */ static int process_style(ASS_Track *track, char *str) { - char *token; char *tname; char *p = str; @@ -511,21 +658,27 @@ static int process_style(ASS_Track *track, char *str) style->ScaleX = 100.; style->ScaleY = 100.; + int32_t ssa_alpha = 0; + while (1) { - NEXT(q, tname); - NEXT(p, token); + NEXTNAME(q, tname); + NEXTVAL(p, token); PARSE_START STARREDSTRVAL(Name) + if (!target->Name) + goto fail; STRVAL(FontName) COLORVAL(PrimaryColour) COLORVAL(SecondaryColour) COLORVAL(OutlineColour) // TertiaryColor COLORVAL(BackColour) - // SSA uses BackColour for both outline and shadow - // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway - if (track->track_type == TRACK_TYPE_SSA) - target->OutlineColour = target->BackColour; + // SSA uses BackColour for both outline and shadow + // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway + if (track->track_type == TRACK_TYPE_SSA) + target->OutlineColour = target->BackColour; + } else if (ass_strcasecmp(tname, "AlphaLevel") == 0) { + ssa_alpha = parse_int_header(token); FPVAL(FontSize) INTVAL(Bold) INTVAL(Italic) @@ -535,13 +688,13 @@ static int process_style(ASS_Track *track, char *str) FPVAL(Angle) INTVAL(BorderStyle) INTVAL(Alignment) - if (track->track_type == TRACK_TYPE_ASS) - target->Alignment = numpad2align(target->Alignment); - // VSFilter compatibility - else if (target->Alignment == 8) - target->Alignment = 3; - else if (target->Alignment == 4) - target->Alignment = 11; + if (track->track_type == TRACK_TYPE_ASS) + target->Alignment = numpad2align(target->Alignment); + // VSFilter compatibility + else if (target->Alignment == 8) + target->Alignment = 3; + else if (target->Alignment == 4) + target->Alignment = 11; INTVAL(MarginL) INTVAL(MarginR) INTVAL(MarginV) @@ -552,7 +705,13 @@ static int process_style(ASS_Track *track, char *str) FPVAL(Shadow) PARSE_END } + free(format); + format = NULL; + + // VSF compat: always set BackColour Alpha to 0x80 in SSA + if (track->track_type == TRACK_TYPE_SSA) + set_style_alpha(style, ssa_alpha, 0x80); style->ScaleX = FFMAX(style->ScaleX, 0.) / 100.; style->ScaleY = FFMAX(style->ScaleY, 0.) / 100.; style->Spacing = FFMAX(style->Spacing, 0.); @@ -562,23 +721,35 @@ static int process_style(ASS_Track *track, char *str) style->Italic = !!style->Italic; style->Underline = !!style->Underline; style->StrikeOut = !!style->StrikeOut; - if (!style->Name) + if (!style->Name || !*style->Name) { + free(style->Name); style->Name = strdup("Default"); + } if (!style->FontName) style->FontName = strdup("Arial"); - if (!style->Name || !style->FontName) { - ass_free_style(track, sid); - track->n_styles--; - return -1; - } + if (!style->Name || !style->FontName) + goto fail; if (strcmp(target->Name, "Default") == 0) track->default_style = sid; return 0; +fail: + free(format); + ass_free_style(track, sid); + track->n_styles--; + return -1; } static bool format_line_compare(const char *fmt1, const char *fmt2) { +#define TOKEN_ALIAS1(token, name, alias) \ + if (token ## _end - token ## _start == sizeof( #alias ) - 1 && \ + !strncmp(token ## _start, #alias, sizeof( #alias ) - 1)) { \ + token ## _start = #name; \ + token ## _end = token ## _start + sizeof( #name ) - 1; \ + } +#define TOKEN_ALIAS(name, alias) TOKEN_ALIAS1(tk1, name, alias) TOKEN_ALIAS1(tk2, name, alias) + while (true) { const char *tk1_start, *tk2_start; const char *tk1_end, *tk2_end; @@ -590,13 +761,19 @@ static bool format_line_compare(const char *fmt1, const char *fmt2) advance_token_pos(&fmt1, &tk1_start, &tk1_end); advance_token_pos(&fmt2, &tk2_start, &tk2_end); + rskip_spaces((char**)&tk1_end, (char*)tk1_start); + rskip_spaces((char**)&tk2_end, (char*)tk2_start); + TOKEN_ALIAS(Name, Actor) if ((tk1_end-tk1_start) != (tk2_end-tk2_start)) return false; if (ass_strncasecmp(tk1_start, tk2_start, tk1_end-tk1_start)) return false; } return *fmt1 == *fmt2; + +#undef TOKEN_ALIAS +#undef TOKEN_ALIAS1 } @@ -648,6 +825,26 @@ static int process_styles_line(ASS_Track *track, char *str) return ret; } +static inline void parse_script_type(ASS_Track *track, const char *str) +{ + // VSF compat: don't check for leading 'v' and + // parse value from the last non-space backwards + const char *p = str + strlen(str); + rskip_spaces((char **) &p, (char *) str); + size_t len = p - str; + if (len < 4) // rskip_spaces stops _at_ last space + return; + + int ver = TRACK_TYPE_SSA; + if (*(p-1) == '+') { + ver = TRACK_TYPE_ASS; + --len; --p; + } + + if (len >= 4 && !strncmp(p-4, "4.00", 4)) + track->track_type = ver; +} + static inline void check_duplicate_info_line(const ASS_Track *const track, const ScriptInfo si, const char *const name) @@ -664,16 +861,22 @@ static int process_info_line(ASS_Track *track, char *str) { if (!strncmp(str, "PlayResX:", 9)) { check_duplicate_info_line(track, SINFO_PLAYRESX, "PlayResX"); - track->PlayResX = atoi(str + 9); + track->PlayResX = parse_int_header(str + 9); } else if (!strncmp(str, "PlayResY:", 9)) { check_duplicate_info_line(track, SINFO_PLAYRESY, "PlayResY"); - track->PlayResY = atoi(str + 9); + track->PlayResY = parse_int_header(str + 9); + } else if (!strncmp(str, "LayoutResX:", 11)) { + check_duplicate_info_line(track, SINFO_LAYOUTRESX, "LayoutResX"); + track->LayoutResX = parse_int_header(str + 11); + } else if (!strncmp(str, "LayoutResY:", 11)) { + check_duplicate_info_line(track, SINFO_LAYOUTRESY, "LayoutResY"); + track->LayoutResY = parse_int_header(str + 11); } else if (!strncmp(str, "Timer:", 6)) { check_duplicate_info_line(track, SINFO_TIMER, "Timer"); track->Timer = ass_atof(str + 6); } else if (!strncmp(str, "WrapStyle:", 10)) { check_duplicate_info_line(track, SINFO_WRAPSTYLE, "WrapStyle"); - track->WrapStyle = atoi(str + 10); + track->WrapStyle = parse_int_header(str + 10); } else if (!strncmp(str, "ScaledBorderAndShadow:", 22)) { check_duplicate_info_line(track, SINFO_SCALEDBORDER, "ScaledBorderAndShadow"); @@ -690,6 +893,9 @@ static int process_info_line(ASS_Track *track, char *str) while (*p && ass_isspace(*p)) p++; free(track->Language); track->Language = strndup(p, 2); + } else if (!strncmp(str, "ScriptType:", 11)) { + check_duplicate_info_line(track, SINFO_SCRIPTTYPE, "ScriptType"); + parse_script_type(track, str + 11); } else if (!strncmp(str, "; Script generated by ", 22)) { if (!strncmp(str + 22,"FFmpeg/Lavc", 11)) track->parser_priv->header_flags |= GENBY_FFMPEG; @@ -750,9 +956,8 @@ static bool detect_legacy_conv_subs(ASS_Track *track) */ // GENBY_FFMPEG and exact ffmpeg headers required - // Note: If there's SINFO_SCRIPTTYPE in the future this needs to be updated if (track->parser_priv->header_flags - ^ (SINFO_PLAYRESX | SINFO_PLAYRESY | GENBY_FFMPEG)) + != (SINFO_SCRIPTTYPE | SINFO_PLAYRESX | SINFO_PLAYRESY | GENBY_FFMPEG)) return false; // Legacy ffmpeg only ever has one style @@ -809,7 +1014,15 @@ static int process_events_line(ASS_Track *track, char *str) return -1; event = track->events + eid; - return process_event_tail(track, event, str, 0); + int ret = process_event_tail(track, event, str, 0); + if (!ret) + return 0; + // If something went wrong, discard the useless Event + ass_free_event(track, eid); + track->n_events--; + return ret; + } else if (!strncmp(str, "Comment:", 8)) { + // Ignore Comments } else { ass_msg(track->library, MSGL_V, "Not understood: '%.30s'", str); } @@ -919,6 +1132,8 @@ static int process_fonts_line(ASS_Track *track, char *str) goto mem_fail; track->parser_priv->fontdata_size = new_size; } + if (!track->parser_priv->fontdata) + return 0; memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used, str, len); track->parser_priv->fontdata_used += len; @@ -1102,15 +1317,16 @@ void ass_process_chunk(ASS_Track *track, char *data, int size, p = str; do { - NEXT(p, token); + NEXTVAL(p, token); event->ReadOrder = atoi(token); if (check_readorder && check_duplicate_event(track, event->ReadOrder)) break; - NEXT(p, token); - event->Layer = atoi(token); + NEXTVAL(p, token); + event->Layer = parse_int_header(token); - process_event_tail(track, event, p, 3); + if (process_event_tail(track, event, p, 3)) + break; event->Start = timecode; event->Duration = duration; @@ -1225,17 +1441,18 @@ out: /** * \brief read file contents into newly allocated buffer * \param fname file name + * \param hint file name origin * \param bufsize out: file size * \return pointer to file contents. Caller is responsible for its deallocation. */ -char *read_file(ASS_Library *library, char *fname, size_t *bufsize) +char *ass_load_file(ASS_Library *library, const char *fname, FileNameSource hint, size_t *bufsize) { int res; long sz; long bytes_read; char *buf; - FILE *fp = fopen(fname, "rb"); + FILE *fp = ass_open_file(fname, hint); if (!fp) { ass_msg(library, MSGL_WARN, "ass_read_file(%s): fopen failed", fname); @@ -1360,7 +1577,7 @@ static char *read_file_recode(ASS_Library *library, char *fname, char *buf; size_t bufsize; - buf = read_file(library, fname, &bufsize); + buf = ass_load_file(library, fname, FN_EXTERNAL, &bufsize); if (!buf) return 0; #ifdef CONFIG_ICONV @@ -1416,7 +1633,7 @@ int ass_read_styles(ASS_Track *track, char *fname, char *codepage) ParserState old_state; size_t sz; - buf = read_file(track->library, fname, &sz); + buf = ass_load_file(track->library, fname, FN_EXTERNAL, &sz); if (!buf) return 1; #ifdef CONFIG_ICONV @@ -1522,17 +1739,37 @@ fail: int ass_track_set_feature(ASS_Track *track, ASS_Feature feature, int enable) { - switch (feature) { - case ASS_FEATURE_INCOMPATIBLE_EXTENSIONS: - //-fallthrough + if (feature >= sizeof(track->parser_priv->feature_flags) * CHAR_BIT || feature < 0) + return -1; + + // all supported non-meta features + static const uint32_t supported = #ifdef USE_FRIBIDI_EX_API - case ASS_FEATURE_BIDI_BRACKETS: - track->parser_priv->bidi_brackets = !!enable; + FEATURE_MASK(ASS_FEATURE_BIDI_BRACKETS) | #endif - return 0; +#ifdef CONFIG_UNIBREAK + FEATURE_MASK(ASS_FEATURE_WRAP_UNICODE) | +#endif + FEATURE_MASK(ASS_FEATURE_WHOLE_TEXT_LAYOUT) | + 0; + uint32_t requested = 0; + + switch (feature) { + case ASS_FEATURE_INCOMPATIBLE_EXTENSIONS: + requested = supported; + break; default: - return -1; + if (!(FEATURE_MASK(feature) & supported)) + return -1; + requested = FEATURE_MASK(feature); } + + if (enable) + track->parser_priv->feature_flags |= requested; + else + track->parser_priv->feature_flags &= ~requested; + + return 0; } /** |