/* * Copyright (C) 2006 Evgeniy Stepanov * * This file is part of libass. * * libass is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * libass is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with libass; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "config.h" #include #include #include #include #include #include #include #include #include #ifdef CONFIG_ICONV #include #endif #include "ass.h" #include "ass_utils.h" #include "ass_library.h" typedef enum { PST_UNKNOWN = 0, PST_INFO, PST_STYLES, PST_EVENTS, PST_FONTS } ParserState; struct parser_priv { ParserState state; char *fontname; char *fontdata; int fontdata_size; int fontdata_used; }; #define ASS_STYLES_ALLOC 20 #define ASS_EVENTS_ALLOC 200 void ass_free_track(ASS_Track *track) { int i; if (track->parser_priv) { if (track->parser_priv->fontname) free(track->parser_priv->fontname); if (track->parser_priv->fontdata) free(track->parser_priv->fontdata); free(track->parser_priv); } if (track->style_format) free(track->style_format); if (track->event_format) free(track->event_format); if (track->styles) { for (i = 0; i < track->n_styles; ++i) ass_free_style(track, i); free(track->styles); } if (track->events) { for (i = 0; i < track->n_events; ++i) ass_free_event(track, i); free(track->events); } free(track->name); free(track); } /// \brief Allocate a new style struct /// \param track track /// \return style id int ass_alloc_style(ASS_Track *track) { int sid; assert(track->n_styles <= track->max_styles); if (track->n_styles == track->max_styles) { track->max_styles += ASS_STYLES_ALLOC; track->styles = (ASS_Style *) realloc(track->styles, sizeof(ASS_Style) * track->max_styles); } sid = track->n_styles++; memset(track->styles + sid, 0, sizeof(ASS_Style)); return sid; } /// \brief Allocate a new event struct /// \param track track /// \return event id int ass_alloc_event(ASS_Track *track) { int eid; assert(track->n_events <= track->max_events); if (track->n_events == track->max_events) { track->max_events += ASS_EVENTS_ALLOC; track->events = (ASS_Event *) realloc(track->events, sizeof(ASS_Event) * track->max_events); } eid = track->n_events++; memset(track->events + eid, 0, sizeof(ASS_Event)); return eid; } void ass_free_event(ASS_Track *track, int eid) { ASS_Event *event = track->events + eid; if (event->Name) free(event->Name); if (event->Effect) free(event->Effect); if (event->Text) free(event->Text); if (event->render_priv) free(event->render_priv); } void ass_free_style(ASS_Track *track, int sid) { ASS_Style *style = track->styles + sid; if (style->Name) free(style->Name); if (style->FontName) free(style->FontName); } // ============================================================================================== static void skip_spaces(char **str) { char *p = *str; while ((*p == ' ') || (*p == '\t')) ++p; *str = p; } static void rskip_spaces(char **str, char *limit) { char *p = *str; while ((p >= limit) && ((*p == ' ') || (*p == '\t'))) --p; *str = p; } /** * \brief find style by name * \param track track * \param name style name * \return index in track->styles * Returnes 0 if no styles found => expects at least 1 style. * Parsing code always adds "Default" style in the end. */ static int lookup_style(ASS_Track *track, char *name) { int i; if (*name == '*') ++name; // FIXME: what does '*' really mean ? for (i = track->n_styles - 1; i >= 0; --i) { // FIXME: mb strcasecmp ? if (strcmp(track->styles[i].Name, name) == 0) return i; } i = track->default_style; ass_msg(track->library, MSGL_WARN, "[%p]: Warning: no style named '%s' found, using '%s'", track, name, track->styles[i].Name); return i; // use the first style } static uint32_t string2color(ASS_Library *library, char *p) { uint32_t tmp; (void) strtocolor(library, &p, &tmp, 0); return tmp; } static long long string2timecode(ASS_Library *library, char *p) { unsigned h, m, s, ms; long long tm; int res = sscanf(p, "%1d:%2d:%2d.%2d", &h, &m, &s, &ms); if (res < 4) { ass_msg(library, MSGL_WARN, "Bad timestamp"); return 0; } tm = ((h * 60 + m) * 60 + s) * 1000 + ms * 10; return tm; } /** * \brief converts numpad-style align to align. */ static int numpad2align(int val) { int res, v; v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment if (v != 0) v = 3 - v; res = ((val - 1) % 3) + 1; // horizontal alignment res += v * 4; return res; } #define NEXT(str,token) \ token = next_token(&str); \ if (!token) break; #define ANYVAL(name,func) \ } else if (strcasecmp(tname, #name) == 0) { \ target->name = func(token); \ ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token); #define STRVAL(name) \ } else if (strcasecmp(tname, #name) == 0) { \ if (target->name != NULL) free(target->name); \ target->name = strdup(token); \ ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token); #define COLORVAL(name) \ } else if (strcasecmp(tname, #name) == 0) { \ target->name = string2color(track->library, token); \ ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token); #define INTVAL(name) ANYVAL(name,atoi) #define FPVAL(name) ANYVAL(name,atof) #define TIMEVAL(name) \ } else if (strcasecmp(tname, #name) == 0) { \ target->name = string2timecode(track->library, token); \ ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token); #define STYLEVAL(name) \ } else if (strcasecmp(tname, #name) == 0) { \ target->name = lookup_style(track, token); \ ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token); #define ALIAS(alias,name) \ if (strcasecmp(tname, #alias) == 0) {tname = #name;} static char *next_token(char **str) { char *p = *str; char *start; skip_spaces(&p); if (*p == '\0') { *str = p; return 0; } start = p; // start of the token for (; (*p != '\0') && (*p != ','); ++p) { } if (*p == '\0') { *str = p; // eos found, str will point to '\0' at exit } else { *p = '\0'; *str = p + 1; // ',' found, str will point to the next char (beginning of the next token) } --p; // end of current token rskip_spaces(&p, start); if (p < start) p = start; // empty token else ++p; // the first space character, or '\0' *p = '\0'; return start; } /** * \brief Parse the tail of Dialogue line * \param track track * \param event parsed data goes here * \param str string to parse, zero-terminated * \param n_ignored number of format options to skip at the beginning */ static int process_event_tail(ASS_Track *track, ASS_Event *event, char *str, int n_ignored) { char *token; char *tname; char *p = str; int i; ASS_Event *target = event; char *format = strdup(track->event_format); char *q = format; // format scanning pointer if (track->n_styles == 0) { // add "Default" style to the end // will be used if track does not contain a default style (or even does not contain styles at all) int sid = ass_alloc_style(track); track->styles[sid].Name = strdup("Default"); track->styles[sid].FontName = strdup("Arial"); } for (i = 0; i < n_ignored; ++i) { NEXT(q, tname); } while (1) { NEXT(q, tname); if (strcasecmp(tname, "Text") == 0) { char *last; event->Text = strdup(p); if (*event->Text != 0) { last = event->Text + strlen(event->Text) - 1; if (last >= event->Text && *last == '\r') *last = 0; } ass_msg(track->library, MSGL_DBG2, "Text = %s", event->Text); event->Duration -= event->Start; free(format); return 0; // "Text" is always the last } NEXT(p, token); ALIAS(End, Duration) // temporarily store end timecode in event->Duration if (0) { // cool ;) INTVAL(Layer) STYLEVAL(Style) STRVAL(Name) STRVAL(Effect) INTVAL(MarginL) INTVAL(MarginR) INTVAL(MarginV) TIMEVAL(Start) TIMEVAL(Duration) } } free(format); return 1; } /** * \brief Parse command line style overrides (--ass-force-style option) * \param track track to apply overrides to * The format for overrides is [StyleName.]Field=Value */ void ass_process_force_style(ASS_Track *track) { char **fs, *eq, *dt, *style, *tname, *token; ASS_Style *target; int sid; char **list = track->library->style_overrides; if (!list) return; for (fs = list; *fs; ++fs) { eq = strrchr(*fs, '='); if (!eq) continue; *eq = '\0'; token = eq + 1; if (!strcasecmp(*fs, "PlayResX")) track->PlayResX = atoi(token); else if (!strcasecmp(*fs, "PlayResY")) track->PlayResY = atoi(token); else if (!strcasecmp(*fs, "Timer")) track->Timer = atof(token); else if (!strcasecmp(*fs, "WrapStyle")) track->WrapStyle = atoi(token); else if (!strcasecmp(*fs, "ScaledBorderAndShadow")) track->ScaledBorderAndShadow = parse_bool(token); else if (!strcasecmp(*fs, "Kerning")) track->Kerning = parse_bool(token); dt = strrchr(*fs, '.'); if (dt) { *dt = '\0'; style = *fs; tname = dt + 1; } else { style = NULL; tname = *fs; } for (sid = 0; sid < track->n_styles; ++sid) { if (style == NULL || strcasecmp(track->styles[sid].Name, style) == 0) { target = track->styles + sid; if (0) { STRVAL(FontName) COLORVAL(PrimaryColour) COLORVAL(SecondaryColour) COLORVAL(OutlineColour) COLORVAL(BackColour) FPVAL(FontSize) INTVAL(Bold) INTVAL(Italic) INTVAL(Underline) INTVAL(StrikeOut) FPVAL(Spacing) INTVAL(Angle) INTVAL(BorderStyle) INTVAL(Alignment) INTVAL(MarginL) INTVAL(MarginR) INTVAL(MarginV) INTVAL(Encoding) FPVAL(ScaleX) FPVAL(ScaleY) FPVAL(Outline) FPVAL(Shadow) } } } *eq = '='; if (dt) *dt = '.'; } } /** * \brief Parse the Style line * \param track track * \param str string to parse, zero-terminated * Allocates a new style struct. */ static int process_style(ASS_Track *track, char *str) { char *token; char *tname; char *p = str; char *format; char *q; // format scanning pointer int sid; ASS_Style *style; ASS_Style *target; if (!track->style_format) { // no style format header // probably an ancient script version if (track->track_type == TRACK_TYPE_SSA) track->style_format = strdup ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour," "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline," "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding"); else track->style_format = strdup ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour," "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut," "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow," "Alignment, MarginL, MarginR, MarginV, Encoding"); } q = format = strdup(track->style_format); ass_msg(track->library, MSGL_V, "[%p] Style: %s", track, str); sid = ass_alloc_style(track); style = track->styles + sid; target = style; // fill style with some default values style->ScaleX = 100.; style->ScaleY = 100.; while (1) { NEXT(q, tname); NEXT(p, token); if (0) { // cool ;) STRVAL(Name) if ((strcmp(target->Name, "Default") == 0) || (strcmp(target->Name, "*Default") == 0)) track->default_style = sid; 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; FPVAL(FontSize) INTVAL(Bold) INTVAL(Italic) INTVAL(Underline) INTVAL(StrikeOut) FPVAL(Spacing) INTVAL(Angle) INTVAL(BorderStyle) INTVAL(Alignment) if (track->track_type == TRACK_TYPE_ASS) target->Alignment = numpad2align(target->Alignment); INTVAL(MarginL) INTVAL(MarginR) INTVAL(MarginV) INTVAL(Encoding) FPVAL(ScaleX) FPVAL(ScaleY) FPVAL(Outline) FPVAL(Shadow) } } style->ScaleX /= 100.; style->ScaleY /= 100.; style->Bold = !!style->Bold; style->Italic = !!style->Italic; style->Underline = !!style->Underline; if (!style->Name) style->Name = strdup("Default"); if (!style->FontName) style->FontName = strdup("Arial"); // skip '@' at the start of the font name if (*style->FontName == '@') { p = style->FontName; style->FontName = strdup(p + 1); free(p); } free(format); return 0; } static int process_styles_line(ASS_Track *track, char *str) { if (!strncmp(str, "Format:", 7)) { char *p = str + 7; skip_spaces(&p); track->style_format = strdup(p); ass_msg(track->library, MSGL_DBG2, "Style format: %s", track->style_format); } else if (!strncmp(str, "Style:", 6)) { char *p = str + 6; skip_spaces(&p); process_style(track, p); } return 0; } static int process_info_line(ASS_Track *track, char *str) { if (!strncmp(str, "PlayResX:", 9)) { track->PlayResX = atoi(str + 9); } else if (!strncmp(str, "PlayResY:", 9)) { track->PlayResY = atoi(str + 9); } else if (!strncmp(str, "Timer:", 6)) { track->Timer = atof(str + 6); } else if (!strncmp(str, "WrapStyle:", 10)) { track->WrapStyle = atoi(str + 10); } else if (!strncmp(str, "ScaledBorderAndShadow:", 22)) { track->ScaledBorderAndShadow = parse_bool(str + 22); } else if (!strncmp(str, "Kerning:", 8)) { track->Kerning = parse_bool(str + 8); } return 0; } static void event_format_fallback(ASS_Track *track) { track->parser_priv->state = PST_EVENTS; if (track->track_type == TRACK_TYPE_SSA) track->event_format = strdup("Format: Marked, Start, End, Style, " "Name, MarginL, MarginR, MarginV, Effect, Text"); else track->event_format = strdup("Format: Layer, Start, End, Style, " "Actor, MarginL, MarginR, MarginV, Effect, Text"); ass_msg(track->library, MSGL_V, "No event format found, using fallback"); } static int process_events_line(ASS_Track *track, char *str) { if (!strncmp(str, "Format:", 7)) { char *p = str + 7; skip_spaces(&p); track->event_format = strdup(p); ass_msg(track->library, MSGL_DBG2, "Event format: %s", track->event_format); } else if (!strncmp(str, "Dialogue:", 9)) { // This should never be reached for embedded subtitles. // They have slightly different format and are parsed in ass_process_chunk, // called directly from demuxer int eid; ASS_Event *event; str += 9; skip_spaces(&str); eid = ass_alloc_event(track); event = track->events + eid; // We can't parse events with event_format if (!track->event_format) event_format_fallback(track); process_event_tail(track, event, str, 0); } else { ass_msg(track->library, MSGL_V, "Not understood: '%s'", str); } return 0; } // Copied from mkvtoolnix static unsigned char *decode_chars(unsigned char c1, unsigned char c2, unsigned char c3, unsigned char c4, unsigned char *dst, int cnt) { uint32_t value; unsigned char bytes[3]; int i; value = ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 - 33); bytes[2] = value & 0xff; bytes[1] = (value & 0xff00) >> 8; bytes[0] = (value & 0xff0000) >> 16; for (i = 0; i < cnt; ++i) *dst++ = bytes[i]; return dst; } static int decode_font(ASS_Track *track) { unsigned char *p; unsigned char *q; int i; int size; // original size int dsize; // decoded size unsigned char *buf = 0; ass_msg(track->library, MSGL_V, "Font: %d bytes encoded data", track->parser_priv->fontdata_used); size = track->parser_priv->fontdata_used; if (size % 4 == 1) { ass_msg(track->library, MSGL_ERR, "Bad encoded data size"); goto error_decode_font; } buf = malloc(size / 4 * 3 + 2); q = buf; for (i = 0, p = (unsigned char *) track->parser_priv->fontdata; i < size / 4; i++, p += 4) { q = decode_chars(p[0], p[1], p[2], p[3], q, 3); } if (size % 4 == 2) { q = decode_chars(p[0], p[1], 0, 0, q, 1); } else if (size % 4 == 3) { q = decode_chars(p[0], p[1], p[2], 0, q, 2); } dsize = q - buf; assert(dsize <= size / 4 * 3 + 2); if (track->library->extract_fonts) { ass_add_font(track->library, track->parser_priv->fontname, (char *) buf, dsize); buf = 0; } error_decode_font: if (buf) free(buf); free(track->parser_priv->fontname); free(track->parser_priv->fontdata); track->parser_priv->fontname = 0; track->parser_priv->fontdata = 0; track->parser_priv->fontdata_size = 0; track->parser_priv->fontdata_used = 0; return 0; } static int process_fonts_line(ASS_Track *track, char *str) { int len; if (!strncmp(str, "fontname:", 9)) { char *p = str + 9; skip_spaces(&p); if (track->parser_priv->fontname) { decode_font(track); } track->parser_priv->fontname = strdup(p); ass_msg(track->library, MSGL_V, "Fontname: %s", track->parser_priv->fontname); return 0; } if (!track->parser_priv->fontname) { ass_msg(track->library, MSGL_V, "Not understood: '%s'", str); return 0; } len = strlen(str); if (len > 80) { ass_msg(track->library, MSGL_WARN, "Font line too long: %d, %s", len, str); return 0; } if (track->parser_priv->fontdata_used + len > track->parser_priv->fontdata_size) { track->parser_priv->fontdata_size += 100 * 1024; track->parser_priv->fontdata = realloc(track->parser_priv->fontdata, track->parser_priv->fontdata_size); } memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used, str, len); track->parser_priv->fontdata_used += len; return 0; } /** * \brief Parse a header line * \param track track * \param str string to parse, zero-terminated */ static int process_line(ASS_Track *track, char *str) { if (!strncasecmp(str, "[Script Info]", 13)) { track->parser_priv->state = PST_INFO; } else if (!strncasecmp(str, "[V4 Styles]", 11)) { track->parser_priv->state = PST_STYLES; track->track_type = TRACK_TYPE_SSA; } else if (!strncasecmp(str, "[V4+ Styles]", 12)) { track->parser_priv->state = PST_STYLES; track->track_type = TRACK_TYPE_ASS; } else if (!strncasecmp(str, "[Events]", 8)) { track->parser_priv->state = PST_EVENTS; } else if (!strncasecmp(str, "[Fonts]", 7)) { track->parser_priv->state = PST_FONTS; } else { switch (track->parser_priv->state) { case PST_INFO: process_info_line(track, str); break; case PST_STYLES: process_styles_line(track, str); break; case PST_EVENTS: process_events_line(track, str); break; case PST_FONTS: process_fonts_line(track, str); break; default: break; } } // there is no explicit end-of-font marker in ssa/ass if ((track->parser_priv->state != PST_FONTS) && (track->parser_priv->fontname)) decode_font(track); return 0; } static int process_text(ASS_Track *track, char *str) { char *p = str; while (1) { char *q; while (1) { if ((*p == '\r') || (*p == '\n')) ++p; else if (p[0] == '\xef' && p[1] == '\xbb' && p[2] == '\xbf') p += 3; // U+FFFE (BOM) else break; } for (q = p; ((*q != '\0') && (*q != '\r') && (*q != '\n')); ++q) { }; if (q == p) break; if (*q != '\0') *(q++) = '\0'; process_line(track, p); if (*q == '\0') break; p = q; } return 0; } /** * \brief Process a chunk of subtitle stream data. * \param track track * \param data string to parse * \param size length of data */ void ass_process_data(ASS_Track *track, char *data, int size) { char *str = malloc(size + 1); memcpy(str, data, size); str[size] = '\0'; ass_msg(track->library, MSGL_V, "Event: %s", str); process_text(track, str); free(str); } /** * \brief Process CodecPrivate section of subtitle stream * \param track track * \param data string to parse * \param size length of data CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections */ void ass_process_codec_private(ASS_Track *track, char *data, int size) { ass_process_data(track, data, size); // probably an mkv produced by ancient mkvtoolnix // such files don't have [Events] and Format: headers if (!track->event_format) event_format_fallback(track); ass_process_force_style(track); } static int check_duplicate_event(ASS_Track *track, int ReadOrder) { int i; for (i = 0; i < track->n_events - 1; ++i) // ignoring last event, it is the one we are comparing with if (track->events[i].ReadOrder == ReadOrder) return 1; return 0; } /** * \brief Process a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary). * \param track track * \param data string to parse * \param size length of data * \param timecode starting time of the event (milliseconds) * \param duration duration of the event (milliseconds) */ void ass_process_chunk(ASS_Track *track, char *data, int size, long long timecode, long long duration) { char *str; int eid; char *p; char *token; ASS_Event *event; if (!track->event_format) { ass_msg(track->library, MSGL_WARN, "Event format header missing"); return; } str = malloc(size + 1); memcpy(str, data, size); str[size] = '\0'; ass_msg(track->library, MSGL_V, "Event at %" PRId64 ", +%" PRId64 ": %s", (int64_t) timecode, (int64_t) duration, str); eid = ass_alloc_event(track); event = track->events + eid; p = str; do { NEXT(p, token); event->ReadOrder = atoi(token); if (check_duplicate_event(track, event->ReadOrder)) break; NEXT(p, token); event->Layer = atoi(token); process_event_tail(track, event, p, 3); event->Start = timecode; event->Duration = duration; free(str); return; // dump_events(tid); } while (0); // some error ass_free_event(track, eid); track->n_events--; free(str); } #ifdef CONFIG_ICONV /** \brief recode buffer to utf-8 * constraint: codepage != 0 * \param data pointer to text buffer * \param size buffer size * \return a pointer to recoded buffer, caller is responsible for freeing it **/ static char *sub_recode(ASS_Library *library, char *data, size_t size, char *codepage) { iconv_t icdsc; char *tocp = "UTF-8"; char *outbuf; assert(codepage); { const char *cp_tmp = codepage; #ifdef CONFIG_ENCA char enca_lang[3], enca_fallback[100]; if (sscanf(codepage, "enca:%2s:%99s", enca_lang, enca_fallback) == 2 || sscanf(codepage, "ENCA:%2s:%99s", enca_lang, enca_fallback) == 2) { cp_tmp = ass_guess_buffer_cp(library, (unsigned char *) data, size, enca_lang, enca_fallback); } #endif if ((icdsc = iconv_open(tocp, cp_tmp)) != (iconv_t) (-1)) { ass_msg(library, MSGL_V, "Opened iconv descriptor"); } else ass_msg(library, MSGL_ERR, "Error opening iconv descriptor"); } { size_t osize = size; size_t ileft = size; size_t oleft = size - 1; char *ip; char *op; size_t rc; int clear = 0; outbuf = malloc(osize); ip = data; op = outbuf; while (1) { if (ileft) rc = iconv(icdsc, &ip, &ileft, &op, &oleft); else { // clear the conversion state and leave clear = 1; rc = iconv(icdsc, NULL, NULL, &op, &oleft); } if (rc == (size_t) (-1)) { if (errno == E2BIG) { size_t offset = op - outbuf; outbuf = (char *) realloc(outbuf, osize + size); op = outbuf + offset; osize += size; oleft += size; } else { ass_msg(library, MSGL_WARN, "Error recoding file"); return NULL; } } else if (clear) break; } outbuf[osize - oleft - 1] = 0; } if (icdsc != (iconv_t) (-1)) { (void) iconv_close(icdsc); icdsc = (iconv_t) (-1); ass_msg(library, MSGL_V, "Closed iconv descriptor"); } return outbuf; } #endif // ICONV /** * \brief read file contents into newly allocated buffer * \param fname file name * \param bufsize out: file size * \return pointer to file contents. Caller is responsible for its deallocation. */ static char *read_file(ASS_Library *library, char *fname, size_t *bufsize) { int res; long sz; long bytes_read; char *buf; FILE *fp = fopen(fname, "rb"); if (!fp) { ass_msg(library, MSGL_WARN, "ass_read_file(%s): fopen failed", fname); return 0; } res = fseek(fp, 0, SEEK_END); if (res == -1) { ass_msg(library, MSGL_WARN, "ass_read_file(%s): fseek failed", fname); fclose(fp); return 0; } sz = ftell(fp); rewind(fp); if (sz > 10 * 1024 * 1024) { ass_msg(library, MSGL_INFO, "ass_read_file(%s): Refusing to load subtitles " "larger than 10MiB", fname); fclose(fp); return 0; } ass_msg(library, MSGL_V, "File size: %ld", sz); buf = malloc(sz + 1); assert(buf); bytes_read = 0; do { res = fread(buf + bytes_read, 1, sz - bytes_read, fp); if (res <= 0) { ass_msg(library, MSGL_INFO, "Read failed, %d: %s", errno, strerror(errno)); fclose(fp); free(buf); return 0; } bytes_read += res; } while (sz - bytes_read > 0); buf[sz] = '\0'; fclose(fp); if (bufsize) *bufsize = sz; return buf; } /* * \param buf pointer to subtitle text in utf-8 */ static ASS_Track *parse_memory(ASS_Library *library, char *buf) { ASS_Track *track; int i; track = ass_new_track(library); // process header process_text(track, buf); // external SSA/ASS subs does not have ReadOrder field for (i = 0; i < track->n_events; ++i) track->events[i].ReadOrder = i; // there is no explicit end-of-font marker in ssa/ass if (track->parser_priv->fontname) decode_font(track); if (track->track_type == TRACK_TYPE_UNKNOWN) { ass_free_track(track); return 0; } ass_process_force_style(track); return track; } /** * \brief Read subtitles from memory. * \param library libass library object * \param buf pointer to subtitles text * \param bufsize size of buffer * \param codepage recode buffer contents from given codepage * \return newly allocated track */ ASS_Track *ass_read_memory(ASS_Library *library, char *buf, size_t bufsize, char *codepage) { ASS_Track *track; int need_free = 0; if (!buf) return 0; #ifdef CONFIG_ICONV if (codepage) { buf = sub_recode(library, buf, bufsize, codepage); if (!buf) return 0; else need_free = 1; } #endif track = parse_memory(library, buf); if (need_free) free(buf); if (!track) return 0; ass_msg(library, MSGL_INFO, "Added subtitle file: " " (%d styles, %d events)", track->n_styles, track->n_events); return track; } static char *read_file_recode(ASS_Library *library, char *fname, char *codepage, size_t *size) { char *buf; size_t bufsize; buf = read_file(library, fname, &bufsize); if (!buf) return 0; #ifdef CONFIG_ICONV if (codepage) { char *tmpbuf = sub_recode(library, buf, bufsize, codepage); free(buf); buf = tmpbuf; } if (!buf) return 0; #endif *size = bufsize; return buf; } /** * \brief Read subtitles from file. * \param library libass library object * \param fname file name * \param codepage recode buffer contents from given codepage * \return newly allocated track */ ASS_Track *ass_read_file(ASS_Library *library, char *fname, char *codepage) { char *buf; ASS_Track *track; size_t bufsize; buf = read_file_recode(library, fname, codepage, &bufsize); if (!buf) return 0; track = parse_memory(library, buf); free(buf); if (!track) return 0; track->name = strdup(fname); ass_msg(library, MSGL_INFO, "Added subtitle file: '%s' (%d styles, %d events)", fname, track->n_styles, track->n_events); return track; } /** * \brief read styles from file into already initialized track */ int ass_read_styles(ASS_Track *track, char *fname, char *codepage) { char *buf; ParserState old_state; size_t sz; buf = read_file(track->library, fname, &sz); if (!buf) return 1; #ifdef CONFIG_ICONV if (codepage) { char *tmpbuf; tmpbuf = sub_recode(track->library, buf, sz, codepage); free(buf); buf = tmpbuf; } if (!buf) return 0; #endif old_state = track->parser_priv->state; track->parser_priv->state = PST_STYLES; process_text(track, buf); track->parser_priv->state = old_state; return 0; } long long ass_step_sub(ASS_Track *track, long long now, int movement) { int i; if (movement == 0) return 0; if (track->n_events == 0) return 0; if (movement < 0) for (i = 0; (i < track->n_events) && ((long long) (track->events[i].Start + track->events[i].Duration) <= now); ++i) { } else for (i = track->n_events - 1; (i >= 0) && ((long long) (track->events[i].Start) > now); --i) { } // -1 and n_events are ok assert(i >= -1); assert(i <= track->n_events); i += movement; if (i < 0) i = 0; if (i >= track->n_events) i = track->n_events - 1; return ((long long) track->events[i].Start) - now; } ASS_Track *ass_new_track(ASS_Library *library) { ASS_Track *track = calloc(1, sizeof(ASS_Track)); track->library = library; track->ScaledBorderAndShadow = 1; track->parser_priv = calloc(1, sizeof(ASS_ParserPriv)); return track; }