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