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.
27 #include <sys/types.h>
38 #include "ass_utils.h"
39 #include "ass_library.h"
41 #define ass_atof(STR) (ass_strtod((STR),NULL))
59 #define ASS_STYLES_ALLOC 20
60 #define ASS_EVENTS_ALLOC 200
62 void ass_free_track(ASS_Track
*track
)
66 if (track
->parser_priv
) {
67 free(track
->parser_priv
->fontname
);
68 free(track
->parser_priv
->fontdata
);
69 free(track
->parser_priv
);
71 free(track
->style_format
);
72 free(track
->event_format
);
73 free(track
->Language
);
75 for (i
= 0; i
< track
->n_styles
; ++i
)
76 ass_free_style(track
, i
);
80 for (i
= 0; i
< track
->n_events
; ++i
)
81 ass_free_event(track
, i
);
88 /// \brief Allocate a new style struct
89 /// \param track track
91 int ass_alloc_style(ASS_Track
*track
)
95 assert(track
->n_styles
<= track
->max_styles
);
97 if (track
->n_styles
== track
->max_styles
) {
98 track
->max_styles
+= ASS_STYLES_ALLOC
;
100 (ASS_Style
*) realloc(track
->styles
,
105 sid
= track
->n_styles
++;
106 memset(track
->styles
+ sid
, 0, sizeof(ASS_Style
));
110 /// \brief Allocate a new event struct
111 /// \param track track
113 int ass_alloc_event(ASS_Track
*track
)
117 assert(track
->n_events
<= track
->max_events
);
119 if (track
->n_events
== track
->max_events
) {
120 track
->max_events
+= ASS_EVENTS_ALLOC
;
122 (ASS_Event
*) realloc(track
->events
,
127 eid
= track
->n_events
++;
128 memset(track
->events
+ eid
, 0, sizeof(ASS_Event
));
132 void ass_free_event(ASS_Track
*track
, int eid
)
134 ASS_Event
*event
= track
->events
+ eid
;
139 free(event
->render_priv
);
142 void ass_free_style(ASS_Track
*track
, int sid
)
144 ASS_Style
*style
= track
->styles
+ sid
;
147 free(style
->FontName
);
150 // ==============================================================================================
152 static void skip_spaces(char **str
)
155 while ((*p
== ' ') || (*p
== '\t'))
160 static void rskip_spaces(char **str
, char *limit
)
163 while ((p
>= limit
) && ((*p
== ' ') || (*p
== '\t')))
169 * \brief Set up default style
170 * \param style style to edit to defaults
171 * The parameters are mostly taken directly from VSFilter source for
172 * best compatibility.
174 static void set_default_style(ASS_Style
*style
)
176 style
->Name
= strdup("Default");
177 style
->FontName
= strdup("Arial");
178 style
->FontSize
= 18;
179 style
->PrimaryColour
= 0xffffff00;
180 style
->SecondaryColour
= 0x00ffff00;
181 style
->OutlineColour
= 0x00000000;
182 style
->BackColour
= 0x00000080;
187 style
->BorderStyle
= 1;
190 style
->Alignment
= 2;
191 style
->MarginL
= style
->MarginR
= style
->MarginV
= 20;
195 * \brief find style by name
197 * \param name style name
198 * \return index in track->styles
199 * Returnes 0 if no styles found => expects at least 1 style.
200 * Parsing code always adds "Default" style in the end.
202 static int lookup_style(ASS_Track
*track
, char *name
)
206 ++name
; // FIXME: what does '*' really mean ?
207 for (i
= track
->n_styles
- 1; i
>= 0; --i
) {
208 if (strcmp(track
->styles
[i
].Name
, name
) == 0)
211 i
= track
->default_style
;
212 ass_msg(track
->library
, MSGL_WARN
,
213 "[%p]: Warning: no style named '%s' found, using '%s'",
214 track
, name
, track
->styles
[i
].Name
);
215 return i
; // use the first style
218 static uint32_t string2color(ASS_Library
*library
, char *p
)
221 (void) strtocolor(library
, &p
, &tmp
, 0);
225 static long long string2timecode(ASS_Library
*library
, char *p
)
227 unsigned h
, m
, s
, ms
;
229 int res
= sscanf(p
, "%1d:%2d:%2d.%2d", &h
, &m
, &s
, &ms
);
231 ass_msg(library
, MSGL_WARN
, "Bad timestamp");
234 tm
= ((h
* 60 + m
) * 60 + s
) * 1000 + ms
* 10;
239 * \brief converts numpad-style align to align.
241 static int numpad2align(int val
)
244 v
= (val
- 1) / 3; // 0, 1 or 2 for vertical alignment
247 res
= ((val
- 1) % 3) + 1; // horizontal alignment
252 #define NEXT(str,token) \
253 token = next_token(&str); \
256 #define ANYVAL(name,func) \
257 } else if (strcasecmp(tname, #name) == 0) { \
258 target->name = func(token); \
259 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
261 #define STRVAL(name) \
262 } else if (strcasecmp(tname, #name) == 0) { \
263 if (target->name != NULL) free(target->name); \
264 target->name = strdup(token); \
265 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
267 #define COLORVAL(name) \
268 } else if (strcasecmp(tname, #name) == 0) { \
269 target->name = string2color(track->library, token); \
270 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
272 #define INTVAL(name) ANYVAL(name,atoi)
273 #define FPVAL(name) ANYVAL(name,ass_atof)
274 #define TIMEVAL(name) \
275 } else if (strcasecmp(tname, #name) == 0) { \
276 target->name = string2timecode(track->library, token); \
277 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
279 #define STYLEVAL(name) \
280 } else if (strcasecmp(tname, #name) == 0) { \
281 target->name = lookup_style(track, token); \
282 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
284 #define ALIAS(alias,name) \
285 if (strcasecmp(tname, #alias) == 0) {tname = #name;}
287 static char *next_token(char **str
)
296 start
= p
; // start of the token
297 for (; (*p
!= '\0') && (*p
!= ','); ++p
) {
300 *str
= p
; // eos found, str will point to '\0' at exit
303 *str
= p
+ 1; // ',' found, str will point to the next char (beginning of the next token)
305 --p
; // end of current token
306 rskip_spaces(&p
, start
);
308 p
= start
; // empty token
310 ++p
; // the first space character, or '\0'
316 * \brief Parse the tail of Dialogue line
318 * \param event parsed data goes here
319 * \param str string to parse, zero-terminated
320 * \param n_ignored number of format options to skip at the beginning
322 static int process_event_tail(ASS_Track
*track
, ASS_Event
*event
,
323 char *str
, int n_ignored
)
329 ASS_Event
*target
= event
;
331 char *format
= strdup(track
->event_format
);
332 char *q
= format
; // format scanning pointer
334 if (track
->n_styles
== 0) {
335 // add "Default" style to the end
336 // will be used if track does not contain a default style (or even does not contain styles at all)
337 int sid
= ass_alloc_style(track
);
338 set_default_style(&track
->styles
[sid
]);
339 track
->default_style
= sid
;
342 for (i
= 0; i
< n_ignored
; ++i
) {
348 if (strcasecmp(tname
, "Text") == 0) {
350 event
->Text
= strdup(p
);
351 if (*event
->Text
!= 0) {
352 last
= event
->Text
+ strlen(event
->Text
) - 1;
353 if (last
>= event
->Text
&& *last
== '\r')
356 ass_msg(track
->library
, MSGL_DBG2
, "Text = %s", event
->Text
);
357 event
->Duration
-= event
->Start
;
359 return 0; // "Text" is always the last
363 ALIAS(End
, Duration
) // temporarily store end timecode in event->Duration
381 * \brief Parse command line style overrides (--ass-force-style option)
382 * \param track track to apply overrides to
383 * The format for overrides is [StyleName.]Field=Value
385 void ass_process_force_style(ASS_Track
*track
)
387 char **fs
, *eq
, *dt
, *style
, *tname
, *token
;
390 char **list
= track
->library
->style_overrides
;
395 for (fs
= list
; *fs
; ++fs
) {
396 eq
= strrchr(*fs
, '=');
402 if (!strcasecmp(*fs
, "PlayResX"))
403 track
->PlayResX
= atoi(token
);
404 else if (!strcasecmp(*fs
, "PlayResY"))
405 track
->PlayResY
= atoi(token
);
406 else if (!strcasecmp(*fs
, "Timer"))
407 track
->Timer
= ass_atof(token
);
408 else if (!strcasecmp(*fs
, "WrapStyle"))
409 track
->WrapStyle
= atoi(token
);
410 else if (!strcasecmp(*fs
, "ScaledBorderAndShadow"))
411 track
->ScaledBorderAndShadow
= parse_bool(token
);
412 else if (!strcasecmp(*fs
, "Kerning"))
413 track
->Kerning
= parse_bool(token
);
415 dt
= strrchr(*fs
, '.');
424 for (sid
= 0; sid
< track
->n_styles
; ++sid
) {
426 || strcasecmp(track
->styles
[sid
].Name
, style
) == 0) {
427 target
= track
->styles
+ sid
;
430 COLORVAL(PrimaryColour
)
431 COLORVAL(SecondaryColour
)
432 COLORVAL(OutlineColour
)
461 * \brief Parse the Style line
463 * \param str string to parse, zero-terminated
464 * Allocates a new style struct.
466 static int process_style(ASS_Track
*track
, char *str
)
473 char *q
; // format scanning pointer
478 if (!track
->style_format
) {
479 // no style format header
480 // probably an ancient script version
481 if (track
->track_type
== TRACK_TYPE_SSA
)
482 track
->style_format
=
484 ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
485 "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline,"
486 "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
488 track
->style_format
=
490 ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
491 "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut,"
492 "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow,"
493 "Alignment, MarginL, MarginR, MarginV, Encoding");
496 q
= format
= strdup(track
->style_format
);
498 // Add default style first
499 if (track
->n_styles
== 0) {
500 // will be used if track does not contain a default style (or even does not contain styles at all)
501 int sid
= ass_alloc_style(track
);
502 set_default_style(&track
->styles
[sid
]);
503 track
->default_style
= sid
;
506 ass_msg(track
->library
, MSGL_V
, "[%p] Style: %s", track
, str
);
508 sid
= ass_alloc_style(track
);
510 style
= track
->styles
+ sid
;
513 // fill style with some default values
514 style
->ScaleX
= 100.;
515 style
->ScaleY
= 100.;
523 if ((strcmp(target
->Name
, "Default") == 0)
524 || (strcmp(target
->Name
, "*Default") == 0))
525 track
->default_style
= sid
;
527 COLORVAL(PrimaryColour
)
528 COLORVAL(SecondaryColour
)
529 COLORVAL(OutlineColour
) // TertiaryColor
531 // SSA uses BackColour for both outline and shadow
532 // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
533 if (track
->track_type
== TRACK_TYPE_SSA
)
534 target
->OutlineColour
= target
->BackColour
;
544 if (track
->track_type
== TRACK_TYPE_ASS
)
545 target
->Alignment
= numpad2align(target
->Alignment
);
556 style
->ScaleX
/= 100.;
557 style
->ScaleY
/= 100.;
558 style
->Bold
= !!style
->Bold
;
559 style
->Italic
= !!style
->Italic
;
560 style
->Underline
= !!style
->Underline
;
562 style
->Name
= strdup("Default");
563 if (!style
->FontName
)
564 style
->FontName
= strdup("Arial");
570 static int process_styles_line(ASS_Track
*track
, char *str
)
572 if (!strncmp(str
, "Format:", 7)) {
575 track
->style_format
= strdup(p
);
576 ass_msg(track
->library
, MSGL_DBG2
, "Style format: %s",
577 track
->style_format
);
578 } else if (!strncmp(str
, "Style:", 6)) {
581 process_style(track
, p
);
586 static int process_info_line(ASS_Track
*track
, char *str
)
588 if (!strncmp(str
, "PlayResX:", 9)) {
589 track
->PlayResX
= atoi(str
+ 9);
590 } else if (!strncmp(str
, "PlayResY:", 9)) {
591 track
->PlayResY
= atoi(str
+ 9);
592 } else if (!strncmp(str
, "Timer:", 6)) {
593 track
->Timer
= ass_atof(str
+ 6);
594 } else if (!strncmp(str
, "WrapStyle:", 10)) {
595 track
->WrapStyle
= atoi(str
+ 10);
596 } else if (!strncmp(str
, "ScaledBorderAndShadow:", 22)) {
597 track
->ScaledBorderAndShadow
= parse_bool(str
+ 22);
598 } else if (!strncmp(str
, "Kerning:", 8)) {
599 track
->Kerning
= parse_bool(str
+ 8);
600 } else if (!strncmp(str
, "Language:", 9)) {
602 while (*p
&& isspace(*p
)) p
++;
603 track
->Language
= malloc(3);
604 strncpy(track
->Language
, p
, 2);
605 track
->Language
[2] = 0;
610 static void event_format_fallback(ASS_Track
*track
)
612 track
->parser_priv
->state
= PST_EVENTS
;
613 if (track
->track_type
== TRACK_TYPE_SSA
)
614 track
->event_format
= strdup("Format: Marked, Start, End, Style, "
615 "Name, MarginL, MarginR, MarginV, Effect, Text");
617 track
->event_format
= strdup("Format: Layer, Start, End, Style, "
618 "Actor, MarginL, MarginR, MarginV, Effect, Text");
619 ass_msg(track
->library
, MSGL_V
,
620 "No event format found, using fallback");
623 static int process_events_line(ASS_Track
*track
, char *str
)
625 if (!strncmp(str
, "Format:", 7)) {
628 free(track
->event_format
);
629 track
->event_format
= strdup(p
);
630 ass_msg(track
->library
, MSGL_DBG2
, "Event format: %s", track
->event_format
);
631 } else if (!strncmp(str
, "Dialogue:", 9)) {
632 // This should never be reached for embedded subtitles.
633 // They have slightly different format and are parsed in ass_process_chunk,
634 // called directly from demuxer
641 eid
= ass_alloc_event(track
);
642 event
= track
->events
+ eid
;
644 // We can't parse events with event_format
645 if (!track
->event_format
)
646 event_format_fallback(track
);
648 process_event_tail(track
, event
, str
, 0);
650 ass_msg(track
->library
, MSGL_V
, "Not understood: '%.30s'", str
);
655 // Copied from mkvtoolnix
656 static unsigned char *decode_chars(unsigned char c1
, unsigned char c2
,
657 unsigned char c3
, unsigned char c4
,
658 unsigned char *dst
, int cnt
)
661 unsigned char bytes
[3];
665 ((c1
- 33) << 18) + ((c2
- 33) << 12) + ((c3
- 33) << 6) + (c4
-
667 bytes
[2] = value
& 0xff;
668 bytes
[1] = (value
& 0xff00) >> 8;
669 bytes
[0] = (value
& 0xff0000) >> 16;
671 for (i
= 0; i
< cnt
; ++i
)
676 static int decode_font(ASS_Track
*track
)
681 int size
; // original size
682 int dsize
; // decoded size
683 unsigned char *buf
= 0;
685 ass_msg(track
->library
, MSGL_V
, "Font: %d bytes encoded data",
686 track
->parser_priv
->fontdata_used
);
687 size
= track
->parser_priv
->fontdata_used
;
689 ass_msg(track
->library
, MSGL_ERR
, "Bad encoded data size");
690 goto error_decode_font
;
692 buf
= malloc(size
/ 4 * 3 + 2);
694 for (i
= 0, p
= (unsigned char *) track
->parser_priv
->fontdata
;
695 i
< size
/ 4; i
++, p
+= 4) {
696 q
= decode_chars(p
[0], p
[1], p
[2], p
[3], q
, 3);
699 q
= decode_chars(p
[0], p
[1], 0, 0, q
, 1);
700 } else if (size
% 4 == 3) {
701 q
= decode_chars(p
[0], p
[1], p
[2], 0, q
, 2);
704 assert(dsize
<= size
/ 4 * 3 + 2);
706 if (track
->library
->extract_fonts
) {
707 ass_add_font(track
->library
, track
->parser_priv
->fontname
,
708 (char *) buf
, dsize
);
713 free(track
->parser_priv
->fontname
);
714 free(track
->parser_priv
->fontdata
);
715 track
->parser_priv
->fontname
= 0;
716 track
->parser_priv
->fontdata
= 0;
717 track
->parser_priv
->fontdata_size
= 0;
718 track
->parser_priv
->fontdata_used
= 0;
722 static int process_fonts_line(ASS_Track
*track
, char *str
)
726 if (!strncmp(str
, "fontname:", 9)) {
729 if (track
->parser_priv
->fontname
) {
732 track
->parser_priv
->fontname
= strdup(p
);
733 ass_msg(track
->library
, MSGL_V
, "Fontname: %s",
734 track
->parser_priv
->fontname
);
738 if (!track
->parser_priv
->fontname
) {
739 ass_msg(track
->library
, MSGL_V
, "Not understood: '%s'", str
);
745 ass_msg(track
->library
, MSGL_WARN
, "Font line too long: %d, %s",
749 if (track
->parser_priv
->fontdata_used
+ len
>
750 track
->parser_priv
->fontdata_size
) {
751 track
->parser_priv
->fontdata_size
+= 100 * 1024;
752 track
->parser_priv
->fontdata
=
753 realloc(track
->parser_priv
->fontdata
,
754 track
->parser_priv
->fontdata_size
);
756 memcpy(track
->parser_priv
->fontdata
+ track
->parser_priv
->fontdata_used
,
758 track
->parser_priv
->fontdata_used
+= len
;
764 * \brief Parse a header line
766 * \param str string to parse, zero-terminated
768 static int process_line(ASS_Track
*track
, char *str
)
770 if (!strncasecmp(str
, "[Script Info]", 13)) {
771 track
->parser_priv
->state
= PST_INFO
;
772 } else if (!strncasecmp(str
, "[V4 Styles]", 11)) {
773 track
->parser_priv
->state
= PST_STYLES
;
774 track
->track_type
= TRACK_TYPE_SSA
;
775 } else if (!strncasecmp(str
, "[V4+ Styles]", 12)) {
776 track
->parser_priv
->state
= PST_STYLES
;
777 track
->track_type
= TRACK_TYPE_ASS
;
778 } else if (!strncasecmp(str
, "[Events]", 8)) {
779 track
->parser_priv
->state
= PST_EVENTS
;
780 } else if (!strncasecmp(str
, "[Fonts]", 7)) {
781 track
->parser_priv
->state
= PST_FONTS
;
783 switch (track
->parser_priv
->state
) {
785 process_info_line(track
, str
);
788 process_styles_line(track
, str
);
791 process_events_line(track
, str
);
794 process_fonts_line(track
, str
);
801 // there is no explicit end-of-font marker in ssa/ass
802 if ((track
->parser_priv
->state
!= PST_FONTS
)
803 && (track
->parser_priv
->fontname
))
809 static int process_text(ASS_Track
*track
, char *str
)
815 if ((*p
== '\r') || (*p
== '\n'))
817 else if (p
[0] == '\xef' && p
[1] == '\xbb' && p
[2] == '\xbf')
818 p
+= 3; // U+FFFE (BOM)
822 for (q
= p
; ((*q
!= '\0') && (*q
!= '\r') && (*q
!= '\n')); ++q
) {
828 process_line(track
, p
);
837 * \brief Process a chunk of subtitle stream data.
839 * \param data string to parse
840 * \param size length of data
842 void ass_process_data(ASS_Track
*track
, char *data
, int size
)
844 char *str
= malloc(size
+ 1);
846 memcpy(str
, data
, size
);
849 ass_msg(track
->library
, MSGL_V
, "Event: %s", str
);
850 process_text(track
, str
);
855 * \brief Process CodecPrivate section of subtitle stream
857 * \param data string to parse
858 * \param size length of data
859 CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections
861 void ass_process_codec_private(ASS_Track
*track
, char *data
, int size
)
863 ass_process_data(track
, data
, size
);
865 // probably an mkv produced by ancient mkvtoolnix
866 // such files don't have [Events] and Format: headers
867 if (!track
->event_format
)
868 event_format_fallback(track
);
870 ass_process_force_style(track
);
873 static int check_duplicate_event(ASS_Track
*track
, int ReadOrder
)
876 for (i
= 0; i
< track
->n_events
- 1; ++i
) // ignoring last event, it is the one we are comparing with
877 if (track
->events
[i
].ReadOrder
== ReadOrder
)
883 * \brief Process a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary).
885 * \param data string to parse
886 * \param size length of data
887 * \param timecode starting time of the event (milliseconds)
888 * \param duration duration of the event (milliseconds)
890 void ass_process_chunk(ASS_Track
*track
, char *data
, int size
,
891 long long timecode
, long long duration
)
899 if (!track
->event_format
) {
900 ass_msg(track
->library
, MSGL_WARN
, "Event format header missing");
904 str
= malloc(size
+ 1);
905 memcpy(str
, data
, size
);
907 ass_msg(track
->library
, MSGL_V
, "Event at %" PRId64
", +%" PRId64
": %s",
908 (int64_t) timecode
, (int64_t) duration
, str
);
910 eid
= ass_alloc_event(track
);
911 event
= track
->events
+ eid
;
917 event
->ReadOrder
= atoi(token
);
918 if (check_duplicate_event(track
, event
->ReadOrder
))
922 event
->Layer
= atoi(token
);
924 process_event_tail(track
, event
, p
, 3);
926 event
->Start
= timecode
;
927 event
->Duration
= duration
;
934 ass_free_event(track
, eid
);
940 * \brief Flush buffered events.
943 void ass_flush_events(ASS_Track
*track
)
947 for (eid
= 0; eid
< track
->n_events
; eid
++)
948 ass_free_event(track
, eid
);
954 /** \brief recode buffer to utf-8
955 * constraint: codepage != 0
956 * \param data pointer to text buffer
957 * \param size buffer size
958 * \return a pointer to recoded buffer, caller is responsible for freeing it
960 static char *sub_recode(ASS_Library
*library
, char *data
, size_t size
,
964 char *tocp
= "UTF-8";
969 const char *cp_tmp
= codepage
;
971 char enca_lang
[3], enca_fallback
[100];
972 if (sscanf(codepage
, "enca:%2s:%99s", enca_lang
, enca_fallback
) == 2
973 || sscanf(codepage
, "ENCA:%2s:%99s", enca_lang
,
974 enca_fallback
) == 2) {
976 ass_guess_buffer_cp(library
, (unsigned char *) data
, size
,
977 enca_lang
, enca_fallback
);
980 if ((icdsc
= iconv_open(tocp
, cp_tmp
)) != (iconv_t
) (-1)) {
981 ass_msg(library
, MSGL_V
, "Opened iconv descriptor");
983 ass_msg(library
, MSGL_ERR
, "Error opening iconv descriptor");
989 size_t oleft
= size
- 1;
995 outbuf
= malloc(osize
);
1001 rc
= iconv(icdsc
, &ip
, &ileft
, &op
, &oleft
);
1002 else { // clear the conversion state and leave
1004 rc
= iconv(icdsc
, NULL
, NULL
, &op
, &oleft
);
1006 if (rc
== (size_t) (-1)) {
1007 if (errno
== E2BIG
) {
1008 size_t offset
= op
- outbuf
;
1009 outbuf
= (char *) realloc(outbuf
, osize
+ size
);
1010 op
= outbuf
+ offset
;
1014 ass_msg(library
, MSGL_WARN
, "Error recoding file");
1020 outbuf
[osize
- oleft
- 1] = 0;
1023 if (icdsc
!= (iconv_t
) (-1)) {
1024 (void) iconv_close(icdsc
);
1025 icdsc
= (iconv_t
) (-1);
1026 ass_msg(library
, MSGL_V
, "Closed iconv descriptor");
1034 * \brief read file contents into newly allocated buffer
1035 * \param fname file name
1036 * \param bufsize out: file size
1037 * \return pointer to file contents. Caller is responsible for its deallocation.
1039 static char *read_file(ASS_Library
*library
, char *fname
, size_t *bufsize
)
1046 FILE *fp
= fopen(fname
, "rb");
1048 ass_msg(library
, MSGL_WARN
,
1049 "ass_read_file(%s): fopen failed", fname
);
1052 res
= fseek(fp
, 0, SEEK_END
);
1054 ass_msg(library
, MSGL_WARN
,
1055 "ass_read_file(%s): fseek failed", fname
);
1063 ass_msg(library
, MSGL_V
, "File size: %ld", sz
);
1065 buf
= malloc(sz
+ 1);
1069 res
= fread(buf
+ bytes_read
, 1, sz
- bytes_read
, fp
);
1071 ass_msg(library
, MSGL_INFO
, "Read failed, %d: %s", errno
,
1078 } while (sz
- bytes_read
> 0);
1088 * \param buf pointer to subtitle text in utf-8
1090 static ASS_Track
*parse_memory(ASS_Library
*library
, char *buf
)
1095 track
= ass_new_track(library
);
1098 process_text(track
, buf
);
1100 // external SSA/ASS subs does not have ReadOrder field
1101 for (i
= 0; i
< track
->n_events
; ++i
)
1102 track
->events
[i
].ReadOrder
= i
;
1104 // there is no explicit end-of-font marker in ssa/ass
1105 if (track
->parser_priv
->fontname
)
1108 if (track
->track_type
== TRACK_TYPE_UNKNOWN
) {
1109 ass_free_track(track
);
1113 ass_process_force_style(track
);
1119 * \brief Read subtitles from memory.
1120 * \param library libass library object
1121 * \param buf pointer to subtitles text
1122 * \param bufsize size of buffer
1123 * \param codepage recode buffer contents from given codepage
1124 * \return newly allocated track
1126 ASS_Track
*ass_read_memory(ASS_Library
*library
, char *buf
,
1127 size_t bufsize
, char *codepage
)
1137 buf
= sub_recode(library
, buf
, bufsize
, codepage
);
1144 track
= parse_memory(library
, buf
);
1150 ass_msg(library
, MSGL_INFO
, "Added subtitle file: "
1151 "<memory> (%d styles, %d events)",
1152 track
->n_styles
, track
->n_events
);
1156 static char *read_file_recode(ASS_Library
*library
, char *fname
,
1157 char *codepage
, size_t *size
)
1162 buf
= read_file(library
, fname
, &bufsize
);
1167 char *tmpbuf
= sub_recode(library
, buf
, bufsize
, codepage
);
1179 * \brief Read subtitles from file.
1180 * \param library libass library object
1181 * \param fname file name
1182 * \param codepage recode buffer contents from given codepage
1183 * \return newly allocated track
1185 ASS_Track
*ass_read_file(ASS_Library
*library
, char *fname
,
1192 buf
= read_file_recode(library
, fname
, codepage
, &bufsize
);
1195 track
= parse_memory(library
, buf
);
1200 track
->name
= strdup(fname
);
1202 ass_msg(library
, MSGL_INFO
,
1203 "Added subtitle file: '%s' (%d styles, %d events)",
1204 fname
, track
->n_styles
, track
->n_events
);
1210 * \brief read styles from file into already initialized track
1212 int ass_read_styles(ASS_Track
*track
, char *fname
, char *codepage
)
1215 ParserState old_state
;
1218 buf
= read_file(track
->library
, fname
, &sz
);
1224 tmpbuf
= sub_recode(track
->library
, buf
, sz
, codepage
);
1232 old_state
= track
->parser_priv
->state
;
1233 track
->parser_priv
->state
= PST_STYLES
;
1234 process_text(track
, buf
);
1235 track
->parser_priv
->state
= old_state
;
1240 long long ass_step_sub(ASS_Track
*track
, long long now
, int movement
)
1246 if (track
->n_events
== 0)
1251 (i
< track
->n_events
)
1253 ((long long) (track
->events
[i
].Start
+
1254 track
->events
[i
].Duration
) <= now
); ++i
) {
1256 for (i
= track
->n_events
- 1;
1257 (i
>= 0) && ((long long) (track
->events
[i
].Start
) > now
);
1261 // -1 and n_events are ok
1263 assert(i
<= track
->n_events
);
1267 if (i
>= track
->n_events
)
1268 i
= track
->n_events
- 1;
1269 return ((long long) track
->events
[i
].Start
) - now
;
1272 ASS_Track
*ass_new_track(ASS_Library
*library
)
1274 ASS_Track
*track
= calloc(1, sizeof(ASS_Track
));
1275 track
->library
= library
;
1276 track
->ScaledBorderAndShadow
= 1;
1277 track
->parser_priv
= calloc(1, sizeof(ASS_ParserPriv
));
1282 * \brief Prepare track for rendering
1284 void ass_lazy_track_init(ASS_Library
*lib
, ASS_Track
*track
)
1286 if (track
->PlayResX
&& track
->PlayResY
)
1288 if (!track
->PlayResX
&& !track
->PlayResY
) {
1289 ass_msg(lib
, MSGL_WARN
,
1290 "Neither PlayResX nor PlayResY defined. Assuming 384x288");
1291 track
->PlayResX
= 384;
1292 track
->PlayResY
= 288;
1294 if (!track
->PlayResY
&& track
->PlayResX
== 1280) {
1295 track
->PlayResY
= 1024;
1296 ass_msg(lib
, MSGL_WARN
,
1297 "PlayResY undefined, setting to %d", track
->PlayResY
);
1298 } else if (!track
->PlayResY
) {
1299 track
->PlayResY
= track
->PlayResX
* 3 / 4;
1300 ass_msg(lib
, MSGL_WARN
,
1301 "PlayResY undefined, setting to %d", track
->PlayResY
);
1302 } else if (!track
->PlayResX
&& track
->PlayResY
== 1024) {
1303 track
->PlayResX
= 1280;
1304 ass_msg(lib
, MSGL_WARN
,
1305 "PlayResX undefined, setting to %d", track
->PlayResX
);
1306 } else if (!track
->PlayResX
) {
1307 track
->PlayResX
= track
->PlayResY
* 4 / 3;
1308 ass_msg(lib
, MSGL_WARN
,
1309 "PlayResX undefined, setting to %d", track
->PlayResX
);