]> granicus.if.org Git - libass/blob - libass/ass.c
Fix whitespace trimming
[libass] / libass / ass.c
1 /*
2  * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
3  *
4  * This file is part of libass.
5  *
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.
9  *
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.
17  */
18
19 #include "config.h"
20
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
32 #ifdef CONFIG_ICONV
33 #include <iconv.h>
34 #endif
35
36 #include "ass.h"
37 #include "ass_utils.h"
38 #include "ass_library.h"
39
40 #define ass_atof(STR) (ass_strtod((STR),NULL))
41
42 typedef enum {
43     PST_UNKNOWN = 0,
44     PST_INFO,
45     PST_STYLES,
46     PST_EVENTS,
47     PST_FONTS
48 } ParserState;
49
50 struct parser_priv {
51     ParserState state;
52     char *fontname;
53     char *fontdata;
54     int fontdata_size;
55     int fontdata_used;
56 };
57
58 #define ASS_STYLES_ALLOC 20
59 #define ASS_EVENTS_ALLOC 200
60
61 void ass_free_track(ASS_Track *track)
62 {
63     int i;
64
65     if (track->parser_priv) {
66         free(track->parser_priv->fontname);
67         free(track->parser_priv->fontdata);
68         free(track->parser_priv);
69     }
70     free(track->style_format);
71     free(track->event_format);
72     if (track->styles) {
73         for (i = 0; i < track->n_styles; ++i)
74             ass_free_style(track, i);
75     }
76     free(track->styles);
77     if (track->events) {
78         for (i = 0; i < track->n_events; ++i)
79             ass_free_event(track, i);
80     }
81     free(track->events);
82     free(track->name);
83     free(track);
84 }
85
86 /// \brief Allocate a new style struct
87 /// \param track track
88 /// \return style id
89 int ass_alloc_style(ASS_Track *track)
90 {
91     int sid;
92
93     assert(track->n_styles <= track->max_styles);
94
95     if (track->n_styles == track->max_styles) {
96         track->max_styles += ASS_STYLES_ALLOC;
97         track->styles =
98             (ASS_Style *) realloc(track->styles,
99                                   sizeof(ASS_Style) *
100                                   track->max_styles);
101     }
102
103     sid = track->n_styles++;
104     memset(track->styles + sid, 0, sizeof(ASS_Style));
105     return sid;
106 }
107
108 /// \brief Allocate a new event struct
109 /// \param track track
110 /// \return event id
111 int ass_alloc_event(ASS_Track *track)
112 {
113     int eid;
114
115     assert(track->n_events <= track->max_events);
116
117     if (track->n_events == track->max_events) {
118         track->max_events += ASS_EVENTS_ALLOC;
119         track->events =
120             (ASS_Event *) realloc(track->events,
121                                   sizeof(ASS_Event) *
122                                   track->max_events);
123     }
124
125     eid = track->n_events++;
126     memset(track->events + eid, 0, sizeof(ASS_Event));
127     return eid;
128 }
129
130 void ass_free_event(ASS_Track *track, int eid)
131 {
132     ASS_Event *event = track->events + eid;
133
134     free(event->Name);
135     free(event->Effect);
136     free(event->Text);
137     free(event->render_priv);
138 }
139
140 void ass_free_style(ASS_Track *track, int sid)
141 {
142     ASS_Style *style = track->styles + sid;
143
144     free(style->Name);
145     free(style->FontName);
146 }
147
148 // ==============================================================================================
149
150 static void skip_spaces(char **str)
151 {
152     char *p = *str;
153     while ((*p == ' ') || (*p == '\t'))
154         ++p;
155     *str = p;
156 }
157
158 static void rskip_spaces(char **str, char *limit)
159 {
160     char *p = *str;
161     while ((p >= limit) && ((*p == ' ') || (*p == '\t')))
162         --p;
163     *str = p;
164 }
165
166 /**
167  * \brief Set up default style
168  * \param style style to edit to defaults
169  * The parameters are mostly taken directly from VSFilter source for
170  * best compatibility.
171  */
172 static void set_default_style(ASS_Style *style)
173 {
174     style->Name             = strdup("Default");
175     style->FontName         = strdup("Arial");
176     style->FontSize         = 18;
177     style->PrimaryColour    = 0xffffff00;
178     style->SecondaryColour  = 0x00ffff00;
179     style->OutlineColour    = 0x00000000;
180     style->BackColour       = 0x00000080;
181     style->Bold             = 200;
182     style->ScaleX           = 1.0;
183     style->ScaleY           = 1.0;
184     style->Spacing          = 0;
185     style->BorderStyle      = 1;
186     style->Outline          = 2;
187     style->Shadow           = 3;
188     style->Alignment        = 2;
189     style->MarginL = style->MarginR = style->MarginV = 20;
190 }
191
192 /**
193  * \brief find style by name
194  * \param track track
195  * \param name style name
196  * \return index in track->styles
197  * Returnes 0 if no styles found => expects at least 1 style.
198  * Parsing code always adds "Default" style in the end.
199  */
200 static int lookup_style(ASS_Track *track, char *name)
201 {
202     int i;
203     if (*name == '*')
204         ++name;                 // FIXME: what does '*' really mean ?
205     for (i = track->n_styles - 1; i >= 0; --i) {
206         if (strcmp(track->styles[i].Name, name) == 0)
207             return i;
208     }
209     i = track->default_style;
210     ass_msg(track->library, MSGL_WARN,
211             "[%p]: Warning: no style named '%s' found, using '%s'",
212             track, name, track->styles[i].Name);
213     return i;                   // use the first style
214 }
215
216 static uint32_t string2color(ASS_Library *library, char *p)
217 {
218     uint32_t tmp;
219     (void) strtocolor(library, &p, &tmp, 0);
220     return tmp;
221 }
222
223 static long long string2timecode(ASS_Library *library, char *p)
224 {
225     unsigned h, m, s, ms;
226     long long tm;
227     int res = sscanf(p, "%1d:%2d:%2d.%2d", &h, &m, &s, &ms);
228     if (res < 4) {
229         ass_msg(library, MSGL_WARN, "Bad timestamp");
230         return 0;
231     }
232     tm = ((h * 60 + m) * 60 + s) * 1000 + ms * 10;
233     return tm;
234 }
235
236 /**
237  * \brief converts numpad-style align to align.
238  */
239 static int numpad2align(int val)
240 {
241     int res, v;
242     v = (val - 1) / 3;          // 0, 1 or 2 for vertical alignment
243     if (v != 0)
244         v = 3 - v;
245     res = ((val - 1) % 3) + 1;  // horizontal alignment
246     res += v * 4;
247     return res;
248 }
249
250 #define NEXT(str,token) \
251         token = next_token(&str); \
252         if (!token) break;
253
254 #define ANYVAL(name,func) \
255         } else if (strcasecmp(tname, #name) == 0) { \
256                 target->name = func(token); \
257                 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
258
259 #define STRVAL(name) \
260         } else if (strcasecmp(tname, #name) == 0) { \
261                 if (target->name != NULL) free(target->name); \
262                 target->name = strdup(token); \
263                 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
264
265 #define COLORVAL(name) \
266         } else if (strcasecmp(tname, #name) == 0) { \
267                 target->name = string2color(track->library, token); \
268                 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
269
270 #define INTVAL(name) ANYVAL(name,atoi)
271 #define FPVAL(name) ANYVAL(name,ass_atof)
272 #define TIMEVAL(name) \
273         } else if (strcasecmp(tname, #name) == 0) { \
274                 target->name = string2timecode(track->library, token); \
275                 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
276
277 #define STYLEVAL(name) \
278         } else if (strcasecmp(tname, #name) == 0) { \
279                 target->name = lookup_style(track, token); \
280                 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
281
282 #define ALIAS(alias,name) \
283         if (strcasecmp(tname, #alias) == 0) {tname = #name;}
284
285 static char *next_token(char **str)
286 {
287     char *p = *str;
288     char *start;
289     skip_spaces(&p);
290     if (*p == '\0') {
291         *str = p;
292         return 0;
293     }
294     start = p;                  // start of the token
295     for (; (*p != '\0') && (*p != ','); ++p) {
296     }
297     if (*p == '\0') {
298         *str = p;               // eos found, str will point to '\0' at exit
299     } else {
300         *p = '\0';
301         *str = p + 1;           // ',' found, str will point to the next char (beginning of the next token)
302     }
303     --p;                        // end of current token
304     rskip_spaces(&p, start);
305     if (p < start)
306         p = start;              // empty token
307     else
308         ++p;                    // the first space character, or '\0'
309     *p = '\0';
310     return start;
311 }
312
313 /**
314  * \brief Parse the tail of Dialogue line
315  * \param track track
316  * \param event parsed data goes here
317  * \param str string to parse, zero-terminated
318  * \param n_ignored number of format options to skip at the beginning
319 */
320 static int process_event_tail(ASS_Track *track, ASS_Event *event,
321                               char *str, int n_ignored)
322 {
323     char *token;
324     char *tname;
325     char *p = str;
326     int i;
327     ASS_Event *target = event;
328
329     char *format = strdup(track->event_format);
330     char *q = format;           // format scanning pointer
331
332     if (track->n_styles == 0) {
333         // add "Default" style to the end
334         // will be used if track does not contain a default style (or even does not contain styles at all)
335         int sid = ass_alloc_style(track);
336         set_default_style(&track->styles[sid]);
337         track->default_style = sid;
338     }
339
340     for (i = 0; i < n_ignored; ++i) {
341         NEXT(q, tname);
342     }
343
344     while (1) {
345         NEXT(q, tname);
346         if (strcasecmp(tname, "Text") == 0) {
347             char *last;
348             event->Text = strdup(p);
349             if (*event->Text != 0) {
350                 last = event->Text + strlen(event->Text) - 1;
351                 if (last >= event->Text && *last == '\r')
352                     *last = 0;
353             }
354             ass_msg(track->library, MSGL_DBG2, "Text = %s", event->Text);
355             event->Duration -= event->Start;
356             free(format);
357             return 0;           // "Text" is always the last
358         }
359         NEXT(p, token);
360
361         ALIAS(End, Duration)    // temporarily store end timecode in event->Duration
362         if (0) {            // cool ;)
363             INTVAL(Layer)
364             STYLEVAL(Style)
365             STRVAL(Name)
366             STRVAL(Effect)
367             INTVAL(MarginL)
368             INTVAL(MarginR)
369             INTVAL(MarginV)
370             TIMEVAL(Start)
371             TIMEVAL(Duration)
372         }
373     }
374     free(format);
375     return 1;
376 }
377
378 /**
379  * \brief Parse command line style overrides (--ass-force-style option)
380  * \param track track to apply overrides to
381  * The format for overrides is [StyleName.]Field=Value
382  */
383 void ass_process_force_style(ASS_Track *track)
384 {
385     char **fs, *eq, *dt, *style, *tname, *token;
386     ASS_Style *target;
387     int sid;
388     char **list = track->library->style_overrides;
389
390     if (!list)
391         return;
392
393     for (fs = list; *fs; ++fs) {
394         eq = strrchr(*fs, '=');
395         if (!eq)
396             continue;
397         *eq = '\0';
398         token = eq + 1;
399
400         if (!strcasecmp(*fs, "PlayResX"))
401             track->PlayResX = atoi(token);
402         else if (!strcasecmp(*fs, "PlayResY"))
403             track->PlayResY = atoi(token);
404         else if (!strcasecmp(*fs, "Timer"))
405             track->Timer = ass_atof(token);
406         else if (!strcasecmp(*fs, "WrapStyle"))
407             track->WrapStyle = atoi(token);
408         else if (!strcasecmp(*fs, "ScaledBorderAndShadow"))
409             track->ScaledBorderAndShadow = parse_bool(token);
410         else if (!strcasecmp(*fs, "Kerning"))
411             track->Kerning = parse_bool(token);
412
413         dt = strrchr(*fs, '.');
414         if (dt) {
415             *dt = '\0';
416             style = *fs;
417             tname = dt + 1;
418         } else {
419             style = NULL;
420             tname = *fs;
421         }
422         for (sid = 0; sid < track->n_styles; ++sid) {
423             if (style == NULL
424                 || strcasecmp(track->styles[sid].Name, style) == 0) {
425                 target = track->styles + sid;
426                 if (0) {
427                     STRVAL(FontName)
428                     COLORVAL(PrimaryColour)
429                     COLORVAL(SecondaryColour)
430                     COLORVAL(OutlineColour)
431                     COLORVAL(BackColour)
432                     FPVAL(FontSize)
433                     INTVAL(Bold)
434                     INTVAL(Italic)
435                     INTVAL(Underline)
436                     INTVAL(StrikeOut)
437                     FPVAL(Spacing)
438                     INTVAL(Angle)
439                     INTVAL(BorderStyle)
440                     INTVAL(Alignment)
441                     INTVAL(MarginL)
442                     INTVAL(MarginR)
443                     INTVAL(MarginV)
444                     INTVAL(Encoding)
445                     FPVAL(ScaleX)
446                     FPVAL(ScaleY)
447                     FPVAL(Outline)
448                     FPVAL(Shadow)
449                 }
450             }
451         }
452         *eq = '=';
453         if (dt)
454             *dt = '.';
455     }
456 }
457
458 /**
459  * \brief Parse the Style line
460  * \param track track
461  * \param str string to parse, zero-terminated
462  * Allocates a new style struct.
463 */
464 static int process_style(ASS_Track *track, char *str)
465 {
466
467     char *token;
468     char *tname;
469     char *p = str;
470     char *format;
471     char *q;                    // format scanning pointer
472     int sid;
473     ASS_Style *style;
474     ASS_Style *target;
475
476     if (!track->style_format) {
477         // no style format header
478         // probably an ancient script version
479         if (track->track_type == TRACK_TYPE_SSA)
480             track->style_format =
481                 strdup
482                 ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
483                  "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline,"
484                  "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
485         else
486             track->style_format =
487                 strdup
488                 ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
489                  "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut,"
490                  "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow,"
491                  "Alignment, MarginL, MarginR, MarginV, Encoding");
492     }
493
494     q = format = strdup(track->style_format);
495
496     // Add default style first
497     if (track->n_styles == 0) {
498         // will be used if track does not contain a default style (or even does not contain styles at all)
499         int sid = ass_alloc_style(track);
500         set_default_style(&track->styles[sid]);
501         track->default_style = sid;
502     }
503
504     ass_msg(track->library, MSGL_V, "[%p] Style: %s", track, str);
505
506     sid = ass_alloc_style(track);
507
508     style = track->styles + sid;
509     target = style;
510
511     // fill style with some default values
512     style->ScaleX = 100.;
513     style->ScaleY = 100.;
514
515     while (1) {
516         NEXT(q, tname);
517         NEXT(p, token);
518
519         if (0) {                // cool ;)
520             STRVAL(Name)
521             if ((strcmp(target->Name, "Default") == 0)
522                 || (strcmp(target->Name, "*Default") == 0))
523             track->default_style = sid;
524             STRVAL(FontName)
525             COLORVAL(PrimaryColour)
526             COLORVAL(SecondaryColour)
527             COLORVAL(OutlineColour) // TertiaryColor
528             COLORVAL(BackColour)
529             // SSA uses BackColour for both outline and shadow
530             // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
531             if (track->track_type == TRACK_TYPE_SSA)
532                 target->OutlineColour = target->BackColour;
533             FPVAL(FontSize)
534             INTVAL(Bold)
535             INTVAL(Italic)
536             INTVAL(Underline)
537             INTVAL(StrikeOut)
538             FPVAL(Spacing)
539             INTVAL(Angle)
540             INTVAL(BorderStyle)
541             INTVAL(Alignment)
542             if (track->track_type == TRACK_TYPE_ASS)
543                 target->Alignment = numpad2align(target->Alignment);
544             INTVAL(MarginL)
545             INTVAL(MarginR)
546             INTVAL(MarginV)
547             INTVAL(Encoding)
548             FPVAL(ScaleX)
549             FPVAL(ScaleY)
550             FPVAL(Outline)
551             FPVAL(Shadow)
552         }
553     }
554     style->ScaleX /= 100.;
555     style->ScaleY /= 100.;
556     style->Bold = !!style->Bold;
557     style->Italic = !!style->Italic;
558     style->Underline = !!style->Underline;
559     if (!style->Name)
560         style->Name = strdup("Default");
561     if (!style->FontName)
562         style->FontName = strdup("Arial");
563     free(format);
564     return 0;
565
566 }
567
568 static int process_styles_line(ASS_Track *track, char *str)
569 {
570     if (!strncmp(str, "Format:", 7)) {
571         char *p = str + 7;
572         skip_spaces(&p);
573         track->style_format = strdup(p);
574         ass_msg(track->library, MSGL_DBG2, "Style format: %s",
575                track->style_format);
576     } else if (!strncmp(str, "Style:", 6)) {
577         char *p = str + 6;
578         skip_spaces(&p);
579         process_style(track, p);
580     }
581     return 0;
582 }
583
584 static int process_info_line(ASS_Track *track, char *str)
585 {
586     if (!strncmp(str, "PlayResX:", 9)) {
587         track->PlayResX = atoi(str + 9);
588     } else if (!strncmp(str, "PlayResY:", 9)) {
589         track->PlayResY = atoi(str + 9);
590     } else if (!strncmp(str, "Timer:", 6)) {
591         track->Timer = ass_atof(str + 6);
592     } else if (!strncmp(str, "WrapStyle:", 10)) {
593         track->WrapStyle = atoi(str + 10);
594     } else if (!strncmp(str, "ScaledBorderAndShadow:", 22)) {
595         track->ScaledBorderAndShadow = parse_bool(str + 22);
596     } else if (!strncmp(str, "Kerning:", 8)) {
597         track->Kerning = parse_bool(str + 8);
598     }
599     return 0;
600 }
601
602 static void event_format_fallback(ASS_Track *track)
603 {
604     track->parser_priv->state = PST_EVENTS;
605     if (track->track_type == TRACK_TYPE_SSA)
606         track->event_format = strdup("Format: Marked, Start, End, Style, "
607             "Name, MarginL, MarginR, MarginV, Effect, Text");
608     else
609         track->event_format = strdup("Format: Layer, Start, End, Style, "
610             "Actor, MarginL, MarginR, MarginV, Effect, Text");
611     ass_msg(track->library, MSGL_V,
612             "No event format found, using fallback");
613 }
614
615 static int process_events_line(ASS_Track *track, char *str)
616 {
617     if (!strncmp(str, "Format:", 7)) {
618         char *p = str + 7;
619         skip_spaces(&p);
620         free(track->event_format);
621         track->event_format = strdup(p);
622         ass_msg(track->library, MSGL_DBG2, "Event format: %s", track->event_format);
623     } else if (!strncmp(str, "Dialogue:", 9)) {
624         // This should never be reached for embedded subtitles.
625         // They have slightly different format and are parsed in ass_process_chunk,
626         // called directly from demuxer
627         int eid;
628         ASS_Event *event;
629
630         str += 9;
631         skip_spaces(&str);
632
633         eid = ass_alloc_event(track);
634         event = track->events + eid;
635
636         // We can't parse events with event_format
637         if (!track->event_format)
638             event_format_fallback(track);
639
640         process_event_tail(track, event, str, 0);
641     } else {
642         ass_msg(track->library, MSGL_V, "Not understood: '%.30s'", str);
643     }
644     return 0;
645 }
646
647 // Copied from mkvtoolnix
648 static unsigned char *decode_chars(unsigned char c1, unsigned char c2,
649                                    unsigned char c3, unsigned char c4,
650                                    unsigned char *dst, int cnt)
651 {
652     uint32_t value;
653     unsigned char bytes[3];
654     int i;
655
656     value =
657         ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 -
658                                                                     33);
659     bytes[2] = value & 0xff;
660     bytes[1] = (value & 0xff00) >> 8;
661     bytes[0] = (value & 0xff0000) >> 16;
662
663     for (i = 0; i < cnt; ++i)
664         *dst++ = bytes[i];
665     return dst;
666 }
667
668 static int decode_font(ASS_Track *track)
669 {
670     unsigned char *p;
671     unsigned char *q;
672     int i;
673     int size;                   // original size
674     int dsize;                  // decoded size
675     unsigned char *buf = 0;
676
677     ass_msg(track->library, MSGL_V, "Font: %d bytes encoded data",
678             track->parser_priv->fontdata_used);
679     size = track->parser_priv->fontdata_used;
680     if (size % 4 == 1) {
681         ass_msg(track->library, MSGL_ERR, "Bad encoded data size");
682         goto error_decode_font;
683     }
684     buf = malloc(size / 4 * 3 + 2);
685     q = buf;
686     for (i = 0, p = (unsigned char *) track->parser_priv->fontdata;
687          i < size / 4; i++, p += 4) {
688         q = decode_chars(p[0], p[1], p[2], p[3], q, 3);
689     }
690     if (size % 4 == 2) {
691         q = decode_chars(p[0], p[1], 0, 0, q, 1);
692     } else if (size % 4 == 3) {
693         q = decode_chars(p[0], p[1], p[2], 0, q, 2);
694     }
695     dsize = q - buf;
696     assert(dsize <= size / 4 * 3 + 2);
697
698     if (track->library->extract_fonts) {
699         ass_add_font(track->library, track->parser_priv->fontname,
700                      (char *) buf, dsize);
701     }
702
703 error_decode_font:
704     free(buf);
705     free(track->parser_priv->fontname);
706     free(track->parser_priv->fontdata);
707     track->parser_priv->fontname = 0;
708     track->parser_priv->fontdata = 0;
709     track->parser_priv->fontdata_size = 0;
710     track->parser_priv->fontdata_used = 0;
711     return 0;
712 }
713
714 static int process_fonts_line(ASS_Track *track, char *str)
715 {
716     int len;
717
718     if (!strncmp(str, "fontname:", 9)) {
719         char *p = str + 9;
720         skip_spaces(&p);
721         if (track->parser_priv->fontname) {
722             decode_font(track);
723         }
724         track->parser_priv->fontname = strdup(p);
725         ass_msg(track->library, MSGL_V, "Fontname: %s",
726                track->parser_priv->fontname);
727         return 0;
728     }
729
730     if (!track->parser_priv->fontname) {
731         ass_msg(track->library, MSGL_V, "Not understood: '%s'", str);
732         return 0;
733     }
734
735     len = strlen(str);
736     if (len > 80) {
737         ass_msg(track->library, MSGL_WARN, "Font line too long: %d, %s",
738                 len, str);
739         return 0;
740     }
741     if (track->parser_priv->fontdata_used + len >
742         track->parser_priv->fontdata_size) {
743         track->parser_priv->fontdata_size += 100 * 1024;
744         track->parser_priv->fontdata =
745             realloc(track->parser_priv->fontdata,
746                     track->parser_priv->fontdata_size);
747     }
748     memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used,
749            str, len);
750     track->parser_priv->fontdata_used += len;
751
752     return 0;
753 }
754
755 /**
756  * \brief Parse a header line
757  * \param track track
758  * \param str string to parse, zero-terminated
759 */
760 static int process_line(ASS_Track *track, char *str)
761 {
762     if (!strncasecmp(str, "[Script Info]", 13)) {
763         track->parser_priv->state = PST_INFO;
764     } else if (!strncasecmp(str, "[V4 Styles]", 11)) {
765         track->parser_priv->state = PST_STYLES;
766         track->track_type = TRACK_TYPE_SSA;
767     } else if (!strncasecmp(str, "[V4+ Styles]", 12)) {
768         track->parser_priv->state = PST_STYLES;
769         track->track_type = TRACK_TYPE_ASS;
770     } else if (!strncasecmp(str, "[Events]", 8)) {
771         track->parser_priv->state = PST_EVENTS;
772     } else if (!strncasecmp(str, "[Fonts]", 7)) {
773         track->parser_priv->state = PST_FONTS;
774     } else {
775         switch (track->parser_priv->state) {
776         case PST_INFO:
777             process_info_line(track, str);
778             break;
779         case PST_STYLES:
780             process_styles_line(track, str);
781             break;
782         case PST_EVENTS:
783             process_events_line(track, str);
784             break;
785         case PST_FONTS:
786             process_fonts_line(track, str);
787             break;
788         default:
789             break;
790         }
791     }
792
793     // there is no explicit end-of-font marker in ssa/ass
794     if ((track->parser_priv->state != PST_FONTS)
795         && (track->parser_priv->fontname))
796         decode_font(track);
797
798     return 0;
799 }
800
801 static int process_text(ASS_Track *track, char *str)
802 {
803     char *p = str;
804     while (1) {
805         char *q;
806         while (1) {
807             if ((*p == '\r') || (*p == '\n'))
808                 ++p;
809             else if (p[0] == '\xef' && p[1] == '\xbb' && p[2] == '\xbf')
810                 p += 3;         // U+FFFE (BOM)
811             else
812                 break;
813         }
814         for (q = p; ((*q != '\0') && (*q != '\r') && (*q != '\n')); ++q) {
815         };
816         if (q == p)
817             break;
818         if (*q != '\0')
819             *(q++) = '\0';
820         process_line(track, p);
821         if (*q == '\0')
822             break;
823         p = q;
824     }
825     return 0;
826 }
827
828 /**
829  * \brief Process a chunk of subtitle stream data.
830  * \param track track
831  * \param data string to parse
832  * \param size length of data
833 */
834 void ass_process_data(ASS_Track *track, char *data, int size)
835 {
836     char *str = malloc(size + 1);
837
838     memcpy(str, data, size);
839     str[size] = '\0';
840
841     ass_msg(track->library, MSGL_V, "Event: %s", str);
842     process_text(track, str);
843     free(str);
844 }
845
846 /**
847  * \brief Process CodecPrivate section of subtitle stream
848  * \param track track
849  * \param data string to parse
850  * \param size length of data
851  CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections
852 */
853 void ass_process_codec_private(ASS_Track *track, char *data, int size)
854 {
855     ass_process_data(track, data, size);
856
857     // probably an mkv produced by ancient mkvtoolnix
858     // such files don't have [Events] and Format: headers
859     if (!track->event_format)
860         event_format_fallback(track);
861
862     ass_process_force_style(track);
863 }
864
865 static int check_duplicate_event(ASS_Track *track, int ReadOrder)
866 {
867     int i;
868     for (i = 0; i < track->n_events - 1; ++i)   // ignoring last event, it is the one we are comparing with
869         if (track->events[i].ReadOrder == ReadOrder)
870             return 1;
871     return 0;
872 }
873
874 /**
875  * \brief Process a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary).
876  * \param track track
877  * \param data string to parse
878  * \param size length of data
879  * \param timecode starting time of the event (milliseconds)
880  * \param duration duration of the event (milliseconds)
881 */
882 void ass_process_chunk(ASS_Track *track, char *data, int size,
883                        long long timecode, long long duration)
884 {
885     char *str;
886     int eid;
887     char *p;
888     char *token;
889     ASS_Event *event;
890
891     if (!track->event_format) {
892         ass_msg(track->library, MSGL_WARN, "Event format header missing");
893         return;
894     }
895
896     str = malloc(size + 1);
897     memcpy(str, data, size);
898     str[size] = '\0';
899     ass_msg(track->library, MSGL_V, "Event at %" PRId64 ", +%" PRId64 ": %s",
900            (int64_t) timecode, (int64_t) duration, str);
901
902     eid = ass_alloc_event(track);
903     event = track->events + eid;
904
905     p = str;
906
907     do {
908         NEXT(p, token);
909         event->ReadOrder = atoi(token);
910         if (check_duplicate_event(track, event->ReadOrder))
911             break;
912
913         NEXT(p, token);
914         event->Layer = atoi(token);
915
916         process_event_tail(track, event, p, 3);
917
918         event->Start = timecode;
919         event->Duration = duration;
920
921         free(str);
922         return;
923 //              dump_events(tid);
924     } while (0);
925     // some error
926     ass_free_event(track, eid);
927     track->n_events--;
928     free(str);
929 }
930
931 /**
932  * \brief Flush buffered events.
933  * \param track track
934 */
935 void ass_flush_events(ASS_Track *track)
936 {
937     if (track->events) {
938         int eid;
939         for (eid = 0; eid < track->n_events; eid++)
940             ass_free_event(track, eid);
941         track->n_events = 0;
942     }
943 }
944
945 #ifdef CONFIG_ICONV
946 /** \brief recode buffer to utf-8
947  * constraint: codepage != 0
948  * \param data pointer to text buffer
949  * \param size buffer size
950  * \return a pointer to recoded buffer, caller is responsible for freeing it
951 **/
952 static char *sub_recode(ASS_Library *library, char *data, size_t size,
953                         char *codepage)
954 {
955     iconv_t icdsc;
956     char *tocp = "UTF-8";
957     char *outbuf;
958     assert(codepage);
959
960     {
961         const char *cp_tmp = codepage;
962 #ifdef CONFIG_ENCA
963         char enca_lang[3], enca_fallback[100];
964         if (sscanf(codepage, "enca:%2s:%99s", enca_lang, enca_fallback) == 2
965             || sscanf(codepage, "ENCA:%2s:%99s", enca_lang,
966                       enca_fallback) == 2) {
967             cp_tmp =
968                 ass_guess_buffer_cp(library, (unsigned char *) data, size,
969                                     enca_lang, enca_fallback);
970         }
971 #endif
972         if ((icdsc = iconv_open(tocp, cp_tmp)) != (iconv_t) (-1)) {
973             ass_msg(library, MSGL_V, "Opened iconv descriptor");
974         } else
975             ass_msg(library, MSGL_ERR, "Error opening iconv descriptor");
976     }
977
978     {
979         size_t osize = size;
980         size_t ileft = size;
981         size_t oleft = size - 1;
982         char *ip;
983         char *op;
984         size_t rc;
985         int clear = 0;
986
987         outbuf = malloc(osize);
988         ip = data;
989         op = outbuf;
990
991         while (1) {
992             if (ileft)
993                 rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
994             else {              // clear the conversion state and leave
995                 clear = 1;
996                 rc = iconv(icdsc, NULL, NULL, &op, &oleft);
997             }
998             if (rc == (size_t) (-1)) {
999                 if (errno == E2BIG) {
1000                     size_t offset = op - outbuf;
1001                     outbuf = (char *) realloc(outbuf, osize + size);
1002                     op = outbuf + offset;
1003                     osize += size;
1004                     oleft += size;
1005                 } else {
1006                     ass_msg(library, MSGL_WARN, "Error recoding file");
1007                     return NULL;
1008                 }
1009             } else if (clear)
1010                 break;
1011         }
1012         outbuf[osize - oleft - 1] = 0;
1013     }
1014
1015     if (icdsc != (iconv_t) (-1)) {
1016         (void) iconv_close(icdsc);
1017         icdsc = (iconv_t) (-1);
1018         ass_msg(library, MSGL_V, "Closed iconv descriptor");
1019     }
1020
1021     return outbuf;
1022 }
1023 #endif                          // ICONV
1024
1025 /**
1026  * \brief read file contents into newly allocated buffer
1027  * \param fname file name
1028  * \param bufsize out: file size
1029  * \return pointer to file contents. Caller is responsible for its deallocation.
1030  */
1031 static char *read_file(ASS_Library *library, char *fname, size_t *bufsize)
1032 {
1033     int res;
1034     long sz;
1035     long bytes_read;
1036     char *buf;
1037
1038     FILE *fp = fopen(fname, "rb");
1039     if (!fp) {
1040         ass_msg(library, MSGL_WARN,
1041                 "ass_read_file(%s): fopen failed", fname);
1042         return 0;
1043     }
1044     res = fseek(fp, 0, SEEK_END);
1045     if (res == -1) {
1046         ass_msg(library, MSGL_WARN,
1047                 "ass_read_file(%s): fseek failed", fname);
1048         fclose(fp);
1049         return 0;
1050     }
1051
1052     sz = ftell(fp);
1053     rewind(fp);
1054
1055     ass_msg(library, MSGL_V, "File size: %ld", sz);
1056
1057     buf = malloc(sz + 1);
1058     assert(buf);
1059     bytes_read = 0;
1060     do {
1061         res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
1062         if (res <= 0) {
1063             ass_msg(library, MSGL_INFO, "Read failed, %d: %s", errno,
1064                     strerror(errno));
1065             fclose(fp);
1066             free(buf);
1067             return 0;
1068         }
1069         bytes_read += res;
1070     } while (sz - bytes_read > 0);
1071     buf[sz] = '\0';
1072     fclose(fp);
1073
1074     if (bufsize)
1075         *bufsize = sz;
1076     return buf;
1077 }
1078
1079 /*
1080  * \param buf pointer to subtitle text in utf-8
1081  */
1082 static ASS_Track *parse_memory(ASS_Library *library, char *buf)
1083 {
1084     ASS_Track *track;
1085     int i;
1086
1087     track = ass_new_track(library);
1088
1089     // process header
1090     process_text(track, buf);
1091
1092     // external SSA/ASS subs does not have ReadOrder field
1093     for (i = 0; i < track->n_events; ++i)
1094         track->events[i].ReadOrder = i;
1095
1096     // there is no explicit end-of-font marker in ssa/ass
1097     if (track->parser_priv->fontname)
1098         decode_font(track);
1099
1100     if (track->track_type == TRACK_TYPE_UNKNOWN) {
1101         ass_free_track(track);
1102         return 0;
1103     }
1104
1105     ass_process_force_style(track);
1106
1107     return track;
1108 }
1109
1110 /**
1111  * \brief Read subtitles from memory.
1112  * \param library libass library object
1113  * \param buf pointer to subtitles text
1114  * \param bufsize size of buffer
1115  * \param codepage recode buffer contents from given codepage
1116  * \return newly allocated track
1117 */
1118 ASS_Track *ass_read_memory(ASS_Library *library, char *buf,
1119                            size_t bufsize, char *codepage)
1120 {
1121     ASS_Track *track;
1122     int need_free = 0;
1123
1124     if (!buf)
1125         return 0;
1126
1127 #ifdef CONFIG_ICONV
1128     if (codepage) {
1129         buf = sub_recode(library, buf, bufsize, codepage);
1130         if (!buf)
1131             return 0;
1132         else
1133             need_free = 1;
1134     }
1135 #endif
1136     track = parse_memory(library, buf);
1137     if (need_free)
1138         free(buf);
1139     if (!track)
1140         return 0;
1141
1142     ass_msg(library, MSGL_INFO, "Added subtitle file: "
1143             "<memory> (%d styles, %d events)",
1144             track->n_styles, track->n_events);
1145     return track;
1146 }
1147
1148 static char *read_file_recode(ASS_Library *library, char *fname,
1149                               char *codepage, size_t *size)
1150 {
1151     char *buf;
1152     size_t bufsize;
1153
1154     buf = read_file(library, fname, &bufsize);
1155     if (!buf)
1156         return 0;
1157 #ifdef CONFIG_ICONV
1158     if (codepage) {
1159         char *tmpbuf = sub_recode(library, buf, bufsize, codepage);
1160         free(buf);
1161         buf = tmpbuf;
1162     }
1163     if (!buf)
1164         return 0;
1165 #endif
1166     *size = bufsize;
1167     return buf;
1168 }
1169
1170 /**
1171  * \brief Read subtitles from file.
1172  * \param library libass library object
1173  * \param fname file name
1174  * \param codepage recode buffer contents from given codepage
1175  * \return newly allocated track
1176 */
1177 ASS_Track *ass_read_file(ASS_Library *library, char *fname,
1178                          char *codepage)
1179 {
1180     char *buf;
1181     ASS_Track *track;
1182     size_t bufsize;
1183
1184     buf = read_file_recode(library, fname, codepage, &bufsize);
1185     if (!buf)
1186         return 0;
1187     track = parse_memory(library, buf);
1188     free(buf);
1189     if (!track)
1190         return 0;
1191
1192     track->name = strdup(fname);
1193
1194     ass_msg(library, MSGL_INFO,
1195             "Added subtitle file: '%s' (%d styles, %d events)",
1196             fname, track->n_styles, track->n_events);
1197
1198     return track;
1199 }
1200
1201 /**
1202  * \brief read styles from file into already initialized track
1203  */
1204 int ass_read_styles(ASS_Track *track, char *fname, char *codepage)
1205 {
1206     char *buf;
1207     ParserState old_state;
1208     size_t sz;
1209
1210     buf = read_file(track->library, fname, &sz);
1211     if (!buf)
1212         return 1;
1213 #ifdef CONFIG_ICONV
1214     if (codepage) {
1215         char *tmpbuf;
1216         tmpbuf = sub_recode(track->library, buf, sz, codepage);
1217         free(buf);
1218         buf = tmpbuf;
1219     }
1220     if (!buf)
1221         return 0;
1222 #endif
1223
1224     old_state = track->parser_priv->state;
1225     track->parser_priv->state = PST_STYLES;
1226     process_text(track, buf);
1227     track->parser_priv->state = old_state;
1228
1229     return 0;
1230 }
1231
1232 long long ass_step_sub(ASS_Track *track, long long now, int movement)
1233 {
1234     int i;
1235
1236     if (movement == 0)
1237         return 0;
1238     if (track->n_events == 0)
1239         return 0;
1240
1241     if (movement < 0)
1242         for (i = 0;
1243              (i < track->n_events)
1244              &&
1245              ((long long) (track->events[i].Start +
1246                            track->events[i].Duration) <= now); ++i) {
1247     } else
1248         for (i = track->n_events - 1;
1249              (i >= 0) && ((long long) (track->events[i].Start) > now);
1250              --i) {
1251         }
1252
1253     // -1 and n_events are ok
1254     assert(i >= -1);
1255     assert(i <= track->n_events);
1256     i += movement;
1257     if (i < 0)
1258         i = 0;
1259     if (i >= track->n_events)
1260         i = track->n_events - 1;
1261     return ((long long) track->events[i].Start) - now;
1262 }
1263
1264 ASS_Track *ass_new_track(ASS_Library *library)
1265 {
1266     ASS_Track *track = calloc(1, sizeof(ASS_Track));
1267     track->library = library;
1268     track->ScaledBorderAndShadow = 1;
1269     track->parser_priv = calloc(1, sizeof(ASS_ParserPriv));
1270     return track;
1271 }
1272
1273 /**
1274  * \brief Prepare track for rendering
1275  */
1276 void ass_lazy_track_init(ASS_Library *lib, ASS_Track *track)
1277 {
1278     if (track->PlayResX && track->PlayResY)
1279         return;
1280     if (!track->PlayResX && !track->PlayResY) {
1281         ass_msg(lib, MSGL_WARN,
1282                "Neither PlayResX nor PlayResY defined. Assuming 384x288");
1283         track->PlayResX = 384;
1284         track->PlayResY = 288;
1285     } else {
1286         if (!track->PlayResY && track->PlayResX == 1280) {
1287             track->PlayResY = 1024;
1288             ass_msg(lib, MSGL_WARN,
1289                    "PlayResY undefined, setting to %d", track->PlayResY);
1290         } else if (!track->PlayResY) {
1291             track->PlayResY = track->PlayResX * 3 / 4;
1292             ass_msg(lib, MSGL_WARN,
1293                    "PlayResY undefined, setting to %d", track->PlayResY);
1294         } else if (!track->PlayResX && track->PlayResY == 1024) {
1295             track->PlayResX = 1280;
1296             ass_msg(lib, MSGL_WARN,
1297                    "PlayResX undefined, setting to %d", track->PlayResX);
1298         } else if (!track->PlayResX) {
1299             track->PlayResX = track->PlayResY * 4 / 3;
1300             ass_msg(lib, MSGL_WARN,
1301                    "PlayResX undefined, setting to %d", track->PlayResX);
1302         }
1303     }
1304 }