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 file is part of libass.
8 * libass is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * libass is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License along
19 * with libass; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
30 #include <sys/types.h>
40 #include "ass_utils.h"
41 #include "ass_library.h"
44 typedef enum {PST_UNKNOWN = 0, PST_INFO, PST_STYLES, PST_EVENTS, PST_FONTS} parser_state_t;
46 struct parser_priv_s {
54 #define ASS_STYLES_ALLOC 20
55 #define ASS_EVENTS_ALLOC 200
57 void ass_free_track(ass_track_t* track) {
60 if (track->parser_priv) {
61 if (track->parser_priv->fontname)
62 free(track->parser_priv->fontname);
63 if (track->parser_priv->fontdata)
64 free(track->parser_priv->fontdata);
65 free(track->parser_priv);
67 if (track->style_format)
68 free(track->style_format);
69 if (track->event_format)
70 free(track->event_format);
72 for (i = 0; i < track->n_styles; ++i)
73 ass_free_style(track, i);
77 for (i = 0; i < track->n_events; ++i)
78 ass_free_event(track, i);
83 /// \brief Allocate a new style struct
84 /// \param track track
86 int ass_alloc_style(ass_track_t* track) {
89 assert(track->n_styles <= track->max_styles);
91 if (track->n_styles == track->max_styles) {
92 track->max_styles += ASS_STYLES_ALLOC;
93 track->styles = (ass_style_t*)realloc(track->styles, sizeof(ass_style_t)*track->max_styles);
96 sid = track->n_styles++;
97 memset(track->styles + sid, 0, sizeof(ass_style_t));
101 /// \brief Allocate a new event struct
102 /// \param track track
104 int ass_alloc_event(ass_track_t* track) {
107 assert(track->n_events <= track->max_events);
109 if (track->n_events == track->max_events) {
110 track->max_events += ASS_EVENTS_ALLOC;
111 track->events = (ass_event_t*)realloc(track->events, sizeof(ass_event_t)*track->max_events);
114 eid = track->n_events++;
115 memset(track->events + eid, 0, sizeof(ass_event_t));
119 void ass_free_event(ass_track_t* track, int eid) {
120 ass_event_t* event = track->events + eid;
127 if (event->render_priv)
128 free(event->render_priv);
131 void ass_free_style(ass_track_t* track, int sid) {
132 ass_style_t* style = track->styles + sid;
136 free(style->FontName);
139 // ==============================================================================================
141 static void skip_spaces(char** str) {
143 while ((*p==' ') || (*p=='\t'))
148 static void rskip_spaces(char** str, char* limit) {
150 while ((p >= limit) && ((*p==' ') || (*p=='\t')))
156 * \brief find style by name
158 * \param name style name
159 * \return index in track->styles
160 * Returnes 0 if no styles found => expects at least 1 style.
161 * Parsing code always adds "Default" style in the end.
163 static int lookup_style(ass_track_t* track, char* name) {
165 if (*name == '*') ++name; // FIXME: what does '*' really mean ?
166 for (i = track->n_styles - 1; i >= 0; --i) {
167 // FIXME: mb strcasecmp ?
168 if (strcmp(track->styles[i].Name, name) == 0)
171 i = track->default_style;
172 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NoStyleNamedXFoundUsingY, track, name, track->styles[i].Name);
173 return i; // use the first style
176 static uint32_t string2color(char* p) {
178 (void)strtocolor(&p, &tmp);
182 static long long string2timecode(char* p) {
183 unsigned h, m, s, ms;
185 int res = sscanf(p, "%1d:%2d:%2d.%2d", &h, &m, &s, &ms);
187 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_BadTimestamp);
190 tm = ((h * 60 + m) * 60 + s) * 1000 + ms * 10;
195 * \brief converts numpad-style align to align.
197 static int numpad2align(int val) {
199 v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment
200 if (v != 0) v = 3 - v;
201 res = ((val - 1) % 3) + 1; // horizontal alignment
206 #define NEXT(str,token) \
207 token = next_token(&str); \
210 #define ANYVAL(name,func) \
211 } else if (strcasecmp(tname, #name) == 0) { \
212 target->name = func(token); \
213 mp_msg(MSGT_ASS, MSGL_DBG2, "%s = %s\n", #name, token);
215 #define STRVAL(name) \
216 } else if (strcasecmp(tname, #name) == 0) { \
217 if (target->name != NULL) free(target->name); \
218 target->name = strdup(token); \
219 mp_msg(MSGT_ASS, MSGL_DBG2, "%s = %s\n", #name, token);
221 #define COLORVAL(name) ANYVAL(name,string2color)
222 #define INTVAL(name) ANYVAL(name,atoi)
223 #define FPVAL(name) ANYVAL(name,atof)
224 #define TIMEVAL(name) ANYVAL(name,string2timecode)
225 #define STYLEVAL(name) \
226 } else if (strcasecmp(tname, #name) == 0) { \
227 target->name = lookup_style(track, token); \
228 mp_msg(MSGT_ASS, MSGL_DBG2, "%s = %s\n", #name, token);
230 #define ALIAS(alias,name) \
231 if (strcasecmp(tname, #alias) == 0) {tname = #name;}
233 static char* next_token(char** str) {
241 start = p; // start of the token
242 for (; (*p != '\0') && (*p != ','); ++p) {}
244 *str = p; // eos found, str will point to '\0' at exit
247 *str = p + 1; // ',' found, str will point to the next char (beginning of the next token)
249 --p; // end of current token
250 rskip_spaces(&p, start);
252 p = start; // empty token
254 ++p; // the first space character, or '\0'
259 * \brief Parse the tail of Dialogue line
261 * \param event parsed data goes here
262 * \param str string to parse, zero-terminated
263 * \param n_ignored number of format options to skip at the beginning
265 static int process_event_tail(ass_track_t* track, ass_event_t* event, char* str, int n_ignored)
271 ass_event_t* target = event;
273 char* format = strdup(track->event_format);
274 char* q = format; // format scanning pointer
276 if (track->n_styles == 0) {
277 // add "Default" style to the end
278 // will be used if track does not contain a default style (or even does not contain styles at all)
279 int sid = ass_alloc_style(track);
280 track->styles[sid].Name = strdup("Default");
281 track->styles[sid].FontName = strdup("Arial");
284 for (i = 0; i < n_ignored; ++i) {
290 if (strcasecmp(tname, "Text") == 0) {
292 event->Text = strdup(p);
293 if (*event->Text != 0) {
294 last = event->Text + strlen(event->Text) - 1;
295 if (last >= event->Text && *last == '\r')
298 mp_msg(MSGT_ASS, MSGL_DBG2, "Text = %s\n", event->Text);
299 event->Duration -= event->Start;
301 return 0; // "Text" is always the last
305 ALIAS(End,Duration) // temporarily store end timecode in event->Duration
323 * \brief Parse command line style overrides (--ass-force-style option)
324 * \param track track to apply overrides to
325 * The format for overrides is [StyleName.]Field=Value
327 void process_force_style(ass_track_t* track) {
328 char **fs, *eq, *dt, *style, *tname, *token;
331 char** list = track->library->style_overrides;
335 for (fs = list; *fs; ++fs) {
336 eq = strrchr(*fs, '=');
342 if(!strcasecmp(*fs, "PlayResX"))
343 track->PlayResX = atoi(token);
344 else if(!strcasecmp(*fs, "PlayResY"))
345 track->PlayResY = atoi(token);
346 else if(!strcasecmp(*fs, "Timer"))
347 track->Timer = atof(token);
348 else if(!strcasecmp(*fs, "WrapStyle"))
349 track->WrapStyle = atoi(token);
351 dt = strrchr(*fs, '.');
360 for (sid = 0; sid < track->n_styles; ++sid) {
361 if (style == NULL || strcasecmp(track->styles[sid].Name, style) == 0) {
362 target = track->styles + sid;
365 COLORVAL(PrimaryColour)
366 COLORVAL(SecondaryColour)
367 COLORVAL(OutlineColour)
395 * \brief Parse the Style line
397 * \param str string to parse, zero-terminated
398 * Allocates a new style struct.
400 static int process_style(ass_track_t* track, char *str)
407 char* q; // format scanning pointer
412 if (!track->style_format) {
413 // no style format header
414 // probably an ancient script version
415 if (track->track_type == TRACK_TYPE_SSA)
416 track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
417 "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline,"
418 "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
420 track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
421 "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut,"
422 "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow,"
423 "Alignment, MarginL, MarginR, MarginV, Encoding");
426 q = format = strdup(track->style_format);
428 mp_msg(MSGT_ASS, MSGL_V, "[%p] Style: %s\n", track, str);
430 sid = ass_alloc_style(track);
432 style = track->styles + sid;
434 // fill style with some default values
435 style->ScaleX = 100.;
436 style->ScaleY = 100.;
442 // ALIAS(TertiaryColour,OutlineColour) // ignore TertiaryColour; it appears only in SSA, and is overridden by BackColour
446 if ((strcmp(target->Name, "Default")==0) || (strcmp(target->Name, "*Default")==0))
447 track->default_style = sid;
449 COLORVAL(PrimaryColour)
450 COLORVAL(SecondaryColour)
451 COLORVAL(OutlineColour) // TertiaryColor
453 // SSA uses BackColour for both outline and shadow
454 // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
455 if (track->track_type == TRACK_TYPE_SSA)
456 target->OutlineColour = target->BackColour;
466 if (track->track_type == TRACK_TYPE_ASS)
467 target->Alignment = numpad2align(target->Alignment);
478 style->ScaleX /= 100.;
479 style->ScaleY /= 100.;
480 style->Bold = !!style->Bold;
481 style->Italic = !!style->Italic;
482 style->Underline = !!style->Underline;
484 style->Name = strdup("Default");
485 if (!style->FontName)
486 style->FontName = strdup("Arial");
487 // skip '@' at the start of the font name
488 if (*style->FontName == '@') {
490 style->FontName = strdup(p + 1);
498 static int process_styles_line(ass_track_t* track, char *str)
500 if (!strncmp(str,"Format:", 7)) {
503 track->style_format = strdup(p);
504 mp_msg(MSGT_ASS, MSGL_DBG2, "Style format: %s\n", track->style_format);
505 } else if (!strncmp(str,"Style:", 6)) {
508 process_style(track, p);
513 static int process_info_line(ass_track_t* track, char *str)
515 if (!strncmp(str, "PlayResX:", 9)) {
516 track->PlayResX = atoi(str + 9);
517 } else if (!strncmp(str,"PlayResY:", 9)) {
518 track->PlayResY = atoi(str + 9);
519 } else if (!strncmp(str,"Timer:", 6)) {
520 track->Timer = atof(str + 6);
521 } else if (!strncmp(str,"WrapStyle:", 10)) {
522 track->WrapStyle = atoi(str + 10);
527 static int process_events_line(ass_track_t* track, char *str)
529 if (!strncmp(str, "Format:", 7)) {
532 track->event_format = strdup(p);
533 mp_msg(MSGT_ASS, MSGL_DBG2, "Event format: %s\n", track->event_format);
534 } else if (!strncmp(str, "Dialogue:", 9)) {
535 // This should never be reached for embedded subtitles.
536 // They have slightly different format and are parsed in ass_process_chunk,
537 // called directly from demuxer
544 eid = ass_alloc_event(track);
545 event = track->events + eid;
547 process_event_tail(track, event, str, 0);
549 mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s \n", str);
554 // Copied from mkvtoolnix
555 static unsigned char* decode_chars(unsigned char c1, unsigned char c2,
556 unsigned char c3, unsigned char c4, unsigned char* dst, int cnt)
559 unsigned char bytes[3];
562 value = ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 - 33);
563 bytes[2] = value & 0xff;
564 bytes[1] = (value & 0xff00) >> 8;
565 bytes[0] = (value & 0xff0000) >> 16;
567 for (i = 0; i < cnt; ++i)
572 static int decode_font(ass_track_t* track)
577 int size; // original size
578 int dsize; // decoded size
579 unsigned char* buf = 0;
581 mp_msg(MSGT_ASS, MSGL_V, "font: %d bytes encoded data \n", track->parser_priv->fontdata_used);
582 size = track->parser_priv->fontdata_used;
584 mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_BadEncodedDataSize);
585 goto error_decode_font;
587 buf = malloc(size / 4 * 3 + 2);
589 for (i = 0, p = (unsigned char*)track->parser_priv->fontdata; i < size / 4; i++, p+=4) {
590 q = decode_chars(p[0], p[1], p[2], p[3], q, 3);
593 q = decode_chars(p[0], p[1], 0, 0, q, 1);
594 } else if (size % 4 == 3) {
595 q = decode_chars(p[0], p[1], p[2], 0, q, 2);
598 assert(dsize <= size / 4 * 3 + 2);
600 if (track->library->extract_fonts) {
601 ass_add_font(track->library, track->parser_priv->fontname, (char*)buf, dsize);
607 free(track->parser_priv->fontname);
608 free(track->parser_priv->fontdata);
609 track->parser_priv->fontname = 0;
610 track->parser_priv->fontdata = 0;
611 track->parser_priv->fontdata_size = 0;
612 track->parser_priv->fontdata_used = 0;
616 static int process_fonts_line(ass_track_t* track, char *str)
620 if (!strncmp(str, "fontname:", 9)) {
623 if (track->parser_priv->fontname) {
626 track->parser_priv->fontname = strdup(p);
627 mp_msg(MSGT_ASS, MSGL_V, "fontname: %s\n", track->parser_priv->fontname);
631 if (!track->parser_priv->fontname) {
632 mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s \n", str);
638 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FontLineTooLong, len, str);
641 if (track->parser_priv->fontdata_used + len > track->parser_priv->fontdata_size) {
642 track->parser_priv->fontdata_size += 100 * 1024;
643 track->parser_priv->fontdata = realloc(track->parser_priv->fontdata, track->parser_priv->fontdata_size);
645 memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used, str, len);
646 track->parser_priv->fontdata_used += len;
652 * \brief Parse a header line
654 * \param str string to parse, zero-terminated
656 static int process_line(ass_track_t* track, char *str)
658 if (!strncasecmp(str, "[Script Info]", 13)) {
659 track->parser_priv->state = PST_INFO;
660 } else if (!strncasecmp(str, "[V4 Styles]", 11)) {
661 track->parser_priv->state = PST_STYLES;
662 track->track_type = TRACK_TYPE_SSA;
663 } else if (!strncasecmp(str, "[V4+ Styles]", 12)) {
664 track->parser_priv->state = PST_STYLES;
665 track->track_type = TRACK_TYPE_ASS;
666 } else if (!strncasecmp(str, "[Events]", 8)) {
667 track->parser_priv->state = PST_EVENTS;
668 } else if (!strncasecmp(str, "[Fonts]", 7)) {
669 track->parser_priv->state = PST_FONTS;
671 switch (track->parser_priv->state) {
673 process_info_line(track, str);
676 process_styles_line(track, str);
679 process_events_line(track, str);
682 process_fonts_line(track, str);
689 // there is no explicit end-of-font marker in ssa/ass
690 if ((track->parser_priv->state != PST_FONTS) && (track->parser_priv->fontname))
696 static int process_text(ass_track_t* track, char* str)
702 if ((*p=='\r')||(*p=='\n')) ++p;
703 else if (p[0]=='\xef' && p[1]=='\xbb' && p[2]=='\xbf') p+=3; // U+FFFE (BOM)
706 for (q=p; ((*q!='\0')&&(*q!='\r')&&(*q!='\n')); ++q) {};
711 process_line(track, p);
720 * \brief Process CodecPrivate section of subtitle stream
722 * \param data string to parse
723 * \param size length of data
724 CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections
726 void ass_process_codec_private(ass_track_t* track, char *data, int size)
728 char* str = malloc(size + 1);
730 memcpy(str, data, size);
733 process_text(track, str);
736 if (!track->event_format) {
737 // probably an mkv produced by ancient mkvtoolnix
738 // such files don't have [Events] and Format: headers
739 track->parser_priv->state = PST_EVENTS;
740 if (track->track_type == TRACK_TYPE_SSA)
741 track->event_format = strdup("Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text");
743 track->event_format = strdup("Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text");
746 process_force_style(track);
749 static int check_duplicate_event(ass_track_t* track, int ReadOrder)
752 for (i = 0; i<track->n_events - 1; ++i) // ignoring last event, it is the one we are comparing with
753 if (track->events[i].ReadOrder == ReadOrder)
759 * \brief Process a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary).
761 * \param data string to parse
762 * \param size length of data
763 * \param timecode starting time of the event (milliseconds)
764 * \param duration duration of the event (milliseconds)
766 void ass_process_chunk(ass_track_t* track, char *data, int size, long long timecode, long long duration)
774 if (!track->event_format) {
775 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EventFormatHeaderMissing);
779 str = malloc(size + 1);
780 memcpy(str, data, size);
782 mp_msg(MSGT_ASS, MSGL_V, "event at %" PRId64 ", +%" PRId64 ": %s \n", (int64_t)timecode, (int64_t)duration, str);
784 eid = ass_alloc_event(track);
785 event = track->events + eid;
791 event->ReadOrder = atoi(token);
792 if (check_duplicate_event(track, event->ReadOrder))
796 event->Layer = atoi(token);
798 process_event_tail(track, event, p, 3);
800 event->Start = timecode;
801 event->Duration = duration;
808 ass_free_event(track, eid);
814 /** \brief recode buffer to utf-8
815 * constraint: codepage != 0
816 * \param data pointer to text buffer
817 * \param size buffer size
818 * \return a pointer to recoded buffer, caller is responsible for freeing it
820 static char* sub_recode(char* data, size_t size, char* codepage)
822 static iconv_t icdsc = (iconv_t)(-1);
823 char* tocp = "UTF-8";
828 const char* cp_tmp = codepage;
830 char enca_lang[3], enca_fallback[100];
831 if (sscanf(codepage, "enca:%2s:%99s", enca_lang, enca_fallback) == 2
832 || sscanf(codepage, "ENCA:%2s:%99s", enca_lang, enca_fallback) == 2) {
833 cp_tmp = guess_buffer_cp((unsigned char*)data, size, enca_lang, enca_fallback);
836 if ((icdsc = iconv_open (tocp, cp_tmp)) != (iconv_t)(-1)){
837 mp_msg(MSGT_ASS,MSGL_V,"LIBSUB: opened iconv descriptor.\n");
839 mp_msg(MSGT_ASS,MSGL_ERR,MSGTR_LIBASS_ErrorOpeningIconvDescriptor);
845 size_t oleft = size - 1;
851 outbuf = malloc(osize);
857 rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
858 else {// clear the conversion state and leave
860 rc = iconv(icdsc, NULL, NULL, &op, &oleft);
862 if (rc == (size_t)(-1)) {
863 if (errno == E2BIG) {
864 size_t offset = op - outbuf;
865 outbuf = (char*)realloc(outbuf, osize + size);
866 op = outbuf + offset;
870 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorRecodingFile);
877 outbuf[osize - oleft - 1] = 0;
880 if (icdsc != (iconv_t)(-1)) {
881 (void)iconv_close(icdsc);
882 icdsc = (iconv_t)(-1);
883 mp_msg(MSGT_ASS,MSGL_V,"LIBSUB: closed iconv descriptor.\n");
891 * \brief read file contents into newly allocated buffer
892 * \param fname file name
893 * \param bufsize out: file size
894 * \return pointer to file contents. Caller is responsible for its deallocation.
896 static char* read_file(char* fname, size_t *bufsize)
903 FILE* fp = fopen(fname, "rb");
905 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FopenFailed, fname);
908 res = fseek(fp, 0, SEEK_END);
910 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FseekFailed, fname);
918 if (sz > 10*1024*1024) {
919 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_RefusingToLoadSubtitlesLargerThan10M, fname);
924 mp_msg(MSGT_ASS, MSGL_V, "file size: %ld\n", sz);
926 buf = malloc(sz + 1);
930 res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
932 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_ReadFailed, errno, strerror(errno));
938 } while (sz - bytes_read > 0);
948 * \param buf pointer to subtitle text in utf-8
950 static ass_track_t* parse_memory(ass_library_t* library, char* buf)
955 track = ass_new_track(library);
958 process_text(track, buf);
960 // external SSA/ASS subs does not have ReadOrder field
961 for (i = 0; i < track->n_events; ++i)
962 track->events[i].ReadOrder = i;
964 // there is no explicit end-of-font marker in ssa/ass
965 if (track->parser_priv->fontname)
968 if (track->track_type == TRACK_TYPE_UNKNOWN) {
969 ass_free_track(track);
973 process_force_style(track);
979 * \brief Read subtitles from memory.
980 * \param library libass library object
981 * \param buf pointer to subtitles text
982 * \param bufsize size of buffer
983 * \param codepage recode buffer contents from given codepage
984 * \return newly allocated track
986 ass_track_t* ass_read_memory(ass_library_t* library, char* buf, size_t bufsize, char* codepage)
996 buf = sub_recode(buf, bufsize, codepage);
1002 track = parse_memory(library, buf);
1008 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_AddedSubtitleFileMemory, track->n_styles, track->n_events);
1012 char* read_file_recode(char* fname, char* codepage, int* size)
1017 buf = read_file(fname, &bufsize);
1022 char* tmpbuf = sub_recode(buf, bufsize, codepage);
1034 * \brief Read subtitles from file.
1035 * \param library libass library object
1036 * \param fname file name
1037 * \param codepage recode buffer contents from given codepage
1038 * \return newly allocated track
1040 ass_track_t* ass_read_file(ass_library_t* library, char* fname, char* codepage)
1046 buf = read_file_recode(fname, codepage, &bufsize);
1049 track = parse_memory(library, buf);
1054 track->name = strdup(fname);
1056 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_AddedSubtitleFileFname, fname, track->n_styles, track->n_events);
1058 // dump_events(forced_tid);
1063 * \brief read styles from file into already initialized track
1065 int ass_read_styles(ass_track_t* track, char* fname, char* codepage)
1068 parser_state_t old_state;
1071 buf = read_file(fname, &sz);
1077 tmpbuf = sub_recode(buf, sz, codepage);
1085 old_state = track->parser_priv->state;
1086 track->parser_priv->state = PST_STYLES;
1087 process_text(track, buf);
1088 track->parser_priv->state = old_state;
1093 long long ass_step_sub(ass_track_t* track, long long now, int movement) {
1096 if (movement == 0) return 0;
1097 if (track->n_events == 0) return 0;
1100 for (i = 0; (i < track->n_events) && ((long long)(track->events[i].Start + track->events[i].Duration) <= now); ++i) {}
1102 for (i = track->n_events - 1; (i >= 0) && ((long long)(track->events[i].Start) > now); --i) {}
1104 // -1 and n_events are ok
1105 assert(i >= -1); assert(i <= track->n_events);
1108 if (i >= track->n_events) i = track->n_events - 1;
1109 return ((long long)track->events[i].Start) - now;
1112 ass_track_t* ass_new_track(ass_library_t* library) {
1113 ass_track_t* track = calloc(1, sizeof(ass_track_t));
1114 track->library = library;
1115 track->parser_priv = calloc(1, sizeof(parser_priv_t));