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