test: separate linker flags
[libass.git] / libass / ass.c
blob712c16cb2724f19aecd3ab49e30c602bad3d79c2
1 /*
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.
19 #include "config.h"
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <strings.h>
25 #include <assert.h>
26 #include <errno.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30 #include <inttypes.h>
31 #include <ctype.h>
33 #ifdef CONFIG_ICONV
34 #include <iconv.h>
35 #endif
37 #include "ass.h"
38 #include "ass_utils.h"
39 #include "ass_library.h"
41 #define ass_atof(STR) (ass_strtod((STR),NULL))
43 typedef enum {
44 PST_UNKNOWN = 0,
45 PST_INFO,
46 PST_STYLES,
47 PST_EVENTS,
48 PST_FONTS
49 } ParserState;
51 struct parser_priv {
52 ParserState state;
53 char *fontname;
54 char *fontdata;
55 int fontdata_size;
56 int fontdata_used;
59 #define ASS_STYLES_ALLOC 20
60 #define ASS_EVENTS_ALLOC 200
62 void ass_free_track(ASS_Track *track)
64 int i;
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);
74 if (track->styles) {
75 for (i = 0; i < track->n_styles; ++i)
76 ass_free_style(track, i);
78 free(track->styles);
79 if (track->events) {
80 for (i = 0; i < track->n_events; ++i)
81 ass_free_event(track, i);
83 free(track->events);
84 free(track->name);
85 free(track);
88 /// \brief Allocate a new style struct
89 /// \param track track
90 /// \return style id
91 int ass_alloc_style(ASS_Track *track)
93 int sid;
95 assert(track->n_styles <= track->max_styles);
97 if (track->n_styles == track->max_styles) {
98 track->max_styles += ASS_STYLES_ALLOC;
99 track->styles =
100 (ASS_Style *) realloc(track->styles,
101 sizeof(ASS_Style) *
102 track->max_styles);
105 sid = track->n_styles++;
106 memset(track->styles + sid, 0, sizeof(ASS_Style));
107 return sid;
110 /// \brief Allocate a new event struct
111 /// \param track track
112 /// \return event id
113 int ass_alloc_event(ASS_Track *track)
115 int eid;
117 assert(track->n_events <= track->max_events);
119 if (track->n_events == track->max_events) {
120 track->max_events += ASS_EVENTS_ALLOC;
121 track->events =
122 (ASS_Event *) realloc(track->events,
123 sizeof(ASS_Event) *
124 track->max_events);
127 eid = track->n_events++;
128 memset(track->events + eid, 0, sizeof(ASS_Event));
129 return eid;
132 void ass_free_event(ASS_Track *track, int eid)
134 ASS_Event *event = track->events + eid;
136 free(event->Name);
137 free(event->Effect);
138 free(event->Text);
139 free(event->render_priv);
142 void ass_free_style(ASS_Track *track, int sid)
144 ASS_Style *style = track->styles + sid;
146 free(style->Name);
147 free(style->FontName);
150 // ==============================================================================================
152 static void skip_spaces(char **str)
154 char *p = *str;
155 while ((*p == ' ') || (*p == '\t'))
156 ++p;
157 *str = p;
160 static void rskip_spaces(char **str, char *limit)
162 char *p = *str;
163 while ((p >= limit) && ((*p == ' ') || (*p == '\t')))
164 --p;
165 *str = p;
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;
183 style->Bold = 200;
184 style->ScaleX = 1.0;
185 style->ScaleY = 1.0;
186 style->Spacing = 0;
187 style->BorderStyle = 1;
188 style->Outline = 2;
189 style->Shadow = 3;
190 style->Alignment = 2;
191 style->MarginL = style->MarginR = style->MarginV = 20;
195 * \brief find style by name
196 * \param track track
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)
204 int i;
205 if (*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)
209 return i;
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)
220 uint32_t tmp;
221 (void) strtocolor(library, &p, &tmp, 0);
222 return tmp;
225 static long long string2timecode(ASS_Library *library, char *p)
227 unsigned h, m, s, ms;
228 long long tm;
229 int res = sscanf(p, "%1d:%2d:%2d.%2d", &h, &m, &s, &ms);
230 if (res < 4) {
231 ass_msg(library, MSGL_WARN, "Bad timestamp");
232 return 0;
234 tm = ((h * 60 + m) * 60 + s) * 1000 + ms * 10;
235 return tm;
239 * \brief converts numpad-style align to align.
241 static int numpad2align(int val)
243 int res, v;
244 v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment
245 if (v != 0)
246 v = 3 - v;
247 res = ((val - 1) % 3) + 1; // horizontal alignment
248 res += v * 4;
249 return res;
252 #define NEXT(str,token) \
253 token = next_token(&str); \
254 if (!token) break;
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)
289 char *p = *str;
290 char *start;
291 skip_spaces(&p);
292 if (*p == '\0') {
293 *str = p;
294 return 0;
296 start = p; // start of the token
297 for (; (*p != '\0') && (*p != ','); ++p) {
299 if (*p == '\0') {
300 *str = p; // eos found, str will point to '\0' at exit
301 } else {
302 *p = '\0';
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);
307 if (p < start)
308 p = start; // empty token
309 else
310 ++p; // the first space character, or '\0'
311 *p = '\0';
312 return start;
316 * \brief Parse the tail of Dialogue line
317 * \param track track
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)
325 char *token;
326 char *tname;
327 char *p = str;
328 int i;
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) {
343 NEXT(q, tname);
346 while (1) {
347 NEXT(q, tname);
348 if (strcasecmp(tname, "Text") == 0) {
349 char *last;
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')
354 *last = 0;
356 ass_msg(track->library, MSGL_DBG2, "Text = %s", event->Text);
357 event->Duration -= event->Start;
358 free(format);
359 return 0; // "Text" is always the last
361 NEXT(p, token);
363 ALIAS(End, Duration) // temporarily store end timecode in event->Duration
364 if (0) { // cool ;)
365 INTVAL(Layer)
366 STYLEVAL(Style)
367 STRVAL(Name)
368 STRVAL(Effect)
369 INTVAL(MarginL)
370 INTVAL(MarginR)
371 INTVAL(MarginV)
372 TIMEVAL(Start)
373 TIMEVAL(Duration)
376 free(format);
377 return 1;
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;
388 ASS_Style *target;
389 int sid;
390 char **list = track->library->style_overrides;
392 if (!list)
393 return;
395 for (fs = list; *fs; ++fs) {
396 eq = strrchr(*fs, '=');
397 if (!eq)
398 continue;
399 *eq = '\0';
400 token = eq + 1;
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, '.');
416 if (dt) {
417 *dt = '\0';
418 style = *fs;
419 tname = dt + 1;
420 } else {
421 style = NULL;
422 tname = *fs;
424 for (sid = 0; sid < track->n_styles; ++sid) {
425 if (style == NULL
426 || strcasecmp(track->styles[sid].Name, style) == 0) {
427 target = track->styles + sid;
428 if (0) {
429 STRVAL(FontName)
430 COLORVAL(PrimaryColour)
431 COLORVAL(SecondaryColour)
432 COLORVAL(OutlineColour)
433 COLORVAL(BackColour)
434 FPVAL(FontSize)
435 INTVAL(Bold)
436 INTVAL(Italic)
437 INTVAL(Underline)
438 INTVAL(StrikeOut)
439 FPVAL(Spacing)
440 INTVAL(Angle)
441 INTVAL(BorderStyle)
442 INTVAL(Alignment)
443 INTVAL(MarginL)
444 INTVAL(MarginR)
445 INTVAL(MarginV)
446 INTVAL(Encoding)
447 FPVAL(ScaleX)
448 FPVAL(ScaleY)
449 FPVAL(Outline)
450 FPVAL(Shadow)
454 *eq = '=';
455 if (dt)
456 *dt = '.';
461 * \brief Parse the Style line
462 * \param track track
463 * \param str string to parse, zero-terminated
464 * Allocates a new style struct.
466 static int process_style(ASS_Track *track, char *str)
469 char *token;
470 char *tname;
471 char *p = str;
472 char *format;
473 char *q; // format scanning pointer
474 int sid;
475 ASS_Style *style;
476 ASS_Style *target;
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 =
483 strdup
484 ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
485 "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline,"
486 "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
487 else
488 track->style_format =
489 strdup
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;
511 target = style;
513 // fill style with some default values
514 style->ScaleX = 100.;
515 style->ScaleY = 100.;
517 while (1) {
518 NEXT(q, tname);
519 NEXT(p, token);
521 if (0) { // cool ;)
522 STRVAL(Name)
523 if ((strcmp(target->Name, "Default") == 0)
524 || (strcmp(target->Name, "*Default") == 0))
525 track->default_style = sid;
526 STRVAL(FontName)
527 COLORVAL(PrimaryColour)
528 COLORVAL(SecondaryColour)
529 COLORVAL(OutlineColour) // TertiaryColor
530 COLORVAL(BackColour)
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;
535 FPVAL(FontSize)
536 INTVAL(Bold)
537 INTVAL(Italic)
538 INTVAL(Underline)
539 INTVAL(StrikeOut)
540 FPVAL(Spacing)
541 INTVAL(Angle)
542 INTVAL(BorderStyle)
543 INTVAL(Alignment)
544 if (track->track_type == TRACK_TYPE_ASS)
545 target->Alignment = numpad2align(target->Alignment);
546 INTVAL(MarginL)
547 INTVAL(MarginR)
548 INTVAL(MarginV)
549 INTVAL(Encoding)
550 FPVAL(ScaleX)
551 FPVAL(ScaleY)
552 FPVAL(Outline)
553 FPVAL(Shadow)
556 style->ScaleX /= 100.;
557 style->ScaleY /= 100.;
558 style->Bold = !!style->Bold;
559 style->Italic = !!style->Italic;
560 style->Underline = !!style->Underline;
561 if (!style->Name)
562 style->Name = strdup("Default");
563 if (!style->FontName)
564 style->FontName = strdup("Arial");
565 free(format);
566 return 0;
570 static int process_styles_line(ASS_Track *track, char *str)
572 if (!strncmp(str, "Format:", 7)) {
573 char *p = str + 7;
574 skip_spaces(&p);
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)) {
579 char *p = str + 6;
580 skip_spaces(&p);
581 process_style(track, p);
583 return 0;
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)) {
601 char *p = str + 9;
602 while (*p && isspace(*p)) p++;
603 track->Language = malloc(3);
604 strncpy(track->Language, p, 2);
605 track->Language[2] = 0;
607 return 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");
616 else
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)) {
626 char *p = str + 7;
627 skip_spaces(&p);
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
635 int eid;
636 ASS_Event *event;
638 str += 9;
639 skip_spaces(&str);
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);
649 } else {
650 ass_msg(track->library, MSGL_V, "Not understood: '%.30s'", str);
652 return 0;
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)
660 uint32_t value;
661 unsigned char bytes[3];
662 int i;
664 value =
665 ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 -
666 33);
667 bytes[2] = value & 0xff;
668 bytes[1] = (value & 0xff00) >> 8;
669 bytes[0] = (value & 0xff0000) >> 16;
671 for (i = 0; i < cnt; ++i)
672 *dst++ = bytes[i];
673 return dst;
676 static int decode_font(ASS_Track *track)
678 unsigned char *p;
679 unsigned char *q;
680 int i;
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;
688 if (size % 4 == 1) {
689 ass_msg(track->library, MSGL_ERR, "Bad encoded data size");
690 goto error_decode_font;
692 buf = malloc(size / 4 * 3 + 2);
693 q = buf;
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);
698 if (size % 4 == 2) {
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);
703 dsize = q - buf;
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);
711 error_decode_font:
712 free(buf);
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;
719 return 0;
722 static int process_fonts_line(ASS_Track *track, char *str)
724 int len;
726 if (!strncmp(str, "fontname:", 9)) {
727 char *p = str + 9;
728 skip_spaces(&p);
729 if (track->parser_priv->fontname) {
730 decode_font(track);
732 track->parser_priv->fontname = strdup(p);
733 ass_msg(track->library, MSGL_V, "Fontname: %s",
734 track->parser_priv->fontname);
735 return 0;
738 if (!track->parser_priv->fontname) {
739 ass_msg(track->library, MSGL_V, "Not understood: '%s'", str);
740 return 0;
743 len = strlen(str);
744 if (len > 80) {
745 ass_msg(track->library, MSGL_WARN, "Font line too long: %d, %s",
746 len, str);
747 return 0;
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,
757 str, len);
758 track->parser_priv->fontdata_used += len;
760 return 0;
764 * \brief Parse a header line
765 * \param track track
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;
782 } else {
783 switch (track->parser_priv->state) {
784 case PST_INFO:
785 process_info_line(track, str);
786 break;
787 case PST_STYLES:
788 process_styles_line(track, str);
789 break;
790 case PST_EVENTS:
791 process_events_line(track, str);
792 break;
793 case PST_FONTS:
794 process_fonts_line(track, str);
795 break;
796 default:
797 break;
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))
804 decode_font(track);
806 return 0;
809 static int process_text(ASS_Track *track, char *str)
811 char *p = str;
812 while (1) {
813 char *q;
814 while (1) {
815 if ((*p == '\r') || (*p == '\n'))
816 ++p;
817 else if (p[0] == '\xef' && p[1] == '\xbb' && p[2] == '\xbf')
818 p += 3; // U+FFFE (BOM)
819 else
820 break;
822 for (q = p; ((*q != '\0') && (*q != '\r') && (*q != '\n')); ++q) {
824 if (q == p)
825 break;
826 if (*q != '\0')
827 *(q++) = '\0';
828 process_line(track, p);
829 if (*q == '\0')
830 break;
831 p = q;
833 return 0;
837 * \brief Process a chunk of subtitle stream data.
838 * \param track track
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);
847 str[size] = '\0';
849 ass_msg(track->library, MSGL_V, "Event: %s", str);
850 process_text(track, str);
851 free(str);
855 * \brief Process CodecPrivate section of subtitle stream
856 * \param track track
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)
875 int i;
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)
878 return 1;
879 return 0;
883 * \brief Process a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary).
884 * \param track track
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)
893 char *str;
894 int eid;
895 char *p;
896 char *token;
897 ASS_Event *event;
899 if (!track->event_format) {
900 ass_msg(track->library, MSGL_WARN, "Event format header missing");
901 return;
904 str = malloc(size + 1);
905 memcpy(str, data, size);
906 str[size] = '\0';
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;
913 p = str;
915 do {
916 NEXT(p, token);
917 event->ReadOrder = atoi(token);
918 if (check_duplicate_event(track, event->ReadOrder))
919 break;
921 NEXT(p, token);
922 event->Layer = atoi(token);
924 process_event_tail(track, event, p, 3);
926 event->Start = timecode;
927 event->Duration = duration;
929 free(str);
930 return;
931 // dump_events(tid);
932 } while (0);
933 // some error
934 ass_free_event(track, eid);
935 track->n_events--;
936 free(str);
940 * \brief Flush buffered events.
941 * \param track track
943 void ass_flush_events(ASS_Track *track)
945 if (track->events) {
946 int eid;
947 for (eid = 0; eid < track->n_events; eid++)
948 ass_free_event(track, eid);
949 track->n_events = 0;
953 #ifdef CONFIG_ICONV
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,
961 char *codepage)
963 iconv_t icdsc;
964 char *tocp = "UTF-8";
965 char *outbuf;
966 assert(codepage);
969 const char *cp_tmp = codepage;
970 #ifdef CONFIG_ENCA
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) {
975 cp_tmp =
976 ass_guess_buffer_cp(library, (unsigned char *) data, size,
977 enca_lang, enca_fallback);
979 #endif
980 if ((icdsc = iconv_open(tocp, cp_tmp)) != (iconv_t) (-1)) {
981 ass_msg(library, MSGL_V, "Opened iconv descriptor");
982 } else
983 ass_msg(library, MSGL_ERR, "Error opening iconv descriptor");
987 size_t osize = size;
988 size_t ileft = size;
989 size_t oleft = size - 1;
990 char *ip;
991 char *op;
992 size_t rc;
993 int clear = 0;
995 outbuf = malloc(osize);
996 ip = data;
997 op = outbuf;
999 while (1) {
1000 if (ileft)
1001 rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
1002 else { // clear the conversion state and leave
1003 clear = 1;
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;
1011 osize += size;
1012 oleft += size;
1013 } else {
1014 ass_msg(library, MSGL_WARN, "Error recoding file");
1015 return NULL;
1017 } else if (clear)
1018 break;
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");
1029 return outbuf;
1031 #endif // ICONV
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)
1041 int res;
1042 long sz;
1043 long bytes_read;
1044 char *buf;
1046 FILE *fp = fopen(fname, "rb");
1047 if (!fp) {
1048 ass_msg(library, MSGL_WARN,
1049 "ass_read_file(%s): fopen failed", fname);
1050 return 0;
1052 res = fseek(fp, 0, SEEK_END);
1053 if (res == -1) {
1054 ass_msg(library, MSGL_WARN,
1055 "ass_read_file(%s): fseek failed", fname);
1056 fclose(fp);
1057 return 0;
1060 sz = ftell(fp);
1061 rewind(fp);
1063 ass_msg(library, MSGL_V, "File size: %ld", sz);
1065 buf = malloc(sz + 1);
1066 assert(buf);
1067 bytes_read = 0;
1068 do {
1069 res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
1070 if (res <= 0) {
1071 ass_msg(library, MSGL_INFO, "Read failed, %d: %s", errno,
1072 strerror(errno));
1073 fclose(fp);
1074 free(buf);
1075 return 0;
1077 bytes_read += res;
1078 } while (sz - bytes_read > 0);
1079 buf[sz] = '\0';
1080 fclose(fp);
1082 if (bufsize)
1083 *bufsize = sz;
1084 return buf;
1088 * \param buf pointer to subtitle text in utf-8
1090 static ASS_Track *parse_memory(ASS_Library *library, char *buf)
1092 ASS_Track *track;
1093 int i;
1095 track = ass_new_track(library);
1097 // process header
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)
1106 decode_font(track);
1108 if (track->track_type == TRACK_TYPE_UNKNOWN) {
1109 ass_free_track(track);
1110 return 0;
1113 ass_process_force_style(track);
1115 return 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)
1129 ASS_Track *track;
1130 int need_free = 0;
1132 if (!buf)
1133 return 0;
1135 #ifdef CONFIG_ICONV
1136 if (codepage) {
1137 buf = sub_recode(library, buf, bufsize, codepage);
1138 if (!buf)
1139 return 0;
1140 else
1141 need_free = 1;
1143 #endif
1144 track = parse_memory(library, buf);
1145 if (need_free)
1146 free(buf);
1147 if (!track)
1148 return 0;
1150 ass_msg(library, MSGL_INFO, "Added subtitle file: "
1151 "<memory> (%d styles, %d events)",
1152 track->n_styles, track->n_events);
1153 return track;
1156 static char *read_file_recode(ASS_Library *library, char *fname,
1157 char *codepage, size_t *size)
1159 char *buf;
1160 size_t bufsize;
1162 buf = read_file(library, fname, &bufsize);
1163 if (!buf)
1164 return 0;
1165 #ifdef CONFIG_ICONV
1166 if (codepage) {
1167 char *tmpbuf = sub_recode(library, buf, bufsize, codepage);
1168 free(buf);
1169 buf = tmpbuf;
1171 if (!buf)
1172 return 0;
1173 #endif
1174 *size = bufsize;
1175 return buf;
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,
1186 char *codepage)
1188 char *buf;
1189 ASS_Track *track;
1190 size_t bufsize;
1192 buf = read_file_recode(library, fname, codepage, &bufsize);
1193 if (!buf)
1194 return 0;
1195 track = parse_memory(library, buf);
1196 free(buf);
1197 if (!track)
1198 return 0;
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);
1206 return track;
1210 * \brief read styles from file into already initialized track
1212 int ass_read_styles(ASS_Track *track, char *fname, char *codepage)
1214 char *buf;
1215 ParserState old_state;
1216 size_t sz;
1218 buf = read_file(track->library, fname, &sz);
1219 if (!buf)
1220 return 1;
1221 #ifdef CONFIG_ICONV
1222 if (codepage) {
1223 char *tmpbuf;
1224 tmpbuf = sub_recode(track->library, buf, sz, codepage);
1225 free(buf);
1226 buf = tmpbuf;
1228 if (!buf)
1229 return 0;
1230 #endif
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;
1237 return 0;
1240 long long ass_step_sub(ASS_Track *track, long long now, int movement)
1242 int i;
1244 if (movement == 0)
1245 return 0;
1246 if (track->n_events == 0)
1247 return 0;
1249 if (movement < 0)
1250 for (i = 0;
1251 (i < track->n_events)
1253 ((long long) (track->events[i].Start +
1254 track->events[i].Duration) <= now); ++i) {
1255 } else
1256 for (i = track->n_events - 1;
1257 (i >= 0) && ((long long) (track->events[i].Start) > now);
1258 --i) {
1261 // -1 and n_events are ok
1262 assert(i >= -1);
1263 assert(i <= track->n_events);
1264 i += movement;
1265 if (i < 0)
1266 i = 0;
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));
1278 return track;
1282 * \brief Prepare track for rendering
1284 void ass_lazy_track_init(ASS_Library *lib, ASS_Track *track)
1286 if (track->PlayResX && track->PlayResY)
1287 return;
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;
1293 } else {
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);