1 // -*- c-basic-offset: 8; indent-tabs-mode: t -*-
2 // vim:ts=8:sw=8:noet:ai:
4 Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
28 #include <sys/types.h>
38 #include "ass_utils.h"
39 #include "ass_library.h"
42 typedef enum {PST_UNKNOWN = 0, PST_INFO, PST_STYLES, PST_EVENTS, PST_FONTS} parser_state_t;
44 struct parser_priv_s {
52 #define ASS_STYLES_ALLOC 20
53 #define ASS_EVENTS_ALLOC 200
55 void ass_free_track(ass_track_t* track) {
58 if (track->parser_priv) {
59 if (track->parser_priv->fontname)
60 free(track->parser_priv->fontname);
61 if (track->parser_priv->fontdata)
62 free(track->parser_priv->fontdata);
63 free(track->parser_priv);
65 if (track->style_format)
66 free(track->style_format);
67 if (track->event_format)
68 free(track->event_format);
70 for (i = 0; i < track->n_styles; ++i)
71 ass_free_style(track, i);
75 for (i = 0; i < track->n_events; ++i)
76 ass_free_event(track, i);
81 /// \brief Allocate a new style struct
82 /// \param track track
84 int ass_alloc_style(ass_track_t* track) {
87 assert(track->n_styles <= track->max_styles);
89 if (track->n_styles == track->max_styles) {
90 track->max_styles += ASS_STYLES_ALLOC;
91 track->styles = (ass_style_t*)realloc(track->styles, sizeof(ass_style_t)*track->max_styles);
94 sid = track->n_styles++;
95 memset(track->styles + sid, 0, sizeof(ass_style_t));
99 /// \brief Allocate a new event struct
100 /// \param track track
102 int ass_alloc_event(ass_track_t* track) {
105 assert(track->n_events <= track->max_events);
107 if (track->n_events == track->max_events) {
108 track->max_events += ASS_EVENTS_ALLOC;
109 track->events = (ass_event_t*)realloc(track->events, sizeof(ass_event_t)*track->max_events);
112 eid = track->n_events++;
113 memset(track->events + eid, 0, sizeof(ass_event_t));
117 void ass_free_event(ass_track_t* track, int eid) {
118 ass_event_t* event = track->events + eid;
125 if (event->render_priv)
126 free(event->render_priv);
129 void ass_free_style(ass_track_t* track, int sid) {
130 ass_style_t* style = track->styles + sid;
134 free(style->FontName);
137 // ==============================================================================================
139 static void skip_spaces(char** str) {
141 while ((*p==' ') || (*p=='\t'))
146 static void rskip_spaces(char** str, char* limit) {
148 while ((p >= limit) && ((*p==' ') || (*p=='\t')))
154 * \brief find style by name
156 * \param name style name
157 * \return index in track->styles
158 * Returnes 0 if no styles found => expects at least 1 style.
159 * Parsing code always adds "Default" style in the end.
161 static int lookup_style(ass_track_t* track, char* name) {
163 if (*name == '*') ++name; // FIXME: what does '*' really mean ?
164 for (i=0; i<track->n_styles; ++i) {
165 // FIXME: mb strcasecmp ?
166 if (strcmp(track->styles[i].Name, name) == 0)
169 i = track->default_style;
170 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NoStyleNamedXFoundUsingY, track, name, track->styles[i].Name);
171 return i; // use the first style
174 static uint32_t string2color(char* p) {
176 (void)strtocolor(&p, &tmp);
180 static long long string2timecode(char* p) {
181 unsigned h, m, s, ms;
183 int res = sscanf(p, "%1d:%2d:%2d.%2d", &h, &m, &s, &ms);
185 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_BadTimestamp);
188 tm = ((h * 60 + m) * 60 + s) * 1000 + ms * 10;
193 * \brief converts numpad-style align to align.
195 static int numpad2align(int val) {
197 v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment
198 if (v != 0) v = 3 - v;
199 res = ((val - 1) % 3) + 1; // horizontal alignment
204 #define NEXT(str,token) \
205 token = next_token(&str); \
208 #define ANYVAL(name,func) \
209 } else if (strcasecmp(tname, #name) == 0) { \
210 target->name = func(token); \
211 mp_msg(MSGT_ASS, MSGL_DBG2, "%s = %s\n", #name, token);
213 #define STRVAL(name) \
214 } else if (strcasecmp(tname, #name) == 0) { \
215 if (target->name != NULL) free(target->name); \
216 target->name = strdup(token); \
217 mp_msg(MSGT_ASS, MSGL_DBG2, "%s = %s\n", #name, token);
219 #define COLORVAL(name) ANYVAL(name,string2color)
220 #define INTVAL(name) ANYVAL(name,atoi)
221 #define FPVAL(name) ANYVAL(name,atof)
222 #define TIMEVAL(name) ANYVAL(name,string2timecode)
223 #define STYLEVAL(name) \
224 } else if (strcasecmp(tname, #name) == 0) { \
225 target->name = lookup_style(track, token); \
226 mp_msg(MSGT_ASS, MSGL_DBG2, "%s = %s\n", #name, token);
228 #define ALIAS(alias,name) \
229 if (strcasecmp(tname, #alias) == 0) {tname = #name;}
231 static char* next_token(char** str) {
239 start = p; // start of the token
240 for (; (*p != '\0') && (*p != ','); ++p) {}
242 *str = p; // eos found, str will point to '\0' at exit
245 *str = p + 1; // ',' found, str will point to the next char (beginning of the next token)
247 --p; // end of current token
248 rskip_spaces(&p, start);
250 p = start; // empty token
252 ++p; // the first space character, or '\0'
257 * \brief Parse the tail of Dialogue line
259 * \param event parsed data goes here
260 * \param str string to parse, zero-terminated
261 * \param n_ignored number of format options to skip at the beginning
263 static int process_event_tail(ass_track_t* track, ass_event_t* event, char* str, int n_ignored)
269 ass_event_t* target = event;
271 char* format = strdup(track->event_format);
272 char* q = format; // format scanning pointer
274 if (track->n_styles == 0) {
275 // add "Default" style to the end
276 // will be used if track does not contain a default style (or even does not contain styles at all)
277 int sid = ass_alloc_style(track);
278 track->styles[sid].Name = strdup("Default");
279 track->styles[sid].FontName = strdup("Arial");
282 for (i = 0; i < n_ignored; ++i) {
288 if (strcasecmp(tname, "Text") == 0) {
290 event->Text = strdup(p);
291 if (*event->Text != 0) {
292 last = event->Text + strlen(event->Text) - 1;
293 if (last >= event->Text && *last == '\r')
296 mp_msg(MSGT_ASS, MSGL_DBG2, "Text = %s\n", event->Text);
297 event->Duration -= event->Start;
299 return 0; // "Text" is always the last
303 ALIAS(End,Duration) // temporarily store end timecode in event->Duration
321 * \brief Parse command line style overrides (--ass-force-style option)
322 * \param track track to apply overrides to
323 * The format for overrides is [StyleName.]Field=Value
325 void process_force_style(ass_track_t* track) {
326 char **fs, *eq, *dt, *style, *tname, *token;
329 char** list = track->library->style_overrides;
333 for (fs = list; *fs; ++fs) {
334 eq = strrchr(*fs, '=');
340 if(!strcasecmp(*fs, "PlayResX"))
341 track->PlayResX = atoi(token);
342 else if(!strcasecmp(*fs, "PlayResY"))
343 track->PlayResY = atoi(token);
344 else if(!strcasecmp(*fs, "Timer"))
345 track->Timer = atof(token);
346 else if(!strcasecmp(*fs, "WrapStyle"))
347 track->WrapStyle = atoi(token);
349 dt = strrchr(*fs, '.');
358 for (sid = 0; sid < track->n_styles; ++sid) {
359 if (style == NULL || strcasecmp(track->styles[sid].Name, style) == 0) {
360 target = track->styles + sid;
363 COLORVAL(PrimaryColour)
364 COLORVAL(SecondaryColour)
365 COLORVAL(OutlineColour)
393 * \brief Parse the Style line
395 * \param str string to parse, zero-terminated
396 * Allocates a new style struct.
398 static int process_style(ass_track_t* track, char *str)
405 char* q; // format scanning pointer
410 if (!track->style_format) {
411 // no style format header
412 // probably an ancient script version
413 if (track->track_type == TRACK_TYPE_SSA)
414 track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
415 "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline,"
416 "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
418 track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
419 "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut,"
420 "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow,"
421 "Alignment, MarginL, MarginR, MarginV, Encoding");
424 q = format = strdup(track->style_format);
426 mp_msg(MSGT_ASS, MSGL_V, "[%p] Style: %s\n", track, str);
428 sid = ass_alloc_style(track);
430 style = track->styles + sid;
432 // fill style with some default values
433 style->ScaleX = 100.;
434 style->ScaleY = 100.;
440 // ALIAS(TertiaryColour,OutlineColour) // ignore TertiaryColour; it appears only in SSA, and is overridden by BackColour
444 if ((strcmp(target->Name, "Default")==0) || (strcmp(target->Name, "*Default")==0))
445 track->default_style = sid;
447 COLORVAL(PrimaryColour)
448 COLORVAL(SecondaryColour)
449 COLORVAL(OutlineColour) // TertiaryColor
451 // SSA uses BackColour for both outline and shadow
452 // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
453 if (track->track_type == TRACK_TYPE_SSA)
454 target->OutlineColour = target->BackColour;
464 if (track->track_type == TRACK_TYPE_ASS)
465 target->Alignment = numpad2align(target->Alignment);
476 style->ScaleX /= 100.;
477 style->ScaleY /= 100.;
478 style->Bold = !!style->Bold;
479 style->Italic = !!style->Italic;
480 style->Underline = !!style->Underline;
482 style->Name = strdup("Default");
483 if (!style->FontName)
484 style->FontName = strdup("Arial");
490 static int process_styles_line(ass_track_t* track, char *str)
492 if (!strncmp(str,"Format:", 7)) {
495 track->style_format = strdup(p);
496 mp_msg(MSGT_ASS, MSGL_DBG2, "Style format: %s\n", track->style_format);
497 } else if (!strncmp(str,"Style:", 6)) {
500 process_style(track, p);
505 static int process_info_line(ass_track_t* track, char *str)
507 if (!strncmp(str, "PlayResX:", 9)) {
508 track->PlayResX = atoi(str + 9);
509 } else if (!strncmp(str,"PlayResY:", 9)) {
510 track->PlayResY = atoi(str + 9);
511 } else if (!strncmp(str,"Timer:", 6)) {
512 track->Timer = atof(str + 6);
513 } else if (!strncmp(str,"WrapStyle:", 10)) {
514 track->WrapStyle = atoi(str + 10);
519 static int process_events_line(ass_track_t* track, char *str)
521 if (!strncmp(str, "Format:", 7)) {
524 track->event_format = strdup(p);
525 mp_msg(MSGT_ASS, MSGL_DBG2, "Event format: %s\n", track->event_format);
526 } else if (!strncmp(str, "Dialogue:", 9)) {
527 // This should never be reached for embedded subtitles.
528 // They have slightly different format and are parsed in ass_process_chunk,
529 // called directly from demuxer
536 eid = ass_alloc_event(track);
537 event = track->events + eid;
539 process_event_tail(track, event, str, 0);
541 mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s \n", str);
546 // Copied from mkvtoolnix
547 static unsigned char* decode_chars(unsigned char c1, unsigned char c2,
548 unsigned char c3, unsigned char c4, unsigned char* dst, int cnt)
551 unsigned char bytes[3];
554 value = ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 - 33);
555 bytes[2] = value & 0xff;
556 bytes[1] = (value & 0xff00) >> 8;
557 bytes[0] = (value & 0xff0000) >> 16;
559 for (i = 0; i < cnt; ++i)
564 static int decode_font(ass_track_t* track)
569 int size; // original size
570 int dsize; // decoded size
571 unsigned char* buf = 0;
573 mp_msg(MSGT_ASS, MSGL_V, "font: %d bytes encoded data \n", track->parser_priv->fontdata_used);
574 size = track->parser_priv->fontdata_used;
576 mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_BadEncodedDataSize);
577 goto error_decode_font;
579 buf = malloc(size / 4 * 3 + 2);
581 for (i = 0, p = (unsigned char*)track->parser_priv->fontdata; i < size / 4; i++, p+=4) {
582 q = decode_chars(p[0], p[1], p[2], p[3], q, 3);
585 q = decode_chars(p[0], p[1], 0, 0, q, 1);
586 } else if (size % 4 == 3) {
587 q = decode_chars(p[0], p[1], p[2], 0, q, 2);
590 assert(dsize <= size / 4 * 3 + 2);
592 if (track->library->extract_fonts) {
593 ass_add_font(track->library, track->parser_priv->fontname, (char*)buf, dsize);
599 free(track->parser_priv->fontname);
600 free(track->parser_priv->fontdata);
601 track->parser_priv->fontname = 0;
602 track->parser_priv->fontdata = 0;
603 track->parser_priv->fontdata_size = 0;
604 track->parser_priv->fontdata_used = 0;
608 static int process_fonts_line(ass_track_t* track, char *str)
612 if (!strncmp(str, "fontname:", 9)) {
615 if (track->parser_priv->fontname) {
618 track->parser_priv->fontname = strdup(p);
619 mp_msg(MSGT_ASS, MSGL_V, "fontname: %s\n", track->parser_priv->fontname);
623 if (!track->parser_priv->fontname) {
624 mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s \n", str);
630 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FontLineTooLong, len, str);
633 if (track->parser_priv->fontdata_used + len > track->parser_priv->fontdata_size) {
634 track->parser_priv->fontdata_size += 100 * 1024;
635 track->parser_priv->fontdata = realloc(track->parser_priv->fontdata, track->parser_priv->fontdata_size);
637 memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used, str, len);
638 track->parser_priv->fontdata_used += len;
644 * \brief Parse a header line
646 * \param str string to parse, zero-terminated
648 static int process_line(ass_track_t* track, char *str)
650 if (!strncmp(str, "[Script Info]", 13)) {
651 track->parser_priv->state = PST_INFO;
652 } else if (!strncmp(str, "[V4 Styles]", 11)) {
653 track->parser_priv->state = PST_STYLES;
654 track->track_type = TRACK_TYPE_SSA;
655 } else if (!strncmp(str, "[V4+ Styles]", 12)) {
656 track->parser_priv->state = PST_STYLES;
657 track->track_type = TRACK_TYPE_ASS;
658 } else if (!strncmp(str, "[Events]", 8)) {
659 track->parser_priv->state = PST_EVENTS;
660 } else if (!strncmp(str, "[Fonts]", 7)) {
661 track->parser_priv->state = PST_FONTS;
663 switch (track->parser_priv->state) {
665 process_info_line(track, str);
668 process_styles_line(track, str);
671 process_events_line(track, str);
674 process_fonts_line(track, str);
681 // there is no explicit end-of-font marker in ssa/ass
682 if ((track->parser_priv->state != PST_FONTS) && (track->parser_priv->fontname))
688 static int process_text(ass_track_t* track, char* str)
694 if ((*p=='\r')||(*p=='\n')) ++p;
695 else if (p[0]=='\xef' && p[1]=='\xbb' && p[2]=='\xbf') p+=3; // U+FFFE (BOM)
698 for (q=p; ((*q!='\0')&&(*q!='\r')&&(*q!='\n')); ++q) {};
703 process_line(track, p);
712 * \brief Process CodecPrivate section of subtitle stream
714 * \param data string to parse
715 * \param size length of data
716 CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections
718 void ass_process_codec_private(ass_track_t* track, char *data, int size)
720 char* str = malloc(size + 1);
722 memcpy(str, data, size);
725 process_text(track, str);
728 if (!track->event_format) {
729 // probably an mkv produced by ancient mkvtoolnix
730 // such files don't have [Events] and Format: headers
731 track->parser_priv->state = PST_EVENTS;
732 if (track->track_type == TRACK_TYPE_SSA)
733 track->event_format = strdup("Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text");
735 track->event_format = strdup("Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text");
738 process_force_style(track);
741 static int check_duplicate_event(ass_track_t* track, int ReadOrder)
744 for (i = 0; i<track->n_events - 1; ++i) // ignoring last event, it is the one we are comparing with
745 if (track->events[i].ReadOrder == ReadOrder)
751 * \brief Process a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary).
753 * \param data string to parse
754 * \param size length of data
755 * \param timecode starting time of the event (milliseconds)
756 * \param duration duration of the event (milliseconds)
758 void ass_process_chunk(ass_track_t* track, char *data, int size, long long timecode, long long duration)
766 if (!track->event_format) {
767 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EventFormatHeaderMissing);
771 str = malloc(size + 1);
772 memcpy(str, data, size);
774 mp_msg(MSGT_ASS, MSGL_V, "event at %" PRId64 ", +%" PRId64 ": %s \n", (int64_t)timecode, (int64_t)duration, str);
776 eid = ass_alloc_event(track);
777 event = track->events + eid;
783 event->ReadOrder = atoi(token);
784 if (check_duplicate_event(track, event->ReadOrder))
788 event->Layer = atoi(token);
790 process_event_tail(track, event, p, 3);
792 event->Start = timecode;
793 event->Duration = duration;
800 ass_free_event(track, eid);
806 /** \brief recode buffer to utf-8
807 * constraint: codepage != 0
808 * \param data pointer to text buffer
809 * \param size buffer size
810 * \return a pointer to recoded buffer, caller is responsible for freeing it
812 static char* sub_recode(char* data, size_t size, char* codepage)
814 static iconv_t icdsc = (iconv_t)(-1);
815 char* tocp = "UTF-8";
820 const char* cp_tmp = codepage;
822 char enca_lang[3], enca_fallback[100];
823 if (sscanf(codepage, "enca:%2s:%99s", enca_lang, enca_fallback) == 2
824 || sscanf(codepage, "ENCA:%2s:%99s", enca_lang, enca_fallback) == 2) {
825 cp_tmp = guess_buffer_cp((unsigned char*)data, size, enca_lang, enca_fallback);
828 if ((icdsc = iconv_open (tocp, cp_tmp)) != (iconv_t)(-1)){
829 mp_msg(MSGT_ASS,MSGL_V,"LIBSUB: opened iconv descriptor.\n");
831 mp_msg(MSGT_ASS,MSGL_ERR,MSGTR_LIBASS_ErrorOpeningIconvDescriptor);
837 size_t oleft = size - 1;
842 outbuf = malloc(size);
847 rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
848 if (rc == (size_t)(-1)) {
849 if (errno == E2BIG) {
850 int offset = op - outbuf;
851 outbuf = (char*)realloc(outbuf, osize + size);
852 op = outbuf + offset;
856 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorRecodingFile);
861 outbuf[osize - oleft - 1] = 0;
864 if (icdsc != (iconv_t)(-1)) {
865 (void)iconv_close(icdsc);
866 icdsc = (iconv_t)(-1);
867 mp_msg(MSGT_ASS,MSGL_V,"LIBSUB: closed iconv descriptor.\n");
875 * \brief read file contents into newly allocated buffer
876 * \param fname file name
877 * \param bufsize out: file size
878 * \return pointer to file contents. Caller is responsible for its deallocation.
880 static char* read_file(char* fname, size_t *bufsize)
887 FILE* fp = fopen(fname, "rb");
889 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FopenFailed, fname);
892 res = fseek(fp, 0, SEEK_END);
894 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FseekFailed, fname);
902 if (sz > 10*1024*1024) {
903 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_RefusingToLoadSubtitlesLargerThan10M, fname);
908 mp_msg(MSGT_ASS, MSGL_V, "file size: %ld\n", sz);
910 buf = malloc(sz + 1);
914 res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
916 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_ReadFailed, errno, strerror(errno));
922 } while (sz - bytes_read > 0);
932 * \param buf pointer to subtitle text in utf-8
934 static ass_track_t* parse_memory(ass_library_t* library, char* buf)
939 track = ass_new_track(library);
942 process_text(track, buf);
944 // external SSA/ASS subs does not have ReadOrder field
945 for (i = 0; i < track->n_events; ++i)
946 track->events[i].ReadOrder = i;
948 // there is no explicit end-of-font marker in ssa/ass
949 if (track->parser_priv->fontname)
952 if (track->track_type == TRACK_TYPE_UNKNOWN) {
953 ass_free_track(track);
957 process_force_style(track);
963 * \brief Read subtitles from memory.
964 * \param library libass library object
965 * \param buf pointer to subtitles text
966 * \param bufsize size of buffer
967 * \param codepage recode buffer contents from given codepage
968 * \return newly allocated track
970 ass_track_t* ass_read_memory(ass_library_t* library, char* buf, size_t bufsize, char* codepage)
980 buf = sub_recode(buf, bufsize, codepage);
986 track = parse_memory(library, buf);
992 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_AddedSubtitleFileMemory, track->n_styles, track->n_events);
996 char* read_file_recode(char* fname, char* codepage, int* size)
1001 buf = read_file(fname, &bufsize);
1006 char* tmpbuf = sub_recode(buf, bufsize, codepage);
1018 * \brief Read subtitles from file.
1019 * \param library libass library object
1020 * \param fname file name
1021 * \param codepage recode buffer contents from given codepage
1022 * \return newly allocated track
1024 ass_track_t* ass_read_file(ass_library_t* library, char* fname, char* codepage)
1030 buf = read_file_recode(fname, codepage, &bufsize);
1033 track = parse_memory(library, buf);
1038 track->name = strdup(fname);
1040 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_AddedSubtitleFileFname, fname, track->n_styles, track->n_events);
1042 // dump_events(forced_tid);
1047 * \brief read styles from file into already initialized track
1049 int ass_read_styles(ass_track_t* track, char* fname, char* codepage)
1052 parser_state_t old_state;
1055 buf = read_file(fname, &sz);
1061 tmpbuf = sub_recode(buf, sz, codepage);
1069 old_state = track->parser_priv->state;
1070 track->parser_priv->state = PST_STYLES;
1071 process_text(track, buf);
1072 track->parser_priv->state = old_state;
1077 long long ass_step_sub(ass_track_t* track, long long now, int movement) {
1080 if (movement == 0) return 0;
1081 if (track->n_events == 0) return 0;
1084 for (i = 0; (i < track->n_events) && ((long long)(track->events[i].Start + track->events[i].Duration) <= now); ++i) {}
1086 for (i = track->n_events - 1; (i >= 0) && ((long long)(track->events[i].Start) > now); --i) {}
1088 // -1 and n_events are ok
1089 assert(i >= -1); assert(i <= track->n_events);
1092 if (i >= track->n_events) i = track->n_events - 1;
1093 return ((long long)track->events[i].Start) - now;
1096 ass_track_t* ass_new_track(ass_library_t* library) {
1097 ass_track_t* track = calloc(1, sizeof(ass_track_t));
1098 track->library = library;
1099 track->parser_priv = calloc(1, sizeof(parser_priv_t));