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