2 * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
4 * This file is part of libass.
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
29 #include <sys/types.h>
40 #include "ass_utils.h"
41 #include "ass_library.h"
44 #pragma comment(lib, "libiconv.lib")
45 #pragma comment(lib, "freetype246MT.lib")
46 #pragma comment(lib, "libfribidi.lib")
49 #define ass_atof(STR) (ass_strtod((STR),NULL))
67 #define ASS_STYLES_ALLOC 20
68 #define ASS_EVENTS_ALLOC 200
70 void ass_free_track(ASS_Track *track)
74 if (track->parser_priv) {
75 free(track->parser_priv->fontname);
76 free(track->parser_priv->fontdata);
77 free(track->parser_priv);
79 free(track->style_format);
80 free(track->event_format);
81 free(track->Language);
83 for (i = 0; i < track->n_styles; ++i)
84 ass_free_style(track, i);
88 for (i = 0; i < track->n_events; ++i)
89 ass_free_event(track, i);
96 /// \brief Allocate a new style struct
97 /// \param track track
99 int ass_alloc_style(ASS_Track *track)
103 assert(track->n_styles <= track->max_styles);
105 if (track->n_styles == track->max_styles) {
106 track->max_styles += ASS_STYLES_ALLOC;
108 (ASS_Style *) realloc(track->styles,
113 sid = track->n_styles++;
114 memset(track->styles + sid, 0, sizeof(ASS_Style));
118 /// \brief Allocate a new event struct
119 /// \param track track
121 int ass_alloc_event(ASS_Track *track)
125 assert(track->n_events <= track->max_events);
127 if (track->n_events == track->max_events) {
128 track->max_events += ASS_EVENTS_ALLOC;
130 (ASS_Event *) realloc(track->events,
135 eid = track->n_events++;
136 memset(track->events + eid, 0, sizeof(ASS_Event));
140 void ass_free_event(ASS_Track *track, int eid)
142 ASS_Event *event = track->events + eid;
147 free(event->render_priv);
150 void ass_free_style(ASS_Track *track, int sid)
152 ASS_Style *style = track->styles + sid;
155 free(style->FontName);
158 // ==============================================================================================
160 static void skip_spaces(char **str)
163 while ((*p == ' ') || (*p == '\t'))
168 static void rskip_spaces(char **str, char *limit)
171 while ((p >= limit) && ((*p == ' ') || (*p == '\t')))
177 * \brief Set up default style
178 * \param style style to edit to defaults
179 * The parameters are mostly taken directly from VSFilter source for
180 * best compatibility.
182 static void set_default_style(ASS_Style *style)
184 style->Name = strdup("Default");
185 style->FontName = strdup("Arial");
186 style->FontSize = 18;
187 style->PrimaryColour = 0xffffff00;
188 style->SecondaryColour = 0x00ffff00;
189 style->OutlineColour = 0x00000000;
190 style->BackColour = 0x00000080;
195 style->BorderStyle = 1;
198 style->Alignment = 2;
199 style->MarginL = style->MarginR = style->MarginV = 20;
203 * \brief find style by name
205 * \param name style name
206 * \return index in track->styles
207 * Returnes 0 if no styles found => expects at least 1 style.
208 * Parsing code always adds "Default" style in the end.
210 static int lookup_style(ASS_Track *track, char *name)
214 ++name; // FIXME: what does '*' really mean ?
215 for (i = track->n_styles - 1; i >= 0; --i) {
216 if (strcmp(track->styles[i].Name, name) == 0)
219 i = track->default_style;
220 ass_msg(track->library, MSGL_WARN,
221 "[%p]: Warning: no style named '%s' found, using '%s'",
222 track, name, track->styles[i].Name);
223 return i; // use the first style
226 static uint32_t string2color(ASS_Library *library, char *p)
229 (void) strtocolor(library, &p, &tmp, 0);
233 static long long string2timecode(ASS_Library *library, char *p)
235 unsigned h, m, s, ms;
237 int res = sscanf(p, "%1d:%2d:%2d.%2d", &h, &m, &s, &ms);
239 ass_msg(library, MSGL_WARN, "Bad timestamp");
242 tm = ((h * 60 + m) * 60 + s) * 1000 + ms * 10;
247 * \brief converts numpad-style align to align.
249 static int numpad2align(int val)
252 v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment
255 res = ((val - 1) % 3) + 1; // horizontal alignment
260 #define NEXT(str,token) \
261 token = next_token(&str); \
264 #define ANYVAL(name,func) \
265 } else if (strcasecmp(tname, #name) == 0) { \
266 target->name = func(token); \
267 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
269 #define STRVAL(name) \
270 } else if (strcasecmp(tname, #name) == 0) { \
271 if (target->name != NULL) free(target->name); \
272 target->name = strdup(token); \
273 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
275 #define COLORVAL(name) \
276 } else if (strcasecmp(tname, #name) == 0) { \
277 target->name = string2color(track->library, token); \
278 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
280 #define INTVAL(name) ANYVAL(name,atoi)
281 #define FPVAL(name) ANYVAL(name,ass_atof)
282 #define TIMEVAL(name) \
283 } else if (strcasecmp(tname, #name) == 0) { \
284 target->name = string2timecode(track->library, token); \
285 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
287 #define STYLEVAL(name) \
288 } else if (strcasecmp(tname, #name) == 0) { \
289 target->name = lookup_style(track, token); \
290 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
292 #define ALIAS(alias,name) \
293 if (strcasecmp(tname, #alias) == 0) {tname = #name;}
295 static char *next_token(char **str)
304 start = p; // start of the token
305 for (; (*p != '\0') && (*p != ','); ++p) {
308 *str = p; // eos found, str will point to '\0' at exit
311 *str = p + 1; // ',' found, str will point to the next char (beginning of the next token)
313 --p; // end of current token
314 rskip_spaces(&p, start);
316 p = start; // empty token
318 ++p; // the first space character, or '\0'
324 * \brief Parse the tail of Dialogue line
326 * \param event parsed data goes here
327 * \param str string to parse, zero-terminated
328 * \param n_ignored number of format options to skip at the beginning
330 static int process_event_tail(ASS_Track *track, ASS_Event *event,
331 char *str, int n_ignored)
337 ASS_Event *target = event;
339 char *format = strdup(track->event_format);
340 char *q = format; // format scanning pointer
342 if (track->n_styles == 0) {
343 // add "Default" style to the end
344 // will be used if track does not contain a default style (or even does not contain styles at all)
345 int sid = ass_alloc_style(track);
346 set_default_style(&track->styles[sid]);
347 track->default_style = sid;
350 for (i = 0; i < n_ignored; ++i) {
356 if (strcasecmp(tname, "Text") == 0) {
358 event->Text = strdup(p);
359 if (*event->Text != 0) {
360 last = event->Text + strlen(event->Text) - 1;
361 if (last >= event->Text && *last == '\r')
364 ass_msg(track->library, MSGL_DBG2, "Text = %s", event->Text);
365 event->Duration -= event->Start;
367 return 0; // "Text" is always the last
371 ALIAS(End, Duration) // temporarily store end timecode in event->Duration
389 * \brief Parse command line style overrides (--ass-force-style option)
390 * \param track track to apply overrides to
391 * The format for overrides is [StyleName.]Field=Value
393 void ass_process_force_style(ASS_Track *track)
395 char **fs, *eq, *dt, *style, *tname, *token;
398 char **list = track->library->style_overrides;
403 for (fs = list; *fs; ++fs) {
404 eq = strrchr(*fs, '=');
410 if (!strcasecmp(*fs, "PlayResX"))
411 track->PlayResX = atoi(token);
412 else if (!strcasecmp(*fs, "PlayResY"))
413 track->PlayResY = atoi(token);
414 else if (!strcasecmp(*fs, "Timer"))
415 track->Timer = ass_atof(token);
416 else if (!strcasecmp(*fs, "WrapStyle"))
417 track->WrapStyle = atoi(token);
418 else if (!strcasecmp(*fs, "ScaledBorderAndShadow"))
419 track->ScaledBorderAndShadow = parse_bool(token);
420 else if (!strcasecmp(*fs, "Kerning"))
421 track->Kerning = parse_bool(token);
423 dt = strrchr(*fs, '.');
432 for (sid = 0; sid < track->n_styles; ++sid) {
434 || strcasecmp(track->styles[sid].Name, style) == 0) {
435 target = track->styles + sid;
438 COLORVAL(PrimaryColour)
439 COLORVAL(SecondaryColour)
440 COLORVAL(OutlineColour)
469 * \brief Parse the Style line
471 * \param str string to parse, zero-terminated
472 * Allocates a new style struct.
474 static int process_style(ASS_Track *track, char *str)
481 char *q; // format scanning pointer
486 if (!track->style_format) {
487 // no style format header
488 // probably an ancient script version
489 if (track->track_type == TRACK_TYPE_SSA)
490 track->style_format =
492 ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
493 "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline,"
494 "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
496 track->style_format =
498 ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
499 "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut,"
500 "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow,"
501 "Alignment, MarginL, MarginR, MarginV, Encoding");
504 q = format = strdup(track->style_format);
506 // Add default style first
507 if (track->n_styles == 0) {
508 // will be used if track does not contain a default style (or even does not contain styles at all)
509 int sid = ass_alloc_style(track);
510 set_default_style(&track->styles[sid]);
511 track->default_style = sid;
514 ass_msg(track->library, MSGL_V, "[%p] Style: %s", track, str);
516 sid = ass_alloc_style(track);
518 style = track->styles + sid;
521 // fill style with some default values
522 style->ScaleX = 100.;
523 style->ScaleY = 100.;
531 if ((strcmp(target->Name, "Default") == 0)
532 || (strcmp(target->Name, "*Default") == 0))
533 track->default_style = sid;
535 COLORVAL(PrimaryColour)
536 COLORVAL(SecondaryColour)
537 COLORVAL(OutlineColour) // TertiaryColor
539 // SSA uses BackColour for both outline and shadow
540 // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
541 if (track->track_type == TRACK_TYPE_SSA)
542 target->OutlineColour = target->BackColour;
552 if (track->track_type == TRACK_TYPE_ASS)
553 target->Alignment = numpad2align(target->Alignment);
564 style->ScaleX /= 100.;
565 style->ScaleY /= 100.;
566 style->Bold = !!style->Bold;
567 style->Italic = !!style->Italic;
568 style->Underline = !!style->Underline;
570 style->Name = strdup("Default");
571 if (!style->FontName)
572 style->FontName = strdup("Arial");
578 static int process_styles_line(ASS_Track *track, char *str)
580 if (!strncmp(str, "Format:", 7)) {
583 track->style_format = strdup(p);
584 ass_msg(track->library, MSGL_DBG2, "Style format: %s",
585 track->style_format);
586 } else if (!strncmp(str, "Style:", 6)) {
589 process_style(track, p);
594 static int process_info_line(ASS_Track *track, char *str)
596 if (!strncmp(str, "PlayResX:", 9)) {
597 track->PlayResX = atoi(str + 9);
598 } else if (!strncmp(str, "PlayResY:", 9)) {
599 track->PlayResY = atoi(str + 9);
600 } else if (!strncmp(str, "Timer:", 6)) {
601 track->Timer = ass_atof(str + 6);
602 } else if (!strncmp(str, "WrapStyle:", 10)) {
603 track->WrapStyle = atoi(str + 10);
604 } else if (!strncmp(str, "ScaledBorderAndShadow:", 22)) {
605 track->ScaledBorderAndShadow = parse_bool(str + 22);
606 } else if (!strncmp(str, "Kerning:", 8)) {
607 track->Kerning = parse_bool(str + 8);
608 } else if (!strncmp(str, "Language:", 9)) {
610 while (*p && isspace(*p)) p++;
611 track->Language = malloc(3);
612 strncpy(track->Language, p, 2);
613 track->Language[2] = 0;
618 static void event_format_fallback(ASS_Track *track)
620 track->parser_priv->state = PST_EVENTS;
621 if (track->track_type == TRACK_TYPE_SSA)
622 track->event_format = strdup("Format: Marked, Start, End, Style, "
623 "Name, MarginL, MarginR, MarginV, Effect, Text");
625 track->event_format = strdup("Format: Layer, Start, End, Style, "
626 "Actor, MarginL, MarginR, MarginV, Effect, Text");
627 ass_msg(track->library, MSGL_V,
628 "No event format found, using fallback");
631 static int process_events_line(ASS_Track *track, char *str)
633 if (!strncmp(str, "Format:", 7)) {
636 free(track->event_format);
637 track->event_format = strdup(p);
638 ass_msg(track->library, MSGL_DBG2, "Event format: %s", track->event_format);
639 } else if (!strncmp(str, "Dialogue:", 9)) {
640 // This should never be reached for embedded subtitles.
641 // They have slightly different format and are parsed in ass_process_chunk,
642 // called directly from demuxer
649 eid = ass_alloc_event(track);
650 event = track->events + eid;
652 // We can't parse events with event_format
653 if (!track->event_format)
654 event_format_fallback(track);
656 process_event_tail(track, event, str, 0);
658 ass_msg(track->library, MSGL_V, "Not understood: '%.30s'", str);
663 // Copied from mkvtoolnix
664 static unsigned char *decode_chars(unsigned char c1, unsigned char c2,
665 unsigned char c3, unsigned char c4,
666 unsigned char *dst, int cnt)
669 unsigned char bytes[3];
673 ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 -
675 bytes[2] = value & 0xff;
676 bytes[1] = (value & 0xff00) >> 8;
677 bytes[0] = (value & 0xff0000) >> 16;
679 for (i = 0; i < cnt; ++i)
684 static int decode_font(ASS_Track *track)
689 int size; // original size
690 int dsize; // decoded size
691 unsigned char *buf = 0;
693 ass_msg(track->library, MSGL_V, "Font: %d bytes encoded data",
694 track->parser_priv->fontdata_used);
695 size = track->parser_priv->fontdata_used;
697 ass_msg(track->library, MSGL_ERR, "Bad encoded data size");
698 goto error_decode_font;
700 buf = malloc(size / 4 * 3 + 2);
702 for (i = 0, p = (unsigned char *) track->parser_priv->fontdata;
703 i < size / 4; i++, p += 4) {
704 q = decode_chars(p[0], p[1], p[2], p[3], q, 3);
707 q = decode_chars(p[0], p[1], 0, 0, q, 1);
708 } else if (size % 4 == 3) {
709 q = decode_chars(p[0], p[1], p[2], 0, q, 2);
712 assert(dsize <= size / 4 * 3 + 2);
714 if (track->library->extract_fonts) {
715 ass_add_font(track->library, track->parser_priv->fontname,
716 (char *) buf, dsize);
721 free(track->parser_priv->fontname);
722 free(track->parser_priv->fontdata);
723 track->parser_priv->fontname = 0;
724 track->parser_priv->fontdata = 0;
725 track->parser_priv->fontdata_size = 0;
726 track->parser_priv->fontdata_used = 0;
730 static int process_fonts_line(ASS_Track *track, char *str)
734 if (!strncmp(str, "fontname:", 9)) {
737 if (track->parser_priv->fontname) {
740 track->parser_priv->fontname = strdup(p);
741 ass_msg(track->library, MSGL_V, "Fontname: %s",
742 track->parser_priv->fontname);
746 if (!track->parser_priv->fontname) {
747 ass_msg(track->library, MSGL_V, "Not understood: '%s'", str);
753 ass_msg(track->library, MSGL_WARN, "Font line too long: %d, %s",
757 if (track->parser_priv->fontdata_used + len >
758 track->parser_priv->fontdata_size) {
759 track->parser_priv->fontdata_size += 100 * 1024;
760 track->parser_priv->fontdata =
761 realloc(track->parser_priv->fontdata,
762 track->parser_priv->fontdata_size);
764 memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used,
766 track->parser_priv->fontdata_used += len;
772 * \brief Parse a header line
774 * \param str string to parse, zero-terminated
776 static int process_line(ASS_Track *track, char *str)
778 if (!strncasecmp(str, "[Script Info]", 13)) {
779 track->parser_priv->state = PST_INFO;
780 } else if (!strncasecmp(str, "[V4 Styles]", 11)) {
781 track->parser_priv->state = PST_STYLES;
782 track->track_type = TRACK_TYPE_SSA;
783 } else if (!strncasecmp(str, "[V4+ Styles]", 12)) {
784 track->parser_priv->state = PST_STYLES;
785 track->track_type = TRACK_TYPE_ASS;
786 } else if (!strncasecmp(str, "[Events]", 8)) {
787 track->parser_priv->state = PST_EVENTS;
788 } else if (!strncasecmp(str, "[Fonts]", 7)) {
789 track->parser_priv->state = PST_FONTS;
791 switch (track->parser_priv->state) {
793 process_info_line(track, str);
796 process_styles_line(track, str);
799 process_events_line(track, str);
802 process_fonts_line(track, str);
809 // there is no explicit end-of-font marker in ssa/ass
810 if ((track->parser_priv->state != PST_FONTS)
811 && (track->parser_priv->fontname))
817 static int process_text(ASS_Track *track, char *str)
823 if ((*p == '\r') || (*p == '\n'))
825 else if (p[0] == '\xef' && p[1] == '\xbb' && p[2] == '\xbf')
826 p += 3; // U+FFFE (BOM)
830 for (q = p; ((*q != '\0') && (*q != '\r') && (*q != '\n')); ++q) {
836 process_line(track, p);
845 * \brief Process a chunk of subtitle stream data.
847 * \param data string to parse
848 * \param size length of data
850 void ass_process_data(ASS_Track *track, char *data, int size)
852 char *str = malloc(size + 1);
854 memcpy(str, data, size);
857 ass_msg(track->library, MSGL_V, "Event: %s", str);
858 process_text(track, str);
863 * \brief Process CodecPrivate section of subtitle stream
865 * \param data string to parse
866 * \param size length of data
867 CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections
869 void ass_process_codec_private(ASS_Track *track, char *data, int size)
871 ass_process_data(track, data, size);
873 // probably an mkv produced by ancient mkvtoolnix
874 // such files don't have [Events] and Format: headers
875 if (!track->event_format)
876 event_format_fallback(track);
878 ass_process_force_style(track);
881 static int check_duplicate_event(ASS_Track *track, int ReadOrder)
884 for (i = 0; i < track->n_events - 1; ++i) // ignoring last event, it is the one we are comparing with
885 if (track->events[i].ReadOrder == ReadOrder)
891 * \brief Process a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary).
893 * \param data string to parse
894 * \param size length of data
895 * \param timecode starting time of the event (milliseconds)
896 * \param duration duration of the event (milliseconds)
898 void ass_process_chunk(ASS_Track *track, char *data, int size,
899 long long timecode, long long duration)
907 if (!track->event_format) {
908 ass_msg(track->library, MSGL_WARN, "Event format header missing");
912 str = malloc(size + 1);
913 memcpy(str, data, size);
915 ass_msg(track->library, MSGL_V, "Event at %" PRId64 ", +%" PRId64 ": %s",
916 (int64_t) timecode, (int64_t) duration, str);
918 eid = ass_alloc_event(track);
919 event = track->events + eid;
925 event->ReadOrder = atoi(token);
926 if (check_duplicate_event(track, event->ReadOrder))
930 event->Layer = atoi(token);
932 process_event_tail(track, event, p, 3);
934 event->Start = timecode;
935 event->Duration = duration;
942 ass_free_event(track, eid);
948 * \brief Flush buffered events.
951 void ass_flush_events(ASS_Track *track)
955 for (eid = 0; eid < track->n_events; eid++)
956 ass_free_event(track, eid);
962 /** \brief recode buffer to utf-8
963 * constraint: codepage != 0
964 * \param data pointer to text buffer
965 * \param size buffer size
966 * \return a pointer to recoded buffer, caller is responsible for freeing it
968 static char *sub_recode(ASS_Library *library, char *data, size_t size,
972 char *tocp = "UTF-8";
977 const char *cp_tmp = codepage;
979 char enca_lang[3], enca_fallback[100];
980 if (sscanf(codepage, "enca:%2s:%99s", enca_lang, enca_fallback) == 2
981 || sscanf(codepage, "ENCA:%2s:%99s", enca_lang,
982 enca_fallback) == 2) {
984 ass_guess_buffer_cp(library, (unsigned char *) data, size,
985 enca_lang, enca_fallback);
988 if ((icdsc = iconv_open(tocp, cp_tmp)) != (iconv_t) (-1)) {
989 ass_msg(library, MSGL_V, "Opened iconv descriptor");
991 ass_msg(library, MSGL_ERR, "Error opening iconv descriptor");
997 size_t oleft = size - 1;
1003 outbuf = malloc(osize);
1009 rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
1010 else { // clear the conversion state and leave
1012 rc = iconv(icdsc, NULL, NULL, &op, &oleft);
1014 if (rc == (size_t) (-1)) {
1015 if (errno == E2BIG) {
1016 size_t offset = op - outbuf;
1017 outbuf = (char *) realloc(outbuf, osize + size);
1018 op = outbuf + offset;
1022 ass_msg(library, MSGL_WARN, "Error recoding file");
1028 outbuf[osize - oleft - 1] = 0;
1031 if (icdsc != (iconv_t) (-1)) {
1032 (void) iconv_close(icdsc);
1033 icdsc = (iconv_t) (-1);
1034 ass_msg(library, MSGL_V, "Closed iconv descriptor");
1042 * \brief read file contents into newly allocated buffer
1043 * \param fname file name
1044 * \param bufsize out: file size
1045 * \return pointer to file contents. Caller is responsible for its deallocation.
1047 static char *read_file(ASS_Library *library, char *fname, size_t *bufsize)
1054 FILE *fp = fopen(fname, "rb");
1056 ass_msg(library, MSGL_WARN,
1057 "ass_read_file(%s): fopen failed", fname);
1060 res = fseek(fp, 0, SEEK_END);
1062 ass_msg(library, MSGL_WARN,
1063 "ass_read_file(%s): fseek failed", fname);
1071 ass_msg(library, MSGL_V, "File size: %ld", sz);
1073 buf = malloc(sz + 1);
1077 res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
1079 ass_msg(library, MSGL_INFO, "Read failed, %d: %s", errno,
1086 } while (sz - bytes_read > 0);
1096 * \param buf pointer to subtitle text in utf-8
1098 static ASS_Track *parse_memory(ASS_Library *library, char *buf)
1103 track = ass_new_track(library);
1106 process_text(track, buf);
1108 // external SSA/ASS subs does not have ReadOrder field
1109 for (i = 0; i < track->n_events; ++i)
1110 track->events[i].ReadOrder = i;
1112 // there is no explicit end-of-font marker in ssa/ass
1113 if (track->parser_priv->fontname)
1116 if (track->track_type == TRACK_TYPE_UNKNOWN) {
1117 ass_free_track(track);
1121 ass_process_force_style(track);
1127 * \brief Read subtitles from memory.
1128 * \param library libass library object
1129 * \param buf pointer to subtitles text
1130 * \param bufsize size of buffer
1131 * \param codepage recode buffer contents from given codepage
1132 * \return newly allocated track
1134 ASS_Track *ass_read_memory(ASS_Library *library, char *buf,
1135 size_t bufsize, char *codepage)
1145 buf = sub_recode(library, buf, bufsize, codepage);
1152 track = parse_memory(library, buf);
1158 ass_msg(library, MSGL_INFO, "Added subtitle file: "
1159 "<memory> (%d styles, %d events)",
1160 track->n_styles, track->n_events);
1164 static char *read_file_recode(ASS_Library *library, char *fname,
1165 char *codepage, size_t *size)
1170 buf = read_file(library, fname, &bufsize);
1175 char *tmpbuf = sub_recode(library, buf, bufsize, codepage);
1187 * \brief Read subtitles from file.
1188 * \param library libass library object
1189 * \param fname file name
1190 * \param codepage recode buffer contents from given codepage
1191 * \return newly allocated track
1193 ASS_Track *ass_read_file(ASS_Library *library, char *fname,
1200 buf = read_file_recode(library, fname, codepage, &bufsize);
1203 track = parse_memory(library, buf);
1208 track->name = strdup(fname);
1210 ass_msg(library, MSGL_INFO,
1211 "Added subtitle file: '%s' (%d styles, %d events)",
1212 fname, track->n_styles, track->n_events);
1218 * \brief read styles from file into already initialized track
1220 int ass_read_styles(ASS_Track *track, char *fname, char *codepage)
1223 ParserState old_state;
1226 buf = read_file(track->library, fname, &sz);
1232 tmpbuf = sub_recode(track->library, buf, sz, codepage);
1240 old_state = track->parser_priv->state;
1241 track->parser_priv->state = PST_STYLES;
1242 process_text(track, buf);
1243 track->parser_priv->state = old_state;
1248 long long ass_step_sub(ASS_Track *track, long long now, int movement)
1254 if (track->n_events == 0)
1259 (i < track->n_events)
1261 ((long long) (track->events[i].Start +
1262 track->events[i].Duration) <= now); ++i) {
1264 for (i = track->n_events - 1;
1265 (i >= 0) && ((long long) (track->events[i].Start) > now);
1269 // -1 and n_events are ok
1271 assert(i <= track->n_events);
1275 if (i >= track->n_events)
1276 i = track->n_events - 1;
1277 return ((long long) track->events[i].Start) - now;
1280 ASS_Track *ass_new_track(ASS_Library *library)
1282 ASS_Track *track = calloc(1, sizeof(ASS_Track));
1283 track->library = library;
1284 track->ScaledBorderAndShadow = 1;
1285 track->parser_priv = calloc(1, sizeof(ASS_ParserPriv));
1290 * \brief Prepare track for rendering
1292 void ass_lazy_track_init(ASS_Library *lib, ASS_Track *track)
1294 if (track->PlayResX && track->PlayResY)
1296 if (!track->PlayResX && !track->PlayResY) {
1297 ass_msg(lib, MSGL_WARN,
1298 "Neither PlayResX nor PlayResY defined. Assuming 384x288");
1299 track->PlayResX = 384;
1300 track->PlayResY = 288;
1302 if (!track->PlayResY && track->PlayResX == 1280) {
1303 track->PlayResY = 1024;
1304 ass_msg(lib, MSGL_WARN,
1305 "PlayResY undefined, setting to %d", track->PlayResY);
1306 } else if (!track->PlayResY) {
1307 track->PlayResY = track->PlayResX * 3 / 4;
1308 ass_msg(lib, MSGL_WARN,
1309 "PlayResY undefined, setting to %d", track->PlayResY);
1310 } else if (!track->PlayResX && track->PlayResY == 1024) {
1311 track->PlayResX = 1280;
1312 ass_msg(lib, MSGL_WARN,
1313 "PlayResX undefined, setting to %d", track->PlayResX);
1314 } else if (!track->PlayResX) {
1315 track->PlayResX = track->PlayResY * 4 / 3;
1316 ass_msg(lib, MSGL_WARN,
1317 "PlayResX undefined, setting to %d", track->PlayResX);