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
;
274 char* q
; // format scanning pointer
276 if (!track
->event_format
) {
277 track
->event_format
= strdup("Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text");
278 mp_msg(MSGT_ASS
, MSGL_V
, "Event format is broken, reseting to defaults.\n");
281 q
= format
= strdup(track
->event_format
);
283 if (track
->n_styles
== 0) {
284 // add "Default" style to the end
285 // will be used if track does not contain a default style (or even does not contain styles at all)
286 int sid
= ass_alloc_style(track
);
287 track
->styles
[sid
].Name
= strdup("Default");
288 track
->styles
[sid
].FontName
= strdup("Arial");
291 for (i
= 0; i
< n_ignored
; ++i
) {
297 if (strcasecmp(tname
, "Text") == 0) {
299 event
->Text
= strdup(p
);
300 if (*event
->Text
!= 0) {
301 last
= event
->Text
+ strlen(event
->Text
) - 1;
302 if (last
>= event
->Text
&& *last
== '\r')
305 mp_msg(MSGT_ASS
, MSGL_DBG2
, "Text = %s\n", event
->Text
);
306 event
->Duration
-= event
->Start
;
308 return 0; // "Text" is always the last
312 ALIAS(End
,Duration
) // temporarily store end timecode in event->Duration
330 * \brief Parse command line style overrides (--ass-force-style option)
331 * \param track track to apply overrides to
332 * The format for overrides is [StyleName.]Field=Value
334 void process_force_style(ass_track_t
* track
) {
335 char **fs
, *eq
, *dt
, *style
, *tname
, *token
;
338 char** list
= track
->library
->style_overrides
;
342 for (fs
= list
; *fs
; ++fs
) {
343 eq
= strrchr(*fs
, '=');
349 if(!strcasecmp(*fs
, "PlayResX"))
350 track
->PlayResX
= atoi(token
);
351 else if(!strcasecmp(*fs
, "PlayResY"))
352 track
->PlayResY
= atoi(token
);
353 else if(!strcasecmp(*fs
, "Timer"))
354 track
->Timer
= atof(token
);
355 else if(!strcasecmp(*fs
, "WrapStyle"))
356 track
->WrapStyle
= atoi(token
);
357 else if(!strcasecmp(*fs
, "ScaledBorderAndShadow"))
358 track
->ScaledBorderAndShadow
= parse_bool(token
);
360 dt
= strrchr(*fs
, '.');
369 for (sid
= 0; sid
< track
->n_styles
; ++sid
) {
370 if (style
== NULL
|| strcasecmp(track
->styles
[sid
].Name
, style
) == 0) {
371 target
= track
->styles
+ sid
;
374 COLORVAL(PrimaryColour
)
375 COLORVAL(SecondaryColour
)
376 COLORVAL(OutlineColour
)
404 * \brief Parse the Style line
406 * \param str string to parse, zero-terminated
407 * Allocates a new style struct.
409 static int process_style(ass_track_t
* track
, char *str
)
416 char* q
; // format scanning pointer
421 if (!track
->style_format
) {
422 // no style format header
423 // probably an ancient script version
424 if (track
->track_type
== TRACK_TYPE_SSA
)
425 track
->style_format
= strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
426 "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline,"
427 "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
429 track
->style_format
= strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
430 "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut,"
431 "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow,"
432 "Alignment, MarginL, MarginR, MarginV, Encoding");
435 q
= format
= strdup(track
->style_format
);
437 mp_msg(MSGT_ASS
, MSGL_V
, "[%p] Style: %s\n", track
, str
);
439 sid
= ass_alloc_style(track
);
441 style
= track
->styles
+ sid
;
443 // fill style with some default values
444 style
->ScaleX
= 100.;
445 style
->ScaleY
= 100.;
451 // ALIAS(TertiaryColour,OutlineColour) // ignore TertiaryColour; it appears only in SSA, and is overridden by BackColour
455 if ((strcmp(target
->Name
, "Default")==0) || (strcmp(target
->Name
, "*Default")==0))
456 track
->default_style
= sid
;
458 COLORVAL(PrimaryColour
)
459 COLORVAL(SecondaryColour
)
460 COLORVAL(OutlineColour
) // TertiaryColor
462 // SSA uses BackColour for both outline and shadow
463 // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
464 if (track
->track_type
== TRACK_TYPE_SSA
)
465 target
->OutlineColour
= target
->BackColour
;
475 if (track
->track_type
== TRACK_TYPE_ASS
)
476 target
->Alignment
= numpad2align(target
->Alignment
);
487 style
->ScaleX
/= 100.;
488 style
->ScaleY
/= 100.;
489 style
->Bold
= !!style
->Bold
;
490 style
->Italic
= !!style
->Italic
;
491 style
->Underline
= !!style
->Underline
;
493 style
->Name
= strdup("Default");
494 if (!style
->FontName
)
495 style
->FontName
= strdup("Arial");
496 // skip '@' at the start of the font name
497 if (*style
->FontName
== '@') {
499 style
->FontName
= strdup(p
+ 1);
507 static int process_styles_line(ass_track_t
* track
, char *str
)
509 if (!strncmp(str
,"Format:", 7)) {
512 track
->style_format
= strdup(p
);
513 mp_msg(MSGT_ASS
, MSGL_DBG2
, "Style format: %s\n", track
->style_format
);
514 } else if (!strncmp(str
,"Style:", 6)) {
517 process_style(track
, p
);
522 static int process_info_line(ass_track_t
* track
, char *str
)
524 if (!strncmp(str
, "PlayResX:", 9)) {
525 track
->PlayResX
= atoi(str
+ 9);
526 } else if (!strncmp(str
,"PlayResY:", 9)) {
527 track
->PlayResY
= atoi(str
+ 9);
528 } else if (!strncmp(str
,"Timer:", 6)) {
529 track
->Timer
= atof(str
+ 6);
530 } else if (!strncmp(str
,"WrapStyle:", 10)) {
531 track
->WrapStyle
= atoi(str
+ 10);
532 } else if (!strncmp(str
, "ScaledBorderAndShadow:", 22)) {
533 track
->ScaledBorderAndShadow
= parse_bool(str
+ 22);
538 static int process_events_line(ass_track_t
* track
, char *str
)
540 if (!strncmp(str
, "Format:", 7)) {
543 track
->event_format
= strdup(p
);
544 mp_msg(MSGT_ASS
, MSGL_DBG2
, "Event format: %s\n", track
->event_format
);
545 } else if (!strncmp(str
, "Dialogue:", 9)) {
546 // This should never be reached for embedded subtitles.
547 // They have slightly different format and are parsed in ass_process_chunk,
548 // called directly from demuxer
555 eid
= ass_alloc_event(track
);
556 event
= track
->events
+ eid
;
558 process_event_tail(track
, event
, str
, 0);
560 mp_msg(MSGT_ASS
, MSGL_V
, "Not understood: %s \n", str
);
565 // Copied from mkvtoolnix
566 static unsigned char* decode_chars(unsigned char c1
, unsigned char c2
,
567 unsigned char c3
, unsigned char c4
, unsigned char* dst
, int cnt
)
570 unsigned char bytes
[3];
573 value
= ((c1
- 33) << 18) + ((c2
- 33) << 12) + ((c3
- 33) << 6) + (c4
- 33);
574 bytes
[2] = value
& 0xff;
575 bytes
[1] = (value
& 0xff00) >> 8;
576 bytes
[0] = (value
& 0xff0000) >> 16;
578 for (i
= 0; i
< cnt
; ++i
)
583 static int decode_font(ass_track_t
* track
)
588 int size
; // original size
589 int dsize
; // decoded size
590 unsigned char* buf
= 0;
592 mp_msg(MSGT_ASS
, MSGL_V
, "font: %d bytes encoded data \n", track
->parser_priv
->fontdata_used
);
593 size
= track
->parser_priv
->fontdata_used
;
595 mp_msg(MSGT_ASS
, MSGL_ERR
, MSGTR_LIBASS_BadEncodedDataSize
);
596 goto error_decode_font
;
598 buf
= malloc(size
/ 4 * 3 + 2);
600 for (i
= 0, p
= (unsigned char*)track
->parser_priv
->fontdata
; i
< size
/ 4; i
++, p
+=4) {
601 q
= decode_chars(p
[0], p
[1], p
[2], p
[3], q
, 3);
604 q
= decode_chars(p
[0], p
[1], 0, 0, q
, 1);
605 } else if (size
% 4 == 3) {
606 q
= decode_chars(p
[0], p
[1], p
[2], 0, q
, 2);
609 assert(dsize
<= size
/ 4 * 3 + 2);
611 if (track
->library
->extract_fonts
) {
612 ass_add_font(track
->library
, track
->parser_priv
->fontname
, (char*)buf
, dsize
);
618 free(track
->parser_priv
->fontname
);
619 free(track
->parser_priv
->fontdata
);
620 track
->parser_priv
->fontname
= 0;
621 track
->parser_priv
->fontdata
= 0;
622 track
->parser_priv
->fontdata_size
= 0;
623 track
->parser_priv
->fontdata_used
= 0;
627 static int process_fonts_line(ass_track_t
* track
, char *str
)
631 if (!strncmp(str
, "fontname:", 9)) {
634 if (track
->parser_priv
->fontname
) {
637 track
->parser_priv
->fontname
= strdup(p
);
638 mp_msg(MSGT_ASS
, MSGL_V
, "fontname: %s\n", track
->parser_priv
->fontname
);
642 if (!track
->parser_priv
->fontname
) {
643 mp_msg(MSGT_ASS
, MSGL_V
, "Not understood: %s \n", str
);
649 mp_msg(MSGT_ASS
, MSGL_WARN
, MSGTR_LIBASS_FontLineTooLong
, len
, str
);
652 if (track
->parser_priv
->fontdata_used
+ len
> track
->parser_priv
->fontdata_size
) {
653 track
->parser_priv
->fontdata_size
+= 100 * 1024;
654 track
->parser_priv
->fontdata
= realloc(track
->parser_priv
->fontdata
, track
->parser_priv
->fontdata_size
);
656 memcpy(track
->parser_priv
->fontdata
+ track
->parser_priv
->fontdata_used
, str
, len
);
657 track
->parser_priv
->fontdata_used
+= len
;
663 * \brief Parse a header line
665 * \param str string to parse, zero-terminated
667 static int process_line(ass_track_t
* track
, char *str
)
669 if (!strncasecmp(str
, "[Script Info]", 13)) {
670 track
->parser_priv
->state
= PST_INFO
;
671 } else if (!strncasecmp(str
, "[V4 Styles]", 11)) {
672 track
->parser_priv
->state
= PST_STYLES
;
673 track
->track_type
= TRACK_TYPE_SSA
;
674 } else if (!strncasecmp(str
, "[V4+ Styles]", 12)) {
675 track
->parser_priv
->state
= PST_STYLES
;
676 track
->track_type
= TRACK_TYPE_ASS
;
677 } else if (!strncasecmp(str
, "[Events]", 8)) {
678 track
->parser_priv
->state
= PST_EVENTS
;
679 } else if (!strncasecmp(str
, "[Fonts]", 7)) {
680 track
->parser_priv
->state
= PST_FONTS
;
682 switch (track
->parser_priv
->state
) {
684 process_info_line(track
, str
);
687 process_styles_line(track
, str
);
690 process_events_line(track
, str
);
693 process_fonts_line(track
, str
);
700 // there is no explicit end-of-font marker in ssa/ass
701 if ((track
->parser_priv
->state
!= PST_FONTS
) && (track
->parser_priv
->fontname
))
707 static int process_text(ass_track_t
* track
, char* str
)
713 if ((*p
=='\r')||(*p
=='\n')) ++p
;
714 else if (p
[0]=='\xef' && p
[1]=='\xbb' && p
[2]=='\xbf') p
+=3; // U+FFFE (BOM)
717 for (q
=p
; ((*q
!='\0')&&(*q
!='\r')&&(*q
!='\n')); ++q
) {};
722 process_line(track
, p
);
731 * \brief Process a chunk of subtitle stream data.
733 * \param data string to parse
734 * \param size length of data
736 void ass_process_data(ass_track_t
* track
, char* data
, int size
)
738 char* str
= malloc(size
+ 1);
740 memcpy(str
, data
, size
);
743 mp_msg(MSGT_ASS
, MSGL_V
, "event: %s\n", str
);
744 process_text(track
, str
);
749 * \brief Process CodecPrivate section of subtitle stream
751 * \param data string to parse
752 * \param size length of data
753 CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections
755 void ass_process_codec_private(ass_track_t
* track
, char *data
, int size
)
757 ass_process_data(track
, data
, size
);
759 if (!track
->event_format
) {
760 // probably an mkv produced by ancient mkvtoolnix
761 // such files don't have [Events] and Format: headers
762 track
->parser_priv
->state
= PST_EVENTS
;
763 if (track
->track_type
== TRACK_TYPE_SSA
)
764 track
->event_format
= strdup("Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text");
766 track
->event_format
= strdup("Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text");
769 process_force_style(track
);
772 static int check_duplicate_event(ass_track_t
* track
, int ReadOrder
)
775 for (i
= 0; i
<track
->n_events
- 1; ++i
) // ignoring last event, it is the one we are comparing with
776 if (track
->events
[i
].ReadOrder
== ReadOrder
)
782 * \brief Process a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary).
784 * \param data string to parse
785 * \param size length of data
786 * \param timecode starting time of the event (milliseconds)
787 * \param duration duration of the event (milliseconds)
789 void ass_process_chunk(ass_track_t
* track
, char *data
, int size
, long long timecode
, long long duration
)
797 if (!track
->event_format
) {
798 mp_msg(MSGT_ASS
, MSGL_WARN
, MSGTR_LIBASS_EventFormatHeaderMissing
);
802 str
= malloc(size
+ 1);
803 memcpy(str
, data
, size
);
805 mp_msg(MSGT_ASS
, MSGL_V
, "event at %" PRId64
", +%" PRId64
": %s \n", (int64_t)timecode
, (int64_t)duration
, str
);
807 eid
= ass_alloc_event(track
);
808 event
= track
->events
+ eid
;
814 event
->ReadOrder
= atoi(token
);
815 if (check_duplicate_event(track
, event
->ReadOrder
))
819 event
->Layer
= atoi(token
);
821 process_event_tail(track
, event
, p
, 3);
823 event
->Start
= timecode
;
824 event
->Duration
= duration
;
831 ass_free_event(track
, eid
);
837 /** \brief recode buffer to utf-8
838 * constraint: codepage != 0
839 * \param data pointer to text buffer
840 * \param size buffer size
841 * \return a pointer to recoded buffer, caller is responsible for freeing it
843 static char* sub_recode(char* data
, size_t size
, char* codepage
)
845 static iconv_t icdsc
= (iconv_t
)(-1);
846 char* tocp
= "UTF-8";
851 const char* cp_tmp
= codepage
;
853 char enca_lang
[3], enca_fallback
[100];
854 if (sscanf(codepage
, "enca:%2s:%99s", enca_lang
, enca_fallback
) == 2
855 || sscanf(codepage
, "ENCA:%2s:%99s", enca_lang
, enca_fallback
) == 2) {
856 cp_tmp
= guess_buffer_cp((unsigned char*)data
, size
, enca_lang
, enca_fallback
);
859 if ((icdsc
= iconv_open (tocp
, cp_tmp
)) != (iconv_t
)(-1)){
860 mp_msg(MSGT_ASS
,MSGL_V
,"LIBSUB: opened iconv descriptor.\n");
862 mp_msg(MSGT_ASS
,MSGL_ERR
,MSGTR_LIBASS_ErrorOpeningIconvDescriptor
);
868 size_t oleft
= size
- 1;
874 outbuf
= malloc(osize
);
880 rc
= iconv(icdsc
, &ip
, &ileft
, &op
, &oleft
);
881 else {// clear the conversion state and leave
883 rc
= iconv(icdsc
, NULL
, NULL
, &op
, &oleft
);
885 if (rc
== (size_t)(-1)) {
886 if (errno
== E2BIG
) {
887 size_t offset
= op
- outbuf
;
888 outbuf
= (char*)realloc(outbuf
, osize
+ size
);
889 op
= outbuf
+ offset
;
893 mp_msg(MSGT_ASS
, MSGL_WARN
, MSGTR_LIBASS_ErrorRecodingFile
);
900 outbuf
[osize
- oleft
- 1] = 0;
903 if (icdsc
!= (iconv_t
)(-1)) {
904 (void)iconv_close(icdsc
);
905 icdsc
= (iconv_t
)(-1);
906 mp_msg(MSGT_ASS
,MSGL_V
,"LIBSUB: closed iconv descriptor.\n");
914 * \brief read file contents into newly allocated buffer
915 * \param fname file name
916 * \param bufsize out: file size
917 * \return pointer to file contents. Caller is responsible for its deallocation.
919 static char* read_file(char* fname
, size_t *bufsize
)
926 FILE* fp
= fopen(fname
, "rb");
928 mp_msg(MSGT_ASS
, MSGL_WARN
, MSGTR_LIBASS_FopenFailed
, fname
);
931 res
= fseek(fp
, 0, SEEK_END
);
933 mp_msg(MSGT_ASS
, MSGL_WARN
, MSGTR_LIBASS_FseekFailed
, fname
);
941 if (sz
> 10*1024*1024) {
942 mp_msg(MSGT_ASS
, MSGL_INFO
, MSGTR_LIBASS_RefusingToLoadSubtitlesLargerThan10M
, fname
);
947 mp_msg(MSGT_ASS
, MSGL_V
, "file size: %ld\n", sz
);
949 buf
= malloc(sz
+ 1);
953 res
= fread(buf
+ bytes_read
, 1, sz
- bytes_read
, fp
);
955 mp_msg(MSGT_ASS
, MSGL_INFO
, MSGTR_LIBASS_ReadFailed
, errno
, strerror(errno
));
961 } while (sz
- bytes_read
> 0);
971 * \param buf pointer to subtitle text in utf-8
973 static ass_track_t
* parse_memory(ass_library_t
* library
, char* buf
)
978 track
= ass_new_track(library
);
981 process_text(track
, buf
);
983 // external SSA/ASS subs does not have ReadOrder field
984 for (i
= 0; i
< track
->n_events
; ++i
)
985 track
->events
[i
].ReadOrder
= i
;
987 // there is no explicit end-of-font marker in ssa/ass
988 if (track
->parser_priv
->fontname
)
991 if (track
->track_type
== TRACK_TYPE_UNKNOWN
) {
992 ass_free_track(track
);
996 process_force_style(track
);
1002 * \brief Read subtitles from memory.
1003 * \param library libass library object
1004 * \param buf pointer to subtitles text
1005 * \param bufsize size of buffer
1006 * \param codepage recode buffer contents from given codepage
1007 * \return newly allocated track
1009 ass_track_t
* ass_read_memory(ass_library_t
* library
, char* buf
, size_t bufsize
, char* codepage
)
1019 buf
= sub_recode(buf
, bufsize
, codepage
);
1025 track
= parse_memory(library
, buf
);
1031 mp_msg(MSGT_ASS
, MSGL_INFO
, MSGTR_LIBASS_AddedSubtitleFileMemory
, track
->n_styles
, track
->n_events
);
1035 char* read_file_recode(char* fname
, char* codepage
, size_t* size
)
1040 buf
= read_file(fname
, &bufsize
);
1045 char* tmpbuf
= sub_recode(buf
, bufsize
, codepage
);
1057 * \brief Read subtitles from file.
1058 * \param library libass library object
1059 * \param fname file name
1060 * \param codepage recode buffer contents from given codepage
1061 * \return newly allocated track
1063 ass_track_t
* ass_read_file(ass_library_t
* library
, char* fname
, char* codepage
)
1069 buf
= read_file_recode(fname
, codepage
, &bufsize
);
1072 track
= parse_memory(library
, buf
);
1077 track
->name
= strdup(fname
);
1079 mp_msg(MSGT_ASS
, MSGL_INFO
, MSGTR_LIBASS_AddedSubtitleFileFname
, fname
, track
->n_styles
, track
->n_events
);
1081 // dump_events(forced_tid);
1086 * \brief read styles from file into already initialized track
1088 int ass_read_styles(ass_track_t
* track
, char* fname
, char* codepage
)
1091 parser_state_t old_state
;
1094 buf
= read_file(fname
, &sz
);
1100 tmpbuf
= sub_recode(buf
, sz
, codepage
);
1108 old_state
= track
->parser_priv
->state
;
1109 track
->parser_priv
->state
= PST_STYLES
;
1110 process_text(track
, buf
);
1111 track
->parser_priv
->state
= old_state
;
1116 long long ass_step_sub(ass_track_t
* track
, long long now
, int movement
) {
1119 if (movement
== 0) return 0;
1120 if (track
->n_events
== 0) return 0;
1123 for (i
= 0; (i
< track
->n_events
) && ((long long)(track
->events
[i
].Start
+ track
->events
[i
].Duration
) <= now
); ++i
) {}
1125 for (i
= track
->n_events
- 1; (i
>= 0) && ((long long)(track
->events
[i
].Start
) > now
); --i
) {}
1127 // -1 and n_events are ok
1128 assert(i
>= -1); assert(i
<= track
->n_events
);
1131 if (i
>= track
->n_events
) i
= track
->n_events
- 1;
1132 return ((long long)track
->events
[i
].Start
) - now
;
1135 ass_track_t
* ass_new_track(ass_library_t
* library
) {
1136 ass_track_t
* track
= calloc(1, sizeof(ass_track_t
));
1137 track
->library
= library
;
1138 track
->ScaledBorderAndShadow
= 1;
1139 track
->parser_priv
= calloc(1, sizeof(parser_priv_t
));