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.
20 #include "ass_compat.h"
28 #include <sys/types.h>
37 #include "ass_utils.h"
38 #include "ass_library.h"
39 #include "ass_string.h"
41 #define ass_atof(STR) (ass_strtod((STR),NULL))
58 // contains bitmap of ReadOrder IDs of all read events
59 uint32_t *read_order_bitmap;
60 int read_order_elems; // size in uint32_t units of read_order_bitmap
64 #define ASS_STYLES_ALLOC 20
66 int ass_library_version(void)
68 return LIBASS_VERSION;
71 void ass_free_track(ASS_Track *track)
75 if (track->parser_priv) {
76 free(track->parser_priv->read_order_bitmap);
77 free(track->parser_priv->fontname);
78 free(track->parser_priv->fontdata);
79 free(track->parser_priv);
81 free(track->style_format);
82 free(track->event_format);
83 free(track->Language);
85 for (i = 0; i < track->n_styles; ++i)
86 ass_free_style(track, i);
90 for (i = 0; i < track->n_events; ++i)
91 ass_free_event(track, i);
98 /// \brief Allocate a new style struct
99 /// \param track track
101 int ass_alloc_style(ASS_Track *track)
105 assert(track->n_styles <= track->max_styles);
107 if (track->n_styles == track->max_styles) {
108 track->max_styles += ASS_STYLES_ALLOC;
110 (ASS_Style *) realloc(track->styles,
115 sid = track->n_styles++;
116 memset(track->styles + sid, 0, sizeof(ASS_Style));
120 /// \brief Allocate a new event struct
121 /// \param track track
123 int ass_alloc_event(ASS_Track *track)
127 assert(track->n_events <= track->max_events);
129 if (track->n_events == track->max_events) {
130 track->max_events = track->max_events * 2 + 1;
132 (ASS_Event *) realloc(track->events,
137 eid = track->n_events++;
138 memset(track->events + eid, 0, sizeof(ASS_Event));
142 void ass_free_event(ASS_Track *track, int eid)
144 ASS_Event *event = track->events + eid;
149 free(event->render_priv);
152 void ass_free_style(ASS_Track *track, int sid)
154 ASS_Style *style = track->styles + sid;
157 free(style->FontName);
160 static int resize_read_order_bitmap(ASS_Track *track, int max_id)
162 // Don't allow malicious files to OOM us easily. Also avoids int overflows.
163 if (max_id < 0 || max_id >= 10 * 1024 * 1024 * 8)
165 if (max_id >= track->parser_priv->read_order_elems * 32) {
166 int oldelems = track->parser_priv->read_order_elems;
167 int elems = ((max_id + 31) / 32 + 1) * 2;
168 assert(elems >= oldelems);
169 track->parser_priv->read_order_elems = elems;
171 realloc(track->parser_priv->read_order_bitmap, elems * 4);
174 track->parser_priv->read_order_bitmap = new_bitmap;
175 memset(track->parser_priv->read_order_bitmap + oldelems, 0,
176 (elems - oldelems) * 4);
181 free(track->parser_priv->read_order_bitmap);
182 track->parser_priv->read_order_bitmap = NULL;
183 track->parser_priv->read_order_elems = 0;
187 static int test_and_set_read_order_bit(ASS_Track *track, int id)
189 if (resize_read_order_bitmap(track, id) < 0)
192 int bit = 1 << (id % 32);
193 if (track->parser_priv->read_order_bitmap[index] & bit)
195 track->parser_priv->read_order_bitmap[index] |= bit;
199 // ==============================================================================================
202 * \brief Set up default style
203 * \param style style to edit to defaults
204 * The parameters are mostly taken directly from VSFilter source for
205 * best compatibility.
207 static void set_default_style(ASS_Style *style)
209 style->Name = strdup("Default");
210 style->FontName = strdup("Arial");
211 style->FontSize = 18;
212 style->PrimaryColour = 0xffffff00;
213 style->SecondaryColour = 0x00ffff00;
214 style->OutlineColour = 0x00000000;
215 style->BackColour = 0x00000080;
220 style->BorderStyle = 1;
223 style->Alignment = 2;
224 style->MarginL = style->MarginR = style->MarginV = 20;
227 static long long string2timecode(ASS_Library *library, char *p)
231 int res = sscanf(p, "%d:%d:%d.%d", &h, &m, &s, &ms);
233 ass_msg(library, MSGL_WARN, "Bad timestamp");
236 tm = ((h * 60LL + m) * 60 + s) * 1000 + ms * 10;
241 * \brief converts numpad-style align to align.
243 static int numpad2align(int val)
246 // Pick an alignment somewhat arbitrarily. VSFilter handles
247 // INT32_MIN as a mix of 1, 2 and 3, so prefer one of those values.
252 int res = ((val - 1) % 3) + 1; // horizontal alignment
256 res |= VALIGN_CENTER;
262 #define NEXT(str,token) \
263 token = next_token(&str); \
267 #define ALIAS(alias,name) \
268 if (ass_strcasecmp(tname, #alias) == 0) {tname = #name;}
270 /* One section started with PARSE_START and PARSE_END parses a single token
271 * (contained in the variable named token) for the header indicated by the
272 * variable tname. It does so by chaining a number of else-if statements, each
273 * of which checks if the tname variable indicates that this header should be
274 * parsed. The first parameter of the macro gives the name of the header.
276 * The string that is passed is in str. str is advanced to the next token if
277 * a header could be parsed. The parsed results are stored in the variable
278 * target, which has the type ASS_Style* or ASS_Event*.
280 #define PARSE_START if (0) {
283 #define ANYVAL(name,func) \
284 } else if (ass_strcasecmp(tname, #name) == 0) { \
285 target->name = func(token);
287 #define STRVAL(name) \
288 } else if (ass_strcasecmp(tname, #name) == 0) { \
289 if (target->name != NULL) free(target->name); \
290 target->name = strdup(token);
292 #define STARREDSTRVAL(name) \
293 } else if (ass_strcasecmp(tname, #name) == 0) { \
294 if (target->name != NULL) free(target->name); \
295 while (*token == '*') ++token; \
296 target->name = strdup(token);
298 #define COLORVAL(name) ANYVAL(name,parse_color_header)
299 #define INTVAL(name) ANYVAL(name,atoi)
300 #define FPVAL(name) ANYVAL(name,ass_atof)
301 #define TIMEVAL(name) \
302 } else if (ass_strcasecmp(tname, #name) == 0) { \
303 target->name = string2timecode(track->library, token);
305 #define STYLEVAL(name) \
306 } else if (ass_strcasecmp(tname, #name) == 0) { \
307 target->name = lookup_style(track, token);
309 static char *next_token(char **str)
318 start = p; // start of the token
319 for (; (*p != '\0') && (*p != ','); ++p) {
322 *str = p; // eos found, str will point to '\0' at exit
325 *str = p + 1; // ',' found, str will point to the next char (beginning of the next token)
327 rskip_spaces(&p, start); // end of current token: the first space character, or '\0'
333 * \brief Parse the tail of Dialogue line
335 * \param event parsed data goes here
336 * \param str string to parse, zero-terminated
337 * \param n_ignored number of format options to skip at the beginning
339 static int process_event_tail(ASS_Track *track, ASS_Event *event,
340 char *str, int n_ignored)
346 ASS_Event *target = event;
348 char *format = strdup(track->event_format);
349 char *q = format; // format scanning pointer
351 if (track->n_styles == 0) {
352 // add "Default" style to the end
353 // will be used if track does not contain a default style (or even does not contain styles at all)
354 int sid = ass_alloc_style(track);
355 set_default_style(&track->styles[sid]);
356 track->default_style = sid;
359 for (i = 0; i < n_ignored; ++i) {
365 if (ass_strcasecmp(tname, "Text") == 0) {
367 event->Text = strdup(p);
368 if (*event->Text != 0) {
369 last = event->Text + strlen(event->Text) - 1;
370 if (last >= event->Text && *last == '\r')
373 event->Duration -= event->Start;
375 return 0; // "Text" is always the last
379 ALIAS(End, Duration) // temporarily store end timecode in event->Duration
397 * \brief Parse command line style overrides (--ass-force-style option)
398 * \param track track to apply overrides to
399 * The format for overrides is [StyleName.]Field=Value
401 void ass_process_force_style(ASS_Track *track)
403 char **fs, *eq, *dt, *style, *tname, *token;
406 char **list = track->library->style_overrides;
411 for (fs = list; *fs; ++fs) {
412 eq = strrchr(*fs, '=');
418 if (!ass_strcasecmp(*fs, "PlayResX"))
419 track->PlayResX = atoi(token);
420 else if (!ass_strcasecmp(*fs, "PlayResY"))
421 track->PlayResY = atoi(token);
422 else if (!ass_strcasecmp(*fs, "Timer"))
423 track->Timer = ass_atof(token);
424 else if (!ass_strcasecmp(*fs, "WrapStyle"))
425 track->WrapStyle = atoi(token);
426 else if (!ass_strcasecmp(*fs, "ScaledBorderAndShadow"))
427 track->ScaledBorderAndShadow = parse_bool(token);
428 else if (!ass_strcasecmp(*fs, "Kerning"))
429 track->Kerning = parse_bool(token);
430 else if (!ass_strcasecmp(*fs, "YCbCr Matrix"))
431 track->YCbCrMatrix = parse_ycbcr_matrix(token);
433 dt = strrchr(*fs, '.');
442 for (sid = 0; sid < track->n_styles; ++sid) {
444 || ass_strcasecmp(track->styles[sid].Name, style) == 0) {
445 target = track->styles + sid;
448 COLORVAL(PrimaryColour)
449 COLORVAL(SecondaryColour)
450 COLORVAL(OutlineColour)
481 * \brief Parse the Style line
483 * \param str string to parse, zero-terminated
484 * Allocates a new style struct.
486 static int process_style(ASS_Track *track, char *str)
493 char *q; // format scanning pointer
498 if (!track->style_format) {
499 // no style format header
500 // probably an ancient script version
501 if (track->track_type == TRACK_TYPE_SSA)
502 track->style_format =
504 ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
505 "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline,"
506 "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
508 track->style_format =
510 ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
511 "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut,"
512 "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow,"
513 "Alignment, MarginL, MarginR, MarginV, Encoding");
516 q = format = strdup(track->style_format);
518 // Add default style first
519 if (track->n_styles == 0) {
520 // will be used if track does not contain a default style (or even does not contain styles at all)
521 int sid = ass_alloc_style(track);
522 set_default_style(&track->styles[sid]);
523 track->default_style = sid;
526 ass_msg(track->library, MSGL_V, "[%p] Style: %s", track, str);
528 sid = ass_alloc_style(track);
530 style = track->styles + sid;
533 // fill style with some default values
534 style->ScaleX = 100.;
535 style->ScaleY = 100.;
543 if (strcmp(target->Name, "Default") == 0)
544 track->default_style = sid;
546 COLORVAL(PrimaryColour)
547 COLORVAL(SecondaryColour)
548 COLORVAL(OutlineColour) // TertiaryColor
550 // SSA uses BackColour for both outline and shadow
551 // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
552 if (track->track_type == TRACK_TYPE_SSA)
553 target->OutlineColour = target->BackColour;
563 if (track->track_type == TRACK_TYPE_ASS)
564 target->Alignment = numpad2align(target->Alignment);
565 // VSFilter compatibility
566 else if (target->Alignment == 8)
567 target->Alignment = 3;
568 else if (target->Alignment == 4)
569 target->Alignment = 11;
580 style->ScaleX = FFMAX(style->ScaleX, 0.) / 100.;
581 style->ScaleY = FFMAX(style->ScaleY, 0.) / 100.;
582 style->Spacing = FFMAX(style->Spacing, 0.);
583 style->Outline = FFMAX(style->Outline, 0.);
584 style->Shadow = FFMAX(style->Shadow, 0.);
585 style->Bold = !!style->Bold;
586 style->Italic = !!style->Italic;
587 style->Underline = !!style->Underline;
588 style->StrikeOut = !!style->StrikeOut;
590 style->Name = strdup("Default");
591 if (!style->FontName)
592 style->FontName = strdup("Arial");
598 static int process_styles_line(ASS_Track *track, char *str)
600 if (!strncmp(str, "Format:", 7)) {
603 free(track->style_format);
604 track->style_format = strdup(p);
605 ass_msg(track->library, MSGL_DBG2, "Style format: %s",
606 track->style_format);
607 } else if (!strncmp(str, "Style:", 6)) {
610 process_style(track, p);
615 static int process_info_line(ASS_Track *track, char *str)
617 if (!strncmp(str, "PlayResX:", 9)) {
618 track->PlayResX = atoi(str + 9);
619 } else if (!strncmp(str, "PlayResY:", 9)) {
620 track->PlayResY = atoi(str + 9);
621 } else if (!strncmp(str, "Timer:", 6)) {
622 track->Timer = ass_atof(str + 6);
623 } else if (!strncmp(str, "WrapStyle:", 10)) {
624 track->WrapStyle = atoi(str + 10);
625 } else if (!strncmp(str, "ScaledBorderAndShadow:", 22)) {
626 track->ScaledBorderAndShadow = parse_bool(str + 22);
627 } else if (!strncmp(str, "Kerning:", 8)) {
628 track->Kerning = parse_bool(str + 8);
629 } else if (!strncmp(str, "YCbCr Matrix:", 13)) {
630 track->YCbCrMatrix = parse_ycbcr_matrix(str + 13);
631 } else if (!strncmp(str, "Language:", 9)) {
633 while (*p && ass_isspace(*p)) p++;
634 free(track->Language);
635 track->Language = strndup(p, 2);
640 static void event_format_fallback(ASS_Track *track)
642 track->parser_priv->state = PST_EVENTS;
643 if (track->track_type == TRACK_TYPE_SSA)
644 track->event_format = strdup("Marked, Start, End, Style, "
645 "Name, MarginL, MarginR, MarginV, Effect, Text");
647 track->event_format = strdup("Layer, Start, End, Style, "
648 "Actor, MarginL, MarginR, MarginV, Effect, Text");
649 ass_msg(track->library, MSGL_V,
650 "No event format found, using fallback");
653 static int process_events_line(ASS_Track *track, char *str)
655 if (!strncmp(str, "Format:", 7)) {
658 free(track->event_format);
659 track->event_format = strdup(p);
660 ass_msg(track->library, MSGL_DBG2, "Event format: %s", track->event_format);
661 } else if (!strncmp(str, "Dialogue:", 9)) {
662 // This should never be reached for embedded subtitles.
663 // They have slightly different format and are parsed in ass_process_chunk,
664 // called directly from demuxer
671 eid = ass_alloc_event(track);
672 event = track->events + eid;
674 // We can't parse events with event_format
675 if (!track->event_format)
676 event_format_fallback(track);
678 process_event_tail(track, event, str, 0);
680 ass_msg(track->library, MSGL_V, "Not understood: '%.30s'", str);
685 // Copied from mkvtoolnix
686 static unsigned char *decode_chars(unsigned char c1, unsigned char c2,
687 unsigned char c3, unsigned char c4,
688 unsigned char *dst, int cnt)
691 unsigned char bytes[3];
695 ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 -
697 bytes[2] = value & 0xff;
698 bytes[1] = (value & 0xff00) >> 8;
699 bytes[0] = (value & 0xff0000) >> 16;
701 for (i = 0; i < cnt; ++i)
706 static int decode_font(ASS_Track *track)
711 int size; // original size
712 int dsize; // decoded size
713 unsigned char *buf = 0;
715 ass_msg(track->library, MSGL_V, "Font: %d bytes encoded data",
716 track->parser_priv->fontdata_used);
717 size = track->parser_priv->fontdata_used;
719 ass_msg(track->library, MSGL_ERR, "Bad encoded data size");
720 goto error_decode_font;
722 buf = malloc(size / 4 * 3 + 2);
724 goto error_decode_font;
726 for (i = 0, p = (unsigned char *) track->parser_priv->fontdata;
727 i < size / 4; i++, p += 4) {
728 q = decode_chars(p[0], p[1], p[2], p[3], q, 3);
731 q = decode_chars(p[0], p[1], 0, 0, q, 1);
732 } else if (size % 4 == 3) {
733 q = decode_chars(p[0], p[1], p[2], 0, q, 2);
736 assert(dsize <= size / 4 * 3 + 2);
738 if (track->library->extract_fonts) {
739 ass_add_font(track->library, track->parser_priv->fontname,
740 (char *) buf, dsize);
745 free(track->parser_priv->fontname);
746 free(track->parser_priv->fontdata);
747 track->parser_priv->fontname = 0;
748 track->parser_priv->fontdata = 0;
749 track->parser_priv->fontdata_size = 0;
750 track->parser_priv->fontdata_used = 0;
754 static int process_fonts_line(ASS_Track *track, char *str)
758 if (!strncmp(str, "fontname:", 9)) {
761 if (track->parser_priv->fontname) {
764 track->parser_priv->fontname = strdup(p);
765 ass_msg(track->library, MSGL_V, "Fontname: %s",
766 track->parser_priv->fontname);
770 if (!track->parser_priv->fontname) {
771 ass_msg(track->library, MSGL_V, "Not understood: '%s'", str);
777 ass_msg(track->library, MSGL_WARN, "Font line too long: %d, %s",
781 if (track->parser_priv->fontdata_used + len >
782 track->parser_priv->fontdata_size) {
783 track->parser_priv->fontdata_size += 100 * 1024;
784 track->parser_priv->fontdata =
785 realloc(track->parser_priv->fontdata,
786 track->parser_priv->fontdata_size);
788 memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used,
790 track->parser_priv->fontdata_used += len;
796 * \brief Parse a header line
798 * \param str string to parse, zero-terminated
800 static int process_line(ASS_Track *track, char *str)
802 if (!ass_strncasecmp(str, "[Script Info]", 13)) {
803 track->parser_priv->state = PST_INFO;
804 } else if (!ass_strncasecmp(str, "[V4 Styles]", 11)) {
805 track->parser_priv->state = PST_STYLES;
806 track->track_type = TRACK_TYPE_SSA;
807 } else if (!ass_strncasecmp(str, "[V4+ Styles]", 12)) {
808 track->parser_priv->state = PST_STYLES;
809 track->track_type = TRACK_TYPE_ASS;
810 } else if (!ass_strncasecmp(str, "[Events]", 8)) {
811 track->parser_priv->state = PST_EVENTS;
812 } else if (!ass_strncasecmp(str, "[Fonts]", 7)) {
813 track->parser_priv->state = PST_FONTS;
815 switch (track->parser_priv->state) {
817 process_info_line(track, str);
820 process_styles_line(track, str);
823 process_events_line(track, str);
826 process_fonts_line(track, str);
835 static int process_text(ASS_Track *track, char *str)
841 if ((*p == '\r') || (*p == '\n'))
843 else if (p[0] == '\xef' && p[1] == '\xbb' && p[2] == '\xbf')
844 p += 3; // U+FFFE (BOM)
848 for (q = p; ((*q != '\0') && (*q != '\r') && (*q != '\n')); ++q) {
854 process_line(track, p);
859 // there is no explicit end-of-font marker in ssa/ass
860 if (track->parser_priv->fontname)
866 * \brief Process a chunk of subtitle stream data.
868 * \param data string to parse
869 * \param size length of data
871 void ass_process_data(ASS_Track *track, char *data, int size)
873 char *str = malloc(size + 1);
877 memcpy(str, data, size);
880 ass_msg(track->library, MSGL_V, "Event: %s", str);
881 process_text(track, str);
886 * \brief Process CodecPrivate section of subtitle stream
888 * \param data string to parse
889 * \param size length of data
890 CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections
892 void ass_process_codec_private(ASS_Track *track, char *data, int size)
894 ass_process_data(track, data, size);
896 // probably an mkv produced by ancient mkvtoolnix
897 // such files don't have [Events] and Format: headers
898 if (!track->event_format)
899 event_format_fallback(track);
901 ass_process_force_style(track);
904 static int check_duplicate_event(ASS_Track *track, int ReadOrder)
906 if (track->parser_priv->read_order_bitmap)
907 return test_and_set_read_order_bit(track, ReadOrder) > 0;
908 // ignoring last event, it is the one we are comparing with
909 for (int i = 0; i < track->n_events - 1; i++)
910 if (track->events[i].ReadOrder == ReadOrder)
915 void ass_set_check_readorder(ASS_Track *track, int check_readorder)
917 track->parser_priv->check_readorder = check_readorder == 1;
921 * \brief Process a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary).
923 * \param data string to parse
924 * \param size length of data
925 * \param timecode starting time of the event (milliseconds)
926 * \param duration duration of the event (milliseconds)
928 void ass_process_chunk(ASS_Track *track, char *data, int size,
929 long long timecode, long long duration)
936 int check_readorder = track->parser_priv->check_readorder;
938 if (check_readorder && !track->parser_priv->read_order_bitmap) {
939 for (int i = 0; i < track->n_events; i++) {
940 if (test_and_set_read_order_bit(track, track->events[i].ReadOrder) < 0)
945 if (!track->event_format) {
946 ass_msg(track->library, MSGL_WARN, "Event format header missing");
950 str = malloc(size + 1);
953 memcpy(str, data, size);
955 ass_msg(track->library, MSGL_V, "Event at %" PRId64 ", +%" PRId64 ": %s",
956 (int64_t) timecode, (int64_t) duration, str);
958 eid = ass_alloc_event(track);
959 event = track->events + eid;
965 event->ReadOrder = atoi(token);
966 if (check_readorder && check_duplicate_event(track, event->ReadOrder))
970 event->Layer = atoi(token);
972 process_event_tail(track, event, p, 3);
974 event->Start = timecode;
975 event->Duration = duration;
982 ass_free_event(track, eid);
988 * \brief Flush buffered events.
991 void ass_flush_events(ASS_Track *track)
995 for (eid = 0; eid < track->n_events; eid++)
996 ass_free_event(track, eid);
999 free(track->parser_priv->read_order_bitmap);
1000 track->parser_priv->read_order_bitmap = NULL;
1001 track->parser_priv->read_order_elems = 0;
1005 /** \brief recode buffer to utf-8
1006 * constraint: codepage != 0
1007 * \param data pointer to text buffer
1008 * \param size buffer size
1009 * \return a pointer to recoded buffer, caller is responsible for freeing it
1011 static char *sub_recode(ASS_Library *library, char *data, size_t size,
1015 char *tocp = "UTF-8";
1019 if ((icdsc = iconv_open(tocp, codepage)) != (iconv_t) (-1)) {
1020 ass_msg(library, MSGL_V, "Opened iconv descriptor");
1022 ass_msg(library, MSGL_ERR, "Error opening iconv descriptor");
1027 size_t osize = size;
1028 size_t ileft = size;
1029 size_t oleft = size - 1;
1035 outbuf = malloc(osize);
1043 rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
1044 else { // clear the conversion state and leave
1046 rc = iconv(icdsc, NULL, NULL, &op, &oleft);
1048 if (rc == (size_t) (-1)) {
1049 if (errno == E2BIG) {
1050 size_t offset = op - outbuf;
1051 char *nbuf = realloc(outbuf, osize + size);
1058 op = outbuf + offset;
1062 ass_msg(library, MSGL_WARN, "Error recoding file");
1070 outbuf[osize - oleft - 1] = 0;
1074 if (icdsc != (iconv_t) (-1)) {
1075 (void) iconv_close(icdsc);
1076 ass_msg(library, MSGL_V, "Closed iconv descriptor");
1084 * \brief read file contents into newly allocated buffer
1085 * \param fname file name
1086 * \param bufsize out: file size
1087 * \return pointer to file contents. Caller is responsible for its deallocation.
1089 char *read_file(ASS_Library *library, char *fname, size_t *bufsize)
1096 FILE *fp = fopen(fname, "rb");
1098 ass_msg(library, MSGL_WARN,
1099 "ass_read_file(%s): fopen failed", fname);
1102 res = fseek(fp, 0, SEEK_END);
1104 ass_msg(library, MSGL_WARN,
1105 "ass_read_file(%s): fseek failed", fname);
1113 ass_msg(library, MSGL_V, "File size: %ld", sz);
1115 buf = sz < SIZE_MAX ? malloc(sz + 1) : NULL;
1123 res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
1125 ass_msg(library, MSGL_INFO, "Read failed, %d: %s", errno,
1132 } while (sz - bytes_read > 0);
1142 * \param buf pointer to subtitle text in utf-8
1144 static ASS_Track *parse_memory(ASS_Library *library, char *buf)
1149 track = ass_new_track(library);
1152 process_text(track, buf);
1154 // external SSA/ASS subs does not have ReadOrder field
1155 for (i = 0; i < track->n_events; ++i)
1156 track->events[i].ReadOrder = i;
1158 if (track->track_type == TRACK_TYPE_UNKNOWN) {
1159 ass_free_track(track);
1163 ass_process_force_style(track);
1169 * \brief Read subtitles from memory.
1170 * \param library libass library object
1171 * \param buf pointer to subtitles text
1172 * \param bufsize size of buffer
1173 * \param codepage recode buffer contents from given codepage
1174 * \return newly allocated track
1176 ASS_Track *ass_read_memory(ASS_Library *library, char *buf,
1177 size_t bufsize, char *codepage)
1187 buf = sub_recode(library, buf, bufsize, codepage);
1195 char *newbuf = malloc(bufsize + 1);
1198 memcpy(newbuf, buf, bufsize);
1199 newbuf[bufsize] = '\0';
1202 track = parse_memory(library, buf);
1207 ass_msg(library, MSGL_INFO, "Added subtitle file: "
1208 "<memory> (%d styles, %d events)",
1209 track->n_styles, track->n_events);
1213 static char *read_file_recode(ASS_Library *library, char *fname,
1214 char *codepage, size_t *size)
1219 buf = read_file(library, fname, &bufsize);
1224 char *tmpbuf = sub_recode(library, buf, bufsize, codepage);
1236 * \brief Read subtitles from file.
1237 * \param library libass library object
1238 * \param fname file name
1239 * \param codepage recode buffer contents from given codepage
1240 * \return newly allocated track
1242 ASS_Track *ass_read_file(ASS_Library *library, char *fname,
1249 buf = read_file_recode(library, fname, codepage, &bufsize);
1252 track = parse_memory(library, buf);
1257 track->name = strdup(fname);
1259 ass_msg(library, MSGL_INFO,
1260 "Added subtitle file: '%s' (%d styles, %d events)",
1261 fname, track->n_styles, track->n_events);
1267 * \brief read styles from file into already initialized track
1269 int ass_read_styles(ASS_Track *track, char *fname, char *codepage)
1272 ParserState old_state;
1275 buf = read_file(track->library, fname, &sz);
1281 tmpbuf = sub_recode(track->library, buf, sz, codepage);
1289 old_state = track->parser_priv->state;
1290 track->parser_priv->state = PST_STYLES;
1291 process_text(track, buf);
1293 track->parser_priv->state = old_state;
1298 long long ass_step_sub(ASS_Track *track, long long now, int movement)
1301 ASS_Event *best = NULL;
1302 long long target = now;
1303 int direction = (movement > 0 ? 1 : -1) * !!movement;
1305 if (track->n_events == 0)
1309 ASS_Event *closest = NULL;
1310 long long closest_time = now;
1311 for (i = 0; i < track->n_events; i++) {
1312 if (direction < 0) {
1314 track->events[i].Start + track->events[i].Duration;
1316 if (!closest || end > closest_time) {
1317 closest = &track->events[i];
1321 } else if (direction > 0) {
1322 long long start = track->events[i].Start;
1323 if (start > target) {
1324 if (!closest || start < closest_time) {
1325 closest = &track->events[i];
1326 closest_time = start;
1330 long long start = track->events[i].Start;
1331 if (start < target) {
1332 if (!closest || start >= closest_time) {
1333 closest = &track->events[i];
1334 closest_time = start;
1339 target = closest_time + direction;
1340 movement -= direction;
1345 return best ? best->Start - now : 0;
1348 ASS_Track *ass_new_track(ASS_Library *library)
1350 ASS_Track *track = calloc(1, sizeof(ASS_Track));
1353 track->library = library;
1354 track->ScaledBorderAndShadow = 1;
1355 track->parser_priv = calloc(1, sizeof(ASS_ParserPriv));
1356 if (!track->parser_priv) {
1360 track->parser_priv->check_readorder = 1;
1365 * \brief Prepare track for rendering
1367 void ass_lazy_track_init(ASS_Library *lib, ASS_Track *track)
1369 if (track->PlayResX && track->PlayResY)
1371 if (!track->PlayResX && !track->PlayResY) {
1372 ass_msg(lib, MSGL_WARN,
1373 "Neither PlayResX nor PlayResY defined. Assuming 384x288");
1374 track->PlayResX = 384;
1375 track->PlayResY = 288;
1377 if (!track->PlayResY && track->PlayResX == 1280) {
1378 track->PlayResY = 1024;
1379 ass_msg(lib, MSGL_WARN,
1380 "PlayResY undefined, setting to %d", track->PlayResY);
1381 } else if (!track->PlayResY) {
1382 track->PlayResY = track->PlayResX * 3 / 4;
1383 ass_msg(lib, MSGL_WARN,
1384 "PlayResY undefined, setting to %d", track->PlayResY);
1385 } else if (!track->PlayResX && track->PlayResY == 1024) {
1386 track->PlayResX = 1280;
1387 ass_msg(lib, MSGL_WARN,
1388 "PlayResX undefined, setting to %d", track->PlayResX);
1389 } else if (!track->PlayResX) {
1390 track->PlayResX = track->PlayResY * 4 / 3;
1391 ass_msg(lib, MSGL_WARN,
1392 "PlayResX undefined, setting to %d", track->PlayResX);