2 * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
4 * This file is part of libass.
6 * libass 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 * libass 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 along
17 * with libass; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
28 #include <sys/types.h>
38 #include "ass_utils.h"
39 #include "ass_library.h"
57 #define ASS_STYLES_ALLOC 20
58 #define ASS_EVENTS_ALLOC 200
60 void ass_free_track(ASS_Track
*track
)
64 if (track
->parser_priv
) {
65 if (track
->parser_priv
->fontname
)
66 free(track
->parser_priv
->fontname
);
67 if (track
->parser_priv
->fontdata
)
68 free(track
->parser_priv
->fontdata
);
69 free(track
->parser_priv
);
71 if (track
->style_format
)
72 free(track
->style_format
);
73 if (track
->event_format
)
74 free(track
->event_format
);
76 for (i
= 0; i
< track
->n_styles
; ++i
)
77 ass_free_style(track
, i
);
81 for (i
= 0; i
< track
->n_events
; ++i
)
82 ass_free_event(track
, i
);
89 /// \brief Allocate a new style struct
90 /// \param track track
92 int ass_alloc_style(ASS_Track
*track
)
96 assert(track
->n_styles
<= track
->max_styles
);
98 if (track
->n_styles
== track
->max_styles
) {
99 track
->max_styles
+= ASS_STYLES_ALLOC
;
101 (ASS_Style
*) realloc(track
->styles
,
106 sid
= track
->n_styles
++;
107 memset(track
->styles
+ sid
, 0, sizeof(ASS_Style
));
111 /// \brief Allocate a new event struct
112 /// \param track track
114 int ass_alloc_event(ASS_Track
*track
)
118 assert(track
->n_events
<= track
->max_events
);
120 if (track
->n_events
== track
->max_events
) {
121 track
->max_events
+= ASS_EVENTS_ALLOC
;
123 (ASS_Event
*) realloc(track
->events
,
128 eid
= track
->n_events
++;
129 memset(track
->events
+ eid
, 0, sizeof(ASS_Event
));
133 void ass_free_event(ASS_Track
*track
, int eid
)
135 ASS_Event
*event
= track
->events
+ eid
;
142 if (event
->render_priv
)
143 free(event
->render_priv
);
146 void ass_free_style(ASS_Track
*track
, int sid
)
148 ASS_Style
*style
= track
->styles
+ sid
;
152 free(style
->FontName
);
155 // ==============================================================================================
157 static void skip_spaces(char **str
)
160 while ((*p
== ' ') || (*p
== '\t'))
165 static void rskip_spaces(char **str
, char *limit
)
168 while ((p
>= limit
) && ((*p
== ' ') || (*p
== '\t')))
174 * \brief find style by name
176 * \param name style name
177 * \return index in track->styles
178 * Returnes 0 if no styles found => expects at least 1 style.
179 * Parsing code always adds "Default" style in the end.
181 static int lookup_style(ASS_Track
*track
, char *name
)
185 ++name
; // FIXME: what does '*' really mean ?
186 for (i
= track
->n_styles
- 1; i
>= 0; --i
) {
187 // FIXME: mb strcasecmp ?
188 if (strcmp(track
->styles
[i
].Name
, name
) == 0)
191 i
= track
->default_style
;
192 ass_msg(track
->library
, MSGL_WARN
,
193 "[%p]: Warning: no style named '%s' found, using '%s'",
194 track
, name
, track
->styles
[i
].Name
);
195 return i
; // use the first style
198 static uint32_t string2color(ASS_Library
*library
, char *p
)
201 (void) strtocolor(library
, &p
, &tmp
, 0);
205 static long long string2timecode(ASS_Library
*library
, char *p
)
207 unsigned h
, m
, s
, ms
;
209 int res
= sscanf(p
, "%1d:%2d:%2d.%2d", &h
, &m
, &s
, &ms
);
211 ass_msg(library
, MSGL_WARN
, "Bad timestamp");
214 tm
= ((h
* 60 + m
) * 60 + s
) * 1000 + ms
* 10;
219 * \brief converts numpad-style align to align.
221 static int numpad2align(int val
)
224 v
= (val
- 1) / 3; // 0, 1 or 2 for vertical alignment
227 res
= ((val
- 1) % 3) + 1; // horizontal alignment
232 #define NEXT(str,token) \
233 token = next_token(&str); \
236 #define ANYVAL(name,func) \
237 } else if (strcasecmp(tname, #name) == 0) { \
238 target->name = func(token); \
239 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
241 #define STRVAL(name) \
242 } else if (strcasecmp(tname, #name) == 0) { \
243 if (target->name != NULL) free(target->name); \
244 target->name = strdup(token); \
245 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
247 #define COLORVAL(name) \
248 } else if (strcasecmp(tname, #name) == 0) { \
249 target->name = string2color(track->library, token); \
250 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
252 #define INTVAL(name) ANYVAL(name,atoi)
253 #define FPVAL(name) ANYVAL(name,atof)
254 #define TIMEVAL(name) \
255 } else if (strcasecmp(tname, #name) == 0) { \
256 target->name = string2timecode(track->library, token); \
257 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
259 #define STYLEVAL(name) \
260 } else if (strcasecmp(tname, #name) == 0) { \
261 target->name = lookup_style(track, token); \
262 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
264 #define ALIAS(alias,name) \
265 if (strcasecmp(tname, #alias) == 0) {tname = #name;}
267 static char *next_token(char **str
)
276 start
= p
; // start of the token
277 for (; (*p
!= '\0') && (*p
!= ','); ++p
) {
280 *str
= p
; // eos found, str will point to '\0' at exit
283 *str
= p
+ 1; // ',' found, str will point to the next char (beginning of the next token)
285 --p
; // end of current token
286 rskip_spaces(&p
, start
);
288 p
= start
; // empty token
290 ++p
; // the first space character, or '\0'
296 * \brief Parse the tail of Dialogue line
298 * \param event parsed data goes here
299 * \param str string to parse, zero-terminated
300 * \param n_ignored number of format options to skip at the beginning
302 static int process_event_tail(ASS_Track
*track
, ASS_Event
*event
,
303 char *str
, int n_ignored
)
309 ASS_Event
*target
= event
;
311 char *format
= strdup(track
->event_format
);
312 char *q
= format
; // format scanning pointer
314 if (track
->n_styles
== 0) {
315 // add "Default" style to the end
316 // will be used if track does not contain a default style (or even does not contain styles at all)
317 int sid
= ass_alloc_style(track
);
318 track
->styles
[sid
].Name
= strdup("Default");
319 track
->styles
[sid
].FontName
= strdup("Arial");
322 for (i
= 0; i
< n_ignored
; ++i
) {
328 if (strcasecmp(tname
, "Text") == 0) {
330 event
->Text
= strdup(p
);
331 if (*event
->Text
!= 0) {
332 last
= event
->Text
+ strlen(event
->Text
) - 1;
333 if (last
>= event
->Text
&& *last
== '\r')
336 ass_msg(track
->library
, MSGL_DBG2
, "Text = %s", event
->Text
);
337 event
->Duration
-= event
->Start
;
339 return 0; // "Text" is always the last
343 ALIAS(End
, Duration
) // temporarily store end timecode in event->Duration
361 * \brief Parse command line style overrides (--ass-force-style option)
362 * \param track track to apply overrides to
363 * The format for overrides is [StyleName.]Field=Value
365 void ass_process_force_style(ASS_Track
*track
)
367 char **fs
, *eq
, *dt
, *style
, *tname
, *token
;
370 char **list
= track
->library
->style_overrides
;
375 for (fs
= list
; *fs
; ++fs
) {
376 eq
= strrchr(*fs
, '=');
382 if (!strcasecmp(*fs
, "PlayResX"))
383 track
->PlayResX
= atoi(token
);
384 else if (!strcasecmp(*fs
, "PlayResY"))
385 track
->PlayResY
= atoi(token
);
386 else if (!strcasecmp(*fs
, "Timer"))
387 track
->Timer
= atof(token
);
388 else if (!strcasecmp(*fs
, "WrapStyle"))
389 track
->WrapStyle
= atoi(token
);
390 else if (!strcasecmp(*fs
, "ScaledBorderAndShadow"))
391 track
->ScaledBorderAndShadow
= parse_bool(token
);
392 else if (!strcasecmp(*fs
, "Kerning"))
393 track
->Kerning
= parse_bool(token
);
395 dt
= strrchr(*fs
, '.');
404 for (sid
= 0; sid
< track
->n_styles
; ++sid
) {
406 || strcasecmp(track
->styles
[sid
].Name
, style
) == 0) {
407 target
= track
->styles
+ sid
;
410 COLORVAL(PrimaryColour
)
411 COLORVAL(SecondaryColour
)
412 COLORVAL(OutlineColour
)
441 * \brief Parse the Style line
443 * \param str string to parse, zero-terminated
444 * Allocates a new style struct.
446 static int process_style(ASS_Track
*track
, char *str
)
453 char *q
; // format scanning pointer
458 if (!track
->style_format
) {
459 // no style format header
460 // probably an ancient script version
461 if (track
->track_type
== TRACK_TYPE_SSA
)
462 track
->style_format
=
464 ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
465 "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline,"
466 "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
468 track
->style_format
=
470 ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
471 "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut,"
472 "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow,"
473 "Alignment, MarginL, MarginR, MarginV, Encoding");
476 q
= format
= strdup(track
->style_format
);
478 ass_msg(track
->library
, MSGL_V
, "[%p] Style: %s", track
, str
);
480 sid
= ass_alloc_style(track
);
482 style
= track
->styles
+ sid
;
485 // fill style with some default values
486 style
->ScaleX
= 100.;
487 style
->ScaleY
= 100.;
495 if ((strcmp(target
->Name
, "Default") == 0)
496 || (strcmp(target
->Name
, "*Default") == 0))
497 track
->default_style
= sid
;
499 COLORVAL(PrimaryColour
)
500 COLORVAL(SecondaryColour
)
501 COLORVAL(OutlineColour
) // TertiaryColor
503 // SSA uses BackColour for both outline and shadow
504 // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
505 if (track
->track_type
== TRACK_TYPE_SSA
)
506 target
->OutlineColour
= target
->BackColour
;
516 if (track
->track_type
== TRACK_TYPE_ASS
)
517 target
->Alignment
= numpad2align(target
->Alignment
);
528 style
->ScaleX
/= 100.;
529 style
->ScaleY
/= 100.;
530 style
->Bold
= !!style
->Bold
;
531 style
->Italic
= !!style
->Italic
;
532 style
->Underline
= !!style
->Underline
;
534 style
->Name
= strdup("Default");
535 if (!style
->FontName
)
536 style
->FontName
= strdup("Arial");
537 // skip '@' at the start of the font name
538 if (*style
->FontName
== '@') {
540 style
->FontName
= strdup(p
+ 1);
548 static int process_styles_line(ASS_Track
*track
, char *str
)
550 if (!strncmp(str
, "Format:", 7)) {
553 track
->style_format
= strdup(p
);
554 ass_msg(track
->library
, MSGL_DBG2
, "Style format: %s",
555 track
->style_format
);
556 } else if (!strncmp(str
, "Style:", 6)) {
559 process_style(track
, p
);
564 static int process_info_line(ASS_Track
*track
, char *str
)
566 if (!strncmp(str
, "PlayResX:", 9)) {
567 track
->PlayResX
= atoi(str
+ 9);
568 } else if (!strncmp(str
, "PlayResY:", 9)) {
569 track
->PlayResY
= atoi(str
+ 9);
570 } else if (!strncmp(str
, "Timer:", 6)) {
571 track
->Timer
= atof(str
+ 6);
572 } else if (!strncmp(str
, "WrapStyle:", 10)) {
573 track
->WrapStyle
= atoi(str
+ 10);
574 } else if (!strncmp(str
, "ScaledBorderAndShadow:", 22)) {
575 track
->ScaledBorderAndShadow
= parse_bool(str
+ 22);
576 } else if (!strncmp(str
, "Kerning:", 8)) {
577 track
->Kerning
= parse_bool(str
+ 8);
582 static void event_format_fallback(ASS_Track
*track
)
584 track
->parser_priv
->state
= PST_EVENTS
;
585 if (track
->track_type
== TRACK_TYPE_SSA
)
586 track
->event_format
= strdup("Format: Marked, Start, End, Style, "
587 "Name, MarginL, MarginR, MarginV, Effect, Text");
589 track
->event_format
= strdup("Format: Layer, Start, End, Style, "
590 "Actor, MarginL, MarginR, MarginV, Effect, Text");
591 ass_msg(track
->library
, MSGL_V
,
592 "No event format found, using fallback");
595 static int process_events_line(ASS_Track
*track
, char *str
)
597 if (!strncmp(str
, "Format:", 7)) {
600 track
->event_format
= strdup(p
);
601 ass_msg(track
->library
, MSGL_DBG2
, "Event format: %s", track
->event_format
);
602 } else if (!strncmp(str
, "Dialogue:", 9)) {
603 // This should never be reached for embedded subtitles.
604 // They have slightly different format and are parsed in ass_process_chunk,
605 // called directly from demuxer
612 eid
= ass_alloc_event(track
);
613 event
= track
->events
+ eid
;
615 // We can't parse events with event_format
616 if (!track
->event_format
)
617 event_format_fallback(track
);
619 process_event_tail(track
, event
, str
, 0);
621 ass_msg(track
->library
, MSGL_V
, "Not understood: '%s'", str
);
626 // Copied from mkvtoolnix
627 static unsigned char *decode_chars(unsigned char c1
, unsigned char c2
,
628 unsigned char c3
, unsigned char c4
,
629 unsigned char *dst
, int cnt
)
632 unsigned char bytes
[3];
636 ((c1
- 33) << 18) + ((c2
- 33) << 12) + ((c3
- 33) << 6) + (c4
-
638 bytes
[2] = value
& 0xff;
639 bytes
[1] = (value
& 0xff00) >> 8;
640 bytes
[0] = (value
& 0xff0000) >> 16;
642 for (i
= 0; i
< cnt
; ++i
)
647 static int decode_font(ASS_Track
*track
)
652 int size
; // original size
653 int dsize
; // decoded size
654 unsigned char *buf
= 0;
656 ass_msg(track
->library
, MSGL_V
, "Font: %d bytes encoded data",
657 track
->parser_priv
->fontdata_used
);
658 size
= track
->parser_priv
->fontdata_used
;
660 ass_msg(track
->library
, MSGL_ERR
, "Bad encoded data size");
661 goto error_decode_font
;
663 buf
= malloc(size
/ 4 * 3 + 2);
665 for (i
= 0, p
= (unsigned char *) track
->parser_priv
->fontdata
;
666 i
< size
/ 4; i
++, p
+= 4) {
667 q
= decode_chars(p
[0], p
[1], p
[2], p
[3], q
, 3);
670 q
= decode_chars(p
[0], p
[1], 0, 0, q
, 1);
671 } else if (size
% 4 == 3) {
672 q
= decode_chars(p
[0], p
[1], p
[2], 0, q
, 2);
675 assert(dsize
<= size
/ 4 * 3 + 2);
677 if (track
->library
->extract_fonts
) {
678 ass_add_font(track
->library
, track
->parser_priv
->fontname
,
679 (char *) buf
, dsize
);
686 free(track
->parser_priv
->fontname
);
687 free(track
->parser_priv
->fontdata
);
688 track
->parser_priv
->fontname
= 0;
689 track
->parser_priv
->fontdata
= 0;
690 track
->parser_priv
->fontdata_size
= 0;
691 track
->parser_priv
->fontdata_used
= 0;
695 static int process_fonts_line(ASS_Track
*track
, char *str
)
699 if (!strncmp(str
, "fontname:", 9)) {
702 if (track
->parser_priv
->fontname
) {
705 track
->parser_priv
->fontname
= strdup(p
);
706 ass_msg(track
->library
, MSGL_V
, "Fontname: %s",
707 track
->parser_priv
->fontname
);
711 if (!track
->parser_priv
->fontname
) {
712 ass_msg(track
->library
, MSGL_V
, "Not understood: '%s'", str
);
718 ass_msg(track
->library
, MSGL_WARN
, "Font line too long: %d, %s",
722 if (track
->parser_priv
->fontdata_used
+ len
>
723 track
->parser_priv
->fontdata_size
) {
724 track
->parser_priv
->fontdata_size
+= 100 * 1024;
725 track
->parser_priv
->fontdata
=
726 realloc(track
->parser_priv
->fontdata
,
727 track
->parser_priv
->fontdata_size
);
729 memcpy(track
->parser_priv
->fontdata
+ track
->parser_priv
->fontdata_used
,
731 track
->parser_priv
->fontdata_used
+= len
;
737 * \brief Parse a header line
739 * \param str string to parse, zero-terminated
741 static int process_line(ASS_Track
*track
, char *str
)
743 if (!strncasecmp(str
, "[Script Info]", 13)) {
744 track
->parser_priv
->state
= PST_INFO
;
745 } else if (!strncasecmp(str
, "[V4 Styles]", 11)) {
746 track
->parser_priv
->state
= PST_STYLES
;
747 track
->track_type
= TRACK_TYPE_SSA
;
748 } else if (!strncasecmp(str
, "[V4+ Styles]", 12)) {
749 track
->parser_priv
->state
= PST_STYLES
;
750 track
->track_type
= TRACK_TYPE_ASS
;
751 } else if (!strncasecmp(str
, "[Events]", 8)) {
752 track
->parser_priv
->state
= PST_EVENTS
;
753 } else if (!strncasecmp(str
, "[Fonts]", 7)) {
754 track
->parser_priv
->state
= PST_FONTS
;
756 switch (track
->parser_priv
->state
) {
758 process_info_line(track
, str
);
761 process_styles_line(track
, str
);
764 process_events_line(track
, str
);
767 process_fonts_line(track
, str
);
774 // there is no explicit end-of-font marker in ssa/ass
775 if ((track
->parser_priv
->state
!= PST_FONTS
)
776 && (track
->parser_priv
->fontname
))
782 static int process_text(ASS_Track
*track
, char *str
)
788 if ((*p
== '\r') || (*p
== '\n'))
790 else if (p
[0] == '\xef' && p
[1] == '\xbb' && p
[2] == '\xbf')
791 p
+= 3; // U+FFFE (BOM)
795 for (q
= p
; ((*q
!= '\0') && (*q
!= '\r') && (*q
!= '\n')); ++q
) {
801 process_line(track
, p
);
810 * \brief Process a chunk of subtitle stream data.
812 * \param data string to parse
813 * \param size length of data
815 void ass_process_data(ASS_Track
*track
, char *data
, int size
)
817 char *str
= malloc(size
+ 1);
819 memcpy(str
, data
, size
);
822 ass_msg(track
->library
, MSGL_V
, "Event: %s", str
);
823 process_text(track
, str
);
828 * \brief Process CodecPrivate section of subtitle stream
830 * \param data string to parse
831 * \param size length of data
832 CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections
834 void ass_process_codec_private(ASS_Track
*track
, char *data
, int size
)
836 ass_process_data(track
, data
, size
);
838 // probably an mkv produced by ancient mkvtoolnix
839 // such files don't have [Events] and Format: headers
840 if (!track
->event_format
)
841 event_format_fallback(track
);
843 ass_process_force_style(track
);
846 static int check_duplicate_event(ASS_Track
*track
, int ReadOrder
)
849 for (i
= 0; i
< track
->n_events
- 1; ++i
) // ignoring last event, it is the one we are comparing with
850 if (track
->events
[i
].ReadOrder
== ReadOrder
)
856 * \brief Process a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary).
858 * \param data string to parse
859 * \param size length of data
860 * \param timecode starting time of the event (milliseconds)
861 * \param duration duration of the event (milliseconds)
863 void ass_process_chunk(ASS_Track
*track
, char *data
, int size
,
864 long long timecode
, long long duration
)
872 if (!track
->event_format
) {
873 ass_msg(track
->library
, MSGL_WARN
, "Event format header missing");
877 str
= malloc(size
+ 1);
878 memcpy(str
, data
, size
);
880 ass_msg(track
->library
, MSGL_V
, "Event at %" PRId64
", +%" PRId64
": %s",
881 (int64_t) timecode
, (int64_t) duration
, str
);
883 eid
= ass_alloc_event(track
);
884 event
= track
->events
+ eid
;
890 event
->ReadOrder
= atoi(token
);
891 if (check_duplicate_event(track
, event
->ReadOrder
))
895 event
->Layer
= atoi(token
);
897 process_event_tail(track
, event
, p
, 3);
899 event
->Start
= timecode
;
900 event
->Duration
= duration
;
907 ass_free_event(track
, eid
);
913 /** \brief recode buffer to utf-8
914 * constraint: codepage != 0
915 * \param data pointer to text buffer
916 * \param size buffer size
917 * \return a pointer to recoded buffer, caller is responsible for freeing it
919 static char *sub_recode(ASS_Library
*library
, char *data
, size_t size
,
923 char *tocp
= "UTF-8";
928 const char *cp_tmp
= codepage
;
930 char enca_lang
[3], enca_fallback
[100];
931 if (sscanf(codepage
, "enca:%2s:%99s", enca_lang
, enca_fallback
) == 2
932 || sscanf(codepage
, "ENCA:%2s:%99s", enca_lang
,
933 enca_fallback
) == 2) {
935 ass_guess_buffer_cp(library
, (unsigned char *) data
, size
,
936 enca_lang
, enca_fallback
);
939 if ((icdsc
= iconv_open(tocp
, cp_tmp
)) != (iconv_t
) (-1)) {
940 ass_msg(library
, MSGL_V
, "Opened iconv descriptor");
942 ass_msg(library
, MSGL_ERR
, "Error opening iconv descriptor");
948 size_t oleft
= size
- 1;
954 outbuf
= malloc(osize
);
960 rc
= iconv(icdsc
, &ip
, &ileft
, &op
, &oleft
);
961 else { // clear the conversion state and leave
963 rc
= iconv(icdsc
, NULL
, NULL
, &op
, &oleft
);
965 if (rc
== (size_t) (-1)) {
966 if (errno
== E2BIG
) {
967 size_t offset
= op
- outbuf
;
968 outbuf
= (char *) realloc(outbuf
, osize
+ size
);
969 op
= outbuf
+ offset
;
973 ass_msg(library
, MSGL_WARN
, "Error recoding file");
979 outbuf
[osize
- oleft
- 1] = 0;
982 if (icdsc
!= (iconv_t
) (-1)) {
983 (void) iconv_close(icdsc
);
984 icdsc
= (iconv_t
) (-1);
985 ass_msg(library
, MSGL_V
, "Closed iconv descriptor");
993 * \brief read file contents into newly allocated buffer
994 * \param fname file name
995 * \param bufsize out: file size
996 * \return pointer to file contents. Caller is responsible for its deallocation.
998 static char *read_file(ASS_Library
*library
, char *fname
, size_t *bufsize
)
1005 FILE *fp
= fopen(fname
, "rb");
1007 ass_msg(library
, MSGL_WARN
,
1008 "ass_read_file(%s): fopen failed", fname
);
1011 res
= fseek(fp
, 0, SEEK_END
);
1013 ass_msg(library
, MSGL_WARN
,
1014 "ass_read_file(%s): fseek failed", fname
);
1022 if (sz
> 10 * 1024 * 1024) {
1023 ass_msg(library
, MSGL_INFO
,
1024 "ass_read_file(%s): Refusing to load subtitles "
1025 "larger than 10MiB", fname
);
1030 ass_msg(library
, MSGL_V
, "File size: %ld", sz
);
1032 buf
= malloc(sz
+ 1);
1036 res
= fread(buf
+ bytes_read
, 1, sz
- bytes_read
, fp
);
1038 ass_msg(library
, MSGL_INFO
, "Read failed, %d: %s", errno
,
1045 } while (sz
- bytes_read
> 0);
1055 * \param buf pointer to subtitle text in utf-8
1057 static ASS_Track
*parse_memory(ASS_Library
*library
, char *buf
)
1062 track
= ass_new_track(library
);
1065 process_text(track
, buf
);
1067 // external SSA/ASS subs does not have ReadOrder field
1068 for (i
= 0; i
< track
->n_events
; ++i
)
1069 track
->events
[i
].ReadOrder
= i
;
1071 // there is no explicit end-of-font marker in ssa/ass
1072 if (track
->parser_priv
->fontname
)
1075 if (track
->track_type
== TRACK_TYPE_UNKNOWN
) {
1076 ass_free_track(track
);
1080 ass_process_force_style(track
);
1086 * \brief Read subtitles from memory.
1087 * \param library libass library object
1088 * \param buf pointer to subtitles text
1089 * \param bufsize size of buffer
1090 * \param codepage recode buffer contents from given codepage
1091 * \return newly allocated track
1093 ASS_Track
*ass_read_memory(ASS_Library
*library
, char *buf
,
1094 size_t bufsize
, char *codepage
)
1104 buf
= sub_recode(library
, buf
, bufsize
, codepage
);
1111 track
= parse_memory(library
, buf
);
1117 ass_msg(library
, MSGL_INFO
, "Added subtitle file: "
1118 "<memory> (%d styles, %d events)",
1119 track
->n_styles
, track
->n_events
);
1123 static char *read_file_recode(ASS_Library
*library
, char *fname
,
1124 char *codepage
, size_t *size
)
1129 buf
= read_file(library
, fname
, &bufsize
);
1134 char *tmpbuf
= sub_recode(library
, buf
, bufsize
, codepage
);
1146 * \brief Read subtitles from file.
1147 * \param library libass library object
1148 * \param fname file name
1149 * \param codepage recode buffer contents from given codepage
1150 * \return newly allocated track
1152 ASS_Track
*ass_read_file(ASS_Library
*library
, char *fname
,
1159 buf
= read_file_recode(library
, fname
, codepage
, &bufsize
);
1162 track
= parse_memory(library
, buf
);
1167 track
->name
= strdup(fname
);
1169 ass_msg(library
, MSGL_INFO
,
1170 "Added subtitle file: '%s' (%d styles, %d events)",
1171 fname
, track
->n_styles
, track
->n_events
);
1177 * \brief read styles from file into already initialized track
1179 int ass_read_styles(ASS_Track
*track
, char *fname
, char *codepage
)
1182 ParserState old_state
;
1185 buf
= read_file(track
->library
, fname
, &sz
);
1191 tmpbuf
= sub_recode(track
->library
, buf
, sz
, codepage
);
1199 old_state
= track
->parser_priv
->state
;
1200 track
->parser_priv
->state
= PST_STYLES
;
1201 process_text(track
, buf
);
1202 track
->parser_priv
->state
= old_state
;
1207 long long ass_step_sub(ASS_Track
*track
, long long now
, int movement
)
1213 if (track
->n_events
== 0)
1218 (i
< track
->n_events
)
1220 ((long long) (track
->events
[i
].Start
+
1221 track
->events
[i
].Duration
) <= now
); ++i
) {
1223 for (i
= track
->n_events
- 1;
1224 (i
>= 0) && ((long long) (track
->events
[i
].Start
) > now
);
1228 // -1 and n_events are ok
1230 assert(i
<= track
->n_events
);
1234 if (i
>= track
->n_events
)
1235 i
= track
->n_events
- 1;
1236 return ((long long) track
->events
[i
].Start
) - now
;
1239 ASS_Track
*ass_new_track(ASS_Library
*library
)
1241 ASS_Track
*track
= calloc(1, sizeof(ASS_Track
));
1242 track
->library
= library
;
1243 track
->ScaledBorderAndShadow
= 1;
1244 track
->parser_priv
= calloc(1, sizeof(ASS_ParserPriv
));