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