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 #include "ass_compat.h"
21 
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <assert.h>
26 #include <errno.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <inttypes.h>
30 
31 #ifdef CONFIG_ICONV
32 #include <iconv.h>
33 #endif
34 
35 #include "ass.h"
36 #include "ass_utils.h"
37 #include "ass_library.h"
38 #include "ass_priv.h"
39 #include "ass_shaper.h"
40 #include "ass_string.h"
41 
42 #define ass_atof(STR) (ass_strtod((STR),NULL))
43 
44 static const char *const ass_style_format =
45         "Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, "
46         "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, "
47         "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, "
48         "Alignment, MarginL, MarginR, MarginV, Encoding";
49 static const char *const ass_event_format =
50         "Layer, Start, End, Style, Name, "
51         "MarginL, MarginR, MarginV, Effect, Text";
52 static const char *const ssa_style_format =
53         "Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, "
54         "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, "
55         "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding";
56 static const char *const ssa_event_format =
57         "Marked, Start, End, Style, Name, "
58         "MarginL, MarginR, MarginV, Effect, Text";
59 
60 #define ASS_STYLES_ALLOC 20
61 
ass_library_version(void)62 int ass_library_version(void)
63 {
64     return LIBASS_VERSION;
65 }
66 
ass_free_track(ASS_Track * track)67 void ass_free_track(ASS_Track *track)
68 {
69     int i;
70 
71     if (!track)
72         return;
73 
74     if (track->parser_priv) {
75         free(track->parser_priv->read_order_bitmap);
76         free(track->parser_priv->fontname);
77         free(track->parser_priv->fontdata);
78         free(track->parser_priv);
79     }
80     free(track->style_format);
81     free(track->event_format);
82     free(track->Language);
83     if (track->styles) {
84         for (i = 0; i < track->n_styles; ++i)
85             ass_free_style(track, i);
86     }
87     free(track->styles);
88     if (track->events) {
89         for (i = 0; i < track->n_events; ++i)
90             ass_free_event(track, i);
91     }
92     free(track->events);
93     free(track->name);
94     free(track);
95 }
96 
97 /// \brief Allocate a new style struct
98 /// \param track track
99 /// \return style id or negative value on failure
ass_alloc_style(ASS_Track * track)100 int ass_alloc_style(ASS_Track *track)
101 {
102     int sid;
103 
104     assert(track->n_styles <= track->max_styles);
105 
106     if (track->n_styles == track->max_styles) {
107         if (track->max_styles >= FFMIN(SIZE_MAX, INT_MAX) - ASS_STYLES_ALLOC)
108             return -1;
109         int new_max = track->max_styles + ASS_STYLES_ALLOC;
110         if (!ASS_REALLOC_ARRAY(track->styles, new_max))
111             return -1;
112         track->max_styles = new_max;
113     }
114 
115     sid = track->n_styles++;
116     memset(track->styles + sid, 0, sizeof(ASS_Style));
117     return sid;
118 }
119 
120 /// \brief Allocate a new event struct
121 /// \param track track
122 /// \return event id or negative value on failure
ass_alloc_event(ASS_Track * track)123 int ass_alloc_event(ASS_Track *track)
124 {
125     int eid;
126 
127     assert(track->n_events <= track->max_events);
128 
129     if (track->n_events == track->max_events) {
130         if (track->max_events >= FFMIN(SIZE_MAX, INT_MAX) / 2)
131             return -1;
132         int new_max = track->max_events * 2 + 1;
133         if (!ASS_REALLOC_ARRAY(track->events, new_max))
134             return -1;
135         track->max_events = new_max;
136     }
137 
138     eid = track->n_events++;
139     memset(track->events + eid, 0, sizeof(ASS_Event));
140     return eid;
141 }
142 
ass_free_event(ASS_Track * track,int eid)143 void ass_free_event(ASS_Track *track, int eid)
144 {
145     ASS_Event *event = track->events + eid;
146 
147     free(event->Name);
148     free(event->Effect);
149     free(event->Text);
150     free(event->render_priv);
151 }
152 
ass_free_style(ASS_Track * track,int sid)153 void ass_free_style(ASS_Track *track, int sid)
154 {
155     ASS_Style *style = track->styles + sid;
156 
157     free(style->Name);
158     free(style->FontName);
159 }
160 
resize_read_order_bitmap(ASS_Track * track,int max_id)161 static int resize_read_order_bitmap(ASS_Track *track, int max_id)
162 {
163     // Don't allow malicious files to OOM us easily. Also avoids int overflows.
164     if (max_id < 0 || max_id >= 10 * 1024 * 1024 * 8)
165         goto fail;
166     assert(track->parser_priv->read_order_bitmap || !track->parser_priv->read_order_elems);
167     if (max_id >= track->parser_priv->read_order_elems * 32) {
168         int oldelems = track->parser_priv->read_order_elems;
169         int elems = ((max_id + 31) / 32 + 1) * 2;
170         assert(elems >= oldelems);
171         track->parser_priv->read_order_elems = elems;
172         void *new_bitmap =
173             realloc(track->parser_priv->read_order_bitmap, elems * 4);
174         if (!new_bitmap)
175             goto fail;
176         track->parser_priv->read_order_bitmap = new_bitmap;
177         memset(track->parser_priv->read_order_bitmap + oldelems, 0,
178                (elems - oldelems) * 4);
179     }
180     return 0;
181 
182 fail:
183     free(track->parser_priv->read_order_bitmap);
184     track->parser_priv->read_order_bitmap = NULL;
185     track->parser_priv->read_order_elems = 0;
186     return -1;
187 }
188 
test_and_set_read_order_bit(ASS_Track * track,int id)189 static int test_and_set_read_order_bit(ASS_Track *track, int id)
190 {
191     if (resize_read_order_bitmap(track, id) < 0)
192         return -1;
193     int index = id / 32;
194     uint32_t bit = 1u << (id % 32);
195     if (track->parser_priv->read_order_bitmap[index] & bit)
196         return 1;
197     track->parser_priv->read_order_bitmap[index] |= bit;
198     return 0;
199 }
200 
201 // ==============================================================================================
202 
203 /**
204  * \brief Set up default style
205  * \param style style to edit to defaults
206  * The parameters are mostly taken directly from VSFilter source for
207  * best compatibility.
208  */
set_default_style(ASS_Style * style)209 static void set_default_style(ASS_Style *style)
210 {
211     style->Name             = strdup("Default");
212     style->FontName         = strdup("Arial");
213     style->FontSize         = 18;
214     style->PrimaryColour    = 0xffffff00;
215     style->SecondaryColour  = 0x00ffff00;
216     style->OutlineColour    = 0x00000000;
217     style->BackColour       = 0x00000080;
218     style->Bold             = 200;
219     style->ScaleX           = 1.0;
220     style->ScaleY           = 1.0;
221     style->Spacing          = 0;
222     style->BorderStyle      = 1;
223     style->Outline          = 2;
224     style->Shadow           = 3;
225     style->Alignment        = 2;
226     style->MarginL = style->MarginR = style->MarginV = 20;
227 }
228 
string2timecode(ASS_Library * library,char * p)229 static long long string2timecode(ASS_Library *library, char *p)
230 {
231     int h, m, s, ms;
232     long long tm;
233     int res = sscanf(p, "%d:%d:%d.%d", &h, &m, &s, &ms);
234     if (res < 4) {
235         ass_msg(library, MSGL_WARN, "Bad timestamp");
236         return 0;
237     }
238     tm = ((h * 60LL + m) * 60 + s) * 1000 + ms * 10LL;
239     return tm;
240 }
241 
242 #define NEXT(str,token) \
243     token = next_token(&str); \
244     if (!token) break;
245 
246 
247 #define ALIAS(alias,name) \
248         if (ass_strcasecmp(tname, #alias) == 0) {tname = #name;}
249 
250 /* One section started with PARSE_START and PARSE_END parses a single token
251  * (contained in the variable named token) for the header indicated by the
252  * variable tname. It does so by chaining a number of else-if statements, each
253  * of which checks if the tname variable indicates that this header should be
254  * parsed. The first parameter of the macro gives the name of the header.
255  *
256  * The string that is passed is in str. str is advanced to the next token if
257  * a header could be parsed. The parsed results are stored in the variable
258  * target, which has the type ASS_Style* or ASS_Event*.
259  */
260 #define PARSE_START if (0) {
261 #define PARSE_END   }
262 
263 #define ANYVAL(name,func) \
264     } else if (ass_strcasecmp(tname, #name) == 0) { \
265         target->name = func(token);
266 
267 #define STRVAL(name) \
268     } else if (ass_strcasecmp(tname, #name) == 0) { \
269         char *new_str = strdup(token); \
270         if (new_str) { \
271             free(target->name); \
272             target->name = new_str; \
273         }
274 
275 #define STARREDSTRVAL(name) \
276     } else if (ass_strcasecmp(tname, #name) == 0) { \
277         while (*token == '*') ++token; \
278         char *new_str = strdup(token); \
279         if (new_str) { \
280             free(target->name); \
281             target->name = new_str; \
282         }
283 
284 #define COLORVAL(name) ANYVAL(name,parse_color_header)
285 #define INTVAL(name) ANYVAL(name,atoi)
286 #define FPVAL(name) ANYVAL(name,ass_atof)
287 #define TIMEVAL(name) \
288     } else if (ass_strcasecmp(tname, #name) == 0) { \
289         target->name = string2timecode(track->library, token);
290 
291 #define STYLEVAL(name) \
292     } else if (ass_strcasecmp(tname, #name) == 0) { \
293         target->name = lookup_style(track, token);
294 
295 // skip spaces in str beforehand, or trim leading spaces afterwards
advance_token_pos(const char ** const str,const char ** const start,const char ** const end)296 static inline void advance_token_pos(const char **const str,
297                                      const char **const start,
298                                      const char **const end)
299 {
300     *start = *str;
301     *end   = *start;
302     while (**end != '\0' && **end != ',') ++*end;
303     *str = *end + (**end == ',');
304     rskip_spaces((char**)end, (char*)*start);
305 }
306 
next_token(char ** str)307 static char *next_token(char **str)
308 {
309     char *p;
310     char *start;
311     skip_spaces(str);
312     if (**str == '\0') {
313         return 0;
314     }
315 
316     advance_token_pos((const char**)str,
317                       (const char**)&start,
318                       (const char**)&p);
319 
320     *p = '\0';
321     return start;
322 }
323 
324 /**
325  * \brief Parse the tail of Dialogue line
326  * \param track track
327  * \param event parsed data goes here
328  * \param str string to parse, zero-terminated
329  * \param n_ignored number of format options to skip at the beginning
330 */
process_event_tail(ASS_Track * track,ASS_Event * event,char * str,int n_ignored)331 static int process_event_tail(ASS_Track *track, ASS_Event *event,
332                               char *str, int n_ignored)
333 {
334     char *token;
335     char *tname;
336     char *p = str;
337     int i;
338     ASS_Event *target = event;
339 
340     char *format = strdup(track->event_format);
341     if (!format)
342         return -1;
343     char *q = format;           // format scanning pointer
344 
345     for (i = 0; i < n_ignored; ++i) {
346         NEXT(q, tname);
347     }
348 
349     while (1) {
350         NEXT(q, tname);
351         if (ass_strcasecmp(tname, "Text") == 0) {
352             char *last;
353             event->Text = strdup(p);
354             if (event->Text && *event->Text != 0) {
355                 last = event->Text + strlen(event->Text) - 1;
356                 if (last >= event->Text && *last == '\r')
357                     *last = 0;
358             }
359             event->Duration -= event->Start;
360             free(format);
361             return event->Text ? 0 : -1;           // "Text" is always the last
362         }
363         NEXT(p, token);
364 
365         ALIAS(End, Duration)    // temporarily store end timecode in event->Duration
366         PARSE_START
367             INTVAL(Layer)
368             STYLEVAL(Style)
369             STRVAL(Name)
370             STRVAL(Effect)
371             INTVAL(MarginL)
372             INTVAL(MarginR)
373             INTVAL(MarginV)
374             TIMEVAL(Start)
375             TIMEVAL(Duration)
376         PARSE_END
377     }
378     free(format);
379     return 1;
380 }
381 
382 /**
383  * \brief Parse command line style overrides (--ass-force-style option)
384  * \param track track to apply overrides to
385  * The format for overrides is [StyleName.]Field=Value
386  */
ass_process_force_style(ASS_Track * track)387 void ass_process_force_style(ASS_Track *track)
388 {
389     char **fs, *eq, *dt, *style, *tname, *token;
390     ASS_Style *target;
391     int sid;
392     char **list = track->library->style_overrides;
393 
394     if (!list)
395         return;
396 
397     for (fs = list; *fs; ++fs) {
398         eq = strrchr(*fs, '=');
399         if (!eq)
400             continue;
401         *eq = '\0';
402         token = eq + 1;
403 
404         if (!ass_strcasecmp(*fs, "PlayResX"))
405             track->PlayResX = atoi(token);
406         else if (!ass_strcasecmp(*fs, "PlayResY"))
407             track->PlayResY = atoi(token);
408         else if (!ass_strcasecmp(*fs, "Timer"))
409             track->Timer = ass_atof(token);
410         else if (!ass_strcasecmp(*fs, "WrapStyle"))
411             track->WrapStyle = atoi(token);
412         else if (!ass_strcasecmp(*fs, "ScaledBorderAndShadow"))
413             track->ScaledBorderAndShadow = parse_bool(token);
414         else if (!ass_strcasecmp(*fs, "Kerning"))
415             track->Kerning = parse_bool(token);
416         else if (!ass_strcasecmp(*fs, "YCbCr Matrix"))
417             track->YCbCrMatrix = parse_ycbcr_matrix(token);
418 
419         dt = strrchr(*fs, '.');
420         if (dt) {
421             *dt = '\0';
422             style = *fs;
423             tname = dt + 1;
424         } else {
425             style = NULL;
426             tname = *fs;
427         }
428         for (sid = 0; sid < track->n_styles; ++sid) {
429             if (style == NULL
430                 || ass_strcasecmp(track->styles[sid].Name, style) == 0) {
431                 target = track->styles + sid;
432                 PARSE_START
433                     STRVAL(FontName)
434                     COLORVAL(PrimaryColour)
435                     COLORVAL(SecondaryColour)
436                     COLORVAL(OutlineColour)
437                     COLORVAL(BackColour)
438                     FPVAL(FontSize)
439                     INTVAL(Bold)
440                     INTVAL(Italic)
441                     INTVAL(Underline)
442                     INTVAL(StrikeOut)
443                     FPVAL(Spacing)
444                     FPVAL(Angle)
445                     INTVAL(BorderStyle)
446                     INTVAL(Alignment)
447                     INTVAL(Justify)
448                     INTVAL(MarginL)
449                     INTVAL(MarginR)
450                     INTVAL(MarginV)
451                     INTVAL(Encoding)
452                     FPVAL(ScaleX)
453                     FPVAL(ScaleY)
454                     FPVAL(Outline)
455                     FPVAL(Shadow)
456                     FPVAL(Blur)
457                 PARSE_END
458             }
459         }
460         *eq = '=';
461         if (dt)
462             *dt = '.';
463     }
464 }
465 
466 /**
467  * \brief Parse the Style line
468  * \param track track
469  * \param str string to parse, zero-terminated
470  * Allocates a new style struct.
471 */
process_style(ASS_Track * track,char * str)472 static int process_style(ASS_Track *track, char *str)
473 {
474 
475     char *token;
476     char *tname;
477     char *p = str;
478     char *format;
479     char *q;                    // format scanning pointer
480     int sid;
481     ASS_Style *style;
482     ASS_Style *target;
483 
484     if (!track->style_format) {
485         // no style format header
486         // probably an ancient script version
487         if (track->track_type == TRACK_TYPE_SSA)
488             track->style_format = strdup(ssa_style_format);
489         else
490             track->style_format = strdup(ass_style_format);
491         if (!track->style_format)
492             return -1;
493     }
494 
495     q = format = strdup(track->style_format);
496     if (!q)
497         return -1;
498 
499     ass_msg(track->library, MSGL_V, "[%p] Style: %s", track, str);
500 
501     sid = ass_alloc_style(track);
502     if (sid < 0) {
503         free(format);
504         return -1;
505     }
506 
507     style = track->styles + sid;
508     target = style;
509 
510     // fill style with some default values
511     style->ScaleX = 100.;
512     style->ScaleY = 100.;
513 
514     while (1) {
515         NEXT(q, tname);
516         NEXT(p, token);
517 
518         PARSE_START
519             STARREDSTRVAL(Name)
520             STRVAL(FontName)
521             COLORVAL(PrimaryColour)
522             COLORVAL(SecondaryColour)
523             COLORVAL(OutlineColour) // TertiaryColor
524             COLORVAL(BackColour)
525             // SSA uses BackColour for both outline and shadow
526             // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
527             if (track->track_type == TRACK_TYPE_SSA)
528                 target->OutlineColour = target->BackColour;
529             FPVAL(FontSize)
530             INTVAL(Bold)
531             INTVAL(Italic)
532             INTVAL(Underline)
533             INTVAL(StrikeOut)
534             FPVAL(Spacing)
535             FPVAL(Angle)
536             INTVAL(BorderStyle)
537             INTVAL(Alignment)
538             if (track->track_type == TRACK_TYPE_ASS)
539                 target->Alignment = numpad2align(target->Alignment);
540             // VSFilter compatibility
541             else if (target->Alignment == 8)
542                 target->Alignment = 3;
543             else if (target->Alignment == 4)
544                 target->Alignment = 11;
545             INTVAL(MarginL)
546             INTVAL(MarginR)
547             INTVAL(MarginV)
548             INTVAL(Encoding)
549             FPVAL(ScaleX)
550             FPVAL(ScaleY)
551             FPVAL(Outline)
552             FPVAL(Shadow)
553         PARSE_END
554     }
555     free(format);
556     style->ScaleX = FFMAX(style->ScaleX, 0.) / 100.;
557     style->ScaleY = FFMAX(style->ScaleY, 0.) / 100.;
558     style->Spacing = FFMAX(style->Spacing, 0.);
559     style->Outline = FFMAX(style->Outline, 0.);
560     style->Shadow = FFMAX(style->Shadow, 0.);
561     style->Bold = !!style->Bold;
562     style->Italic = !!style->Italic;
563     style->Underline = !!style->Underline;
564     style->StrikeOut = !!style->StrikeOut;
565     if (!style->Name)
566         style->Name = strdup("Default");
567     if (!style->FontName)
568         style->FontName = strdup("Arial");
569     if (!style->Name || !style->FontName) {
570         ass_free_style(track, sid);
571         track->n_styles--;
572         return -1;
573     }
574     if (strcmp(target->Name, "Default") == 0)
575         track->default_style = sid;
576     return 0;
577 
578 }
579 
format_line_compare(const char * fmt1,const char * fmt2)580 static bool format_line_compare(const char *fmt1, const char *fmt2)
581 {
582     while (true) {
583         const char *tk1_start, *tk2_start;
584         const char *tk1_end, *tk2_end;
585 
586         skip_spaces((char**)&fmt1);
587         skip_spaces((char**)&fmt2);
588         if (!*fmt1 || !*fmt2)
589             break;
590 
591         advance_token_pos(&fmt1, &tk1_start, &tk1_end);
592         advance_token_pos(&fmt2, &tk2_start, &tk2_end);
593 
594         if ((tk1_end-tk1_start) != (tk2_end-tk2_start))
595             return false;
596         if (ass_strncasecmp(tk1_start, tk2_start, tk1_end-tk1_start))
597             return false;
598     }
599     return *fmt1 == *fmt2;
600 }
601 
602 
603 /**
604  * \brief Set SBAS=1 if not set explicitly in case of custom format line
605  * \param track track
606  * \param fmt   format line of file
607  * \param std   standard format line
608  *
609  * As of writing libass is the only renderer accepting custom format lines.
610  * For years libass defaultet SBAS to yes instead of no.
611  * To avoid breaking released scripts with custom format lines,
612  * keep SBAS=1 default for custom format files.
613  */
custom_format_line_compatibility(ASS_Track * const track,const char * const fmt,const char * const std)614 static void custom_format_line_compatibility(ASS_Track *const track,
615                                              const char *const fmt,
616                                              const char *const std)
617 {
618     if (!(track->parser_priv->header_flags & SINFO_SCALEDBORDER)
619         && !format_line_compare(fmt, std)) {
620         ass_msg(track->library, MSGL_INFO,
621                "Track has custom format line(s). "
622                 "'ScaledBorderAndShadow' will default to 'yes'.");
623         track->ScaledBorderAndShadow = 1;
624     }
625 }
626 
process_styles_line(ASS_Track * track,char * str)627 static int process_styles_line(ASS_Track *track, char *str)
628 {
629     int ret = 0;
630     if (!strncmp(str, "Format:", 7)) {
631         char *p = str + 7;
632         skip_spaces(&p);
633         free(track->style_format);
634         track->style_format = strdup(p);
635         if (!track->style_format)
636             return -1;
637         ass_msg(track->library, MSGL_DBG2, "Style format: %s",
638                track->style_format);
639         if (track->track_type == TRACK_TYPE_ASS)
640             custom_format_line_compatibility(track, p, ass_style_format);
641         else
642             custom_format_line_compatibility(track, p, ssa_style_format);
643     } else if (!strncmp(str, "Style:", 6)) {
644         char *p = str + 6;
645         skip_spaces(&p);
646         ret = process_style(track, p);
647     }
648     return ret;
649 }
650 
check_duplicate_info_line(const ASS_Track * const track,const ScriptInfo si,const char * const name)651 static inline void check_duplicate_info_line(const ASS_Track *const track,
652                                              const ScriptInfo si,
653                                              const char *const name)
654 {
655     if (track->parser_priv->header_flags & si)
656         ass_msg(track->library, MSGL_WARN,
657             "Duplicate Script Info Header '%s'. Previous value overwritten!",
658             name);
659     else
660         track->parser_priv->header_flags |= si;
661 }
662 
process_info_line(ASS_Track * track,char * str)663 static int process_info_line(ASS_Track *track, char *str)
664 {
665     if (!strncmp(str, "PlayResX:", 9)) {
666         check_duplicate_info_line(track, SINFO_PLAYRESX, "PlayResX");
667         track->PlayResX = atoi(str + 9);
668     } else if (!strncmp(str, "PlayResY:", 9)) {
669         check_duplicate_info_line(track, SINFO_PLAYRESY, "PlayResY");
670         track->PlayResY = atoi(str + 9);
671     } else if (!strncmp(str, "Timer:", 6)) {
672         check_duplicate_info_line(track, SINFO_TIMER, "Timer");
673         track->Timer = ass_atof(str + 6);
674     } else if (!strncmp(str, "WrapStyle:", 10)) {
675         check_duplicate_info_line(track, SINFO_WRAPSTYLE, "WrapStyle");
676         track->WrapStyle = atoi(str + 10);
677     } else if (!strncmp(str, "ScaledBorderAndShadow:", 22)) {
678         check_duplicate_info_line(track, SINFO_SCALEDBORDER,
679                                     "ScaledBorderAndShadow");
680         track->ScaledBorderAndShadow = parse_bool(str + 22);
681     } else if (!strncmp(str, "Kerning:", 8)) {
682         check_duplicate_info_line(track, SINFO_KERNING, "Kerning");
683         track->Kerning = parse_bool(str + 8);
684     } else if (!strncmp(str, "YCbCr Matrix:", 13)) {
685         check_duplicate_info_line(track, SINFO_COLOURMATRIX, "YCbCr Matrix");
686         track->YCbCrMatrix = parse_ycbcr_matrix(str + 13);
687     } else if (!strncmp(str, "Language:", 9)) {
688         check_duplicate_info_line(track, SINFO_LANGUAGE, "Language");
689         char *p = str + 9;
690         while (*p && ass_isspace(*p)) p++;
691         free(track->Language);
692         track->Language = strndup(p, 2);
693     } else if (!strncmp(str, "; Script generated by ", 22)) {
694         if (!strncmp(str + 22,"FFmpeg/Lavc", 11))
695             track->parser_priv->header_flags |= GENBY_FFMPEG;
696     }
697     return 0;
698 }
699 
event_format_fallback(ASS_Track * track)700 static void event_format_fallback(ASS_Track *track)
701 {
702     track->parser_priv->state = PST_EVENTS;
703     if (track->track_type == TRACK_TYPE_SSA)
704         track->event_format = strdup(ssa_event_format);
705     else
706         track->event_format = strdup(ass_event_format);
707     ass_msg(track->library, MSGL_V,
708             "No event format found, using fallback");
709 }
710 
711 /**
712  * \brief Return if track is post-signature and pre-SBAS ffmpeg track
713  * \param track track
714 */
detect_legacy_conv_subs(ASS_Track * track)715 static bool detect_legacy_conv_subs(ASS_Track *track)
716 {
717     /*
718      * FFmpeg and libav convert srt subtitles to ass.
719      * In legacy versions, they did not set the 'ScaledBorderAndShadow' header,
720      * but expected it to default to yes (which libass did).
721      * To avoid breaking them, we try to detect these
722      * converted subs by common properties of ffmpeg/libav's converted subs.
723      * Since files with custom format lines (-2014.10.11) default to SBAS=1
724      * regardless of being ffmpeg generated or not, we are only concerned with
725      * post-signature and pre-SBAS ffmpeg-files (2014.10.11-2020.04.17).
726      * We want to avoid matching modified ffmpeg files though.
727      *
728      * Relevant ffmpeg commits are:
729      *  2c77c90684e24ef16f7e7c4462e011434cee6a98  2010.12.29
730      *    Initial conversion format.
731      *    Style "Format:" line is mix of SSA and ASS
732      *    Event "Format:" line
733      *     "Format: Layer, Start, End, Text\r\n"
734      *    Only Header in ScriptInfo is "ScriptType: v4.00+"
735      *  0e7782c08ec77739edb0b98ba5d896b45e98235f  2012.06.15
736      *    Adds 'Style' to Event "Format:" line
737      *  5039aadf68deb9ad6dd0737ea11259fe53d3727b  2014.06.18
738      *    Adds PlayerRes(X|Y) (384x288)
739      *    (moved below ScriptType: a few minutes later)
740      *  40b9f28641b696c6bb73ce49dc97c2ce2700cbdb  2014.10.11 14:31:23 +0200
741      *    Regular full ASS Event and Style "Format:" lines
742      *  52b0a0ecaa02e17f7e01bead8c3f215f1cfd48dc  2014.10.11 18:37:43 +0200 <==
743      *    Signature comment
744      *  56bc0a6736cdc7edab837ff8f304661fd16de0e4  2015.02.08
745      *    Allow custom PlayRes(X|Y)
746      *  a8ba2a2c1294a330a0e79ae7f0d3a203a7599166  2020.04.17
747      *    Set 'ScaledBorderAndShadow: yes'
748      *
749      * libav outputs initial ffmpeg format. (no longer maintained)
750      */
751 
752     // GENBY_FFMPEG and exact ffmpeg headers required
753     // Note: If there's SINFO_SCRIPTTYPE in the future this needs to be updated
754     if (track->parser_priv->header_flags
755             ^ (SINFO_PLAYRESX | SINFO_PLAYRESY | GENBY_FFMPEG))
756         return false;
757 
758     // Legacy ffmpeg only ever has one style
759     // Check 2 not 1 because libass also adds a def style
760     if (track->n_styles != 2
761         || strncmp(track->styles[1].Name, "Default", 7))
762         return false;
763 
764     return true;
765 }
766 
767 
process_events_line(ASS_Track * track,char * str)768 static int process_events_line(ASS_Track *track, char *str)
769 {
770     if (!strncmp(str, "Format:", 7)) {
771         char *p = str + 7;
772         skip_spaces(&p);
773         free(track->event_format);
774         track->event_format = strdup(p);
775         if (!track->event_format)
776             return -1;
777         ass_msg(track->library, MSGL_DBG2, "Event format: %s", track->event_format);
778         if (track->track_type == TRACK_TYPE_ASS)
779             custom_format_line_compatibility(track, p, ass_event_format);
780         else
781             custom_format_line_compatibility(track, p, ssa_event_format);
782 
783         // Guess if we are dealing with legacy ffmpeg subs and change accordingly
784         // If file has no event format it was probably not created by ffmpeg/libav
785         if (detect_legacy_conv_subs(track)) {
786             track->ScaledBorderAndShadow = 1;
787             ass_msg(track->library, MSGL_INFO,
788                     "Track treated as legacy ffmpeg sub.");
789         }
790     } else if (!strncmp(str, "Dialogue:", 9)) {
791         // This should never be reached for embedded subtitles.
792         // They have slightly different format and are parsed in ass_process_chunk,
793         // called directly from demuxer
794         int eid;
795         ASS_Event *event;
796 
797         // We can't parse events without event_format
798         if (!track->event_format) {
799             event_format_fallback(track);
800             if (!track->event_format)
801                 return -1;
802         }
803 
804         str += 9;
805         skip_spaces(&str);
806 
807         eid = ass_alloc_event(track);
808         if (eid < 0)
809             return -1;
810         event = track->events + eid;
811 
812         return process_event_tail(track, event, str, 0);
813     } else {
814         ass_msg(track->library, MSGL_V, "Not understood: '%.30s'", str);
815     }
816     return 0;
817 }
818 
decode_chars(const unsigned char * src,unsigned char * dst,size_t cnt_in)819 static unsigned char *decode_chars(const unsigned char *src,
820                                    unsigned char *dst, size_t cnt_in)
821 {
822     uint32_t value = 0;
823     for (size_t i = 0; i < cnt_in; i++)
824         value |= (uint32_t) ((src[i] - 33u) & 63) << 6 * (3 - i);
825 
826     *dst++ = value >> 16;
827     if (cnt_in >= 3)
828         *dst++ = value >> 8 & 0xff;
829     if (cnt_in >= 4)
830         *dst++ = value & 0xff;
831     return dst;
832 }
833 
reset_embedded_font_parsing(ASS_ParserPriv * parser_priv)834 static void reset_embedded_font_parsing(ASS_ParserPriv *parser_priv)
835 {
836     free(parser_priv->fontname);
837     free(parser_priv->fontdata);
838     parser_priv->fontname = NULL;
839     parser_priv->fontdata = NULL;
840     parser_priv->fontdata_size = 0;
841     parser_priv->fontdata_used = 0;
842 }
843 
decode_font(ASS_Track * track)844 static int decode_font(ASS_Track *track)
845 {
846     unsigned char *p;
847     unsigned char *q;
848     size_t i;
849     size_t size;                   // original size
850     size_t dsize;                  // decoded size
851     unsigned char *buf = 0;
852 
853     ass_msg(track->library, MSGL_V, "Font: %zu bytes encoded data",
854             track->parser_priv->fontdata_used);
855     size = track->parser_priv->fontdata_used;
856     if (size % 4 == 1) {
857         ass_msg(track->library, MSGL_ERR, "Bad encoded data size");
858         goto error_decode_font;
859     }
860     buf = malloc(size / 4 * 3 + FFMAX(size % 4, 1) - 1);
861     if (!buf)
862         goto error_decode_font;
863     q = buf;
864     for (i = 0, p = (unsigned char *) track->parser_priv->fontdata;
865          i < size / 4; i++, p += 4) {
866         q = decode_chars(p, q, 4);
867     }
868     if (size % 4 == 2) {
869         q = decode_chars(p, q, 2);
870     } else if (size % 4 == 3) {
871         q = decode_chars(p, q, 3);
872     }
873     dsize = q - buf;
874     assert(dsize == size / 4 * 3 + FFMAX(size % 4, 1) - 1);
875 
876     if (track->library->extract_fonts) {
877         ass_add_font(track->library, track->parser_priv->fontname,
878                      (char *) buf, dsize);
879     }
880 
881 error_decode_font:
882     free(buf);
883     reset_embedded_font_parsing(track->parser_priv);
884     return 0;
885 }
886 
process_fonts_line(ASS_Track * track,char * str)887 static int process_fonts_line(ASS_Track *track, char *str)
888 {
889     size_t len;
890 
891     if (!strncmp(str, "fontname:", 9)) {
892         char *p = str + 9;
893         skip_spaces(&p);
894         if (track->parser_priv->fontname) {
895             decode_font(track);
896         }
897         track->parser_priv->fontname = strdup(p);
898         if (!track->parser_priv->fontname)
899             return -1;
900         ass_msg(track->library, MSGL_V, "Fontname: %s",
901                 track->parser_priv->fontname);
902         return 0;
903     }
904 
905     if (!track->parser_priv->fontname) {
906         ass_msg(track->library, MSGL_V, "Not understood: '%s'", str);
907         return 1;
908     }
909 
910     len = strlen(str);
911     if (track->parser_priv->fontdata_used >=
912         SIZE_MAX - FFMAX(len, 100 * 1024)) {
913         goto mem_fail;
914     } else if (track->parser_priv->fontdata_used + len >
915                track->parser_priv->fontdata_size) {
916         size_t new_size =
917                 track->parser_priv->fontdata_size + FFMAX(len, 100 * 1024);
918         if (!ASS_REALLOC_ARRAY(track->parser_priv->fontdata, new_size))
919             goto mem_fail;
920         track->parser_priv->fontdata_size = new_size;
921     }
922     memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used,
923            str, len);
924     track->parser_priv->fontdata_used += len;
925 
926     return 0;
927 
928 mem_fail:
929     reset_embedded_font_parsing(track->parser_priv);
930     return -1;
931 }
932 
933 /**
934  * \brief Parse a header line
935  * \param track track
936  * \param str string to parse, zero-terminated
937 */
process_line(ASS_Track * track,char * str)938 static int process_line(ASS_Track *track, char *str)
939 {
940     skip_spaces(&str);
941     if (!ass_strncasecmp(str, "[Script Info]", 13)) {
942         track->parser_priv->state = PST_INFO;
943     } else if (!ass_strncasecmp(str, "[V4 Styles]", 11)) {
944         track->parser_priv->state = PST_STYLES;
945         track->track_type = TRACK_TYPE_SSA;
946     } else if (!ass_strncasecmp(str, "[V4+ Styles]", 12)) {
947         track->parser_priv->state = PST_STYLES;
948         track->track_type = TRACK_TYPE_ASS;
949     } else if (!ass_strncasecmp(str, "[Events]", 8)) {
950         track->parser_priv->state = PST_EVENTS;
951     } else if (!ass_strncasecmp(str, "[Fonts]", 7)) {
952         track->parser_priv->state = PST_FONTS;
953     } else {
954         switch (track->parser_priv->state) {
955         case PST_INFO:
956             process_info_line(track, str);
957             break;
958         case PST_STYLES:
959             process_styles_line(track, str);
960             break;
961         case PST_EVENTS:
962             process_events_line(track, str);
963             break;
964         case PST_FONTS:
965             process_fonts_line(track, str);
966             break;
967         default:
968             break;
969         }
970     }
971     return 0;
972 }
973 
process_text(ASS_Track * track,char * str)974 static int process_text(ASS_Track *track, char *str)
975 {
976     char *p = str;
977     while (1) {
978         char *q;
979         while (1) {
980             if ((*p == '\r') || (*p == '\n'))
981                 ++p;
982             else if (p[0] == '\xef' && p[1] == '\xbb' && p[2] == '\xbf')
983                 p += 3;         // U+FFFE (BOM)
984             else
985                 break;
986         }
987         for (q = p; ((*q != '\0') && (*q != '\r') && (*q != '\n')); ++q) {
988         };
989         if (q == p)
990             break;
991         if (*q != '\0')
992             *(q++) = '\0';
993         process_line(track, p);
994         if (*q == '\0')
995             break;
996         p = q;
997     }
998     // there is no explicit end-of-font marker in ssa/ass
999     if (track->parser_priv->fontname)
1000         decode_font(track);
1001     return 0;
1002 }
1003 
1004 /**
1005  * \brief Process a chunk of subtitle stream data.
1006  * \param track track
1007  * \param data string to parse
1008  * \param size length of data
1009 */
ass_process_data(ASS_Track * track,char * data,int size)1010 void ass_process_data(ASS_Track *track, char *data, int size)
1011 {
1012     char *str = malloc(size + 1);
1013     if (!str)
1014         return;
1015 
1016     memcpy(str, data, size);
1017     str[size] = '\0';
1018 
1019     ass_msg(track->library, MSGL_V, "Event: %s", str);
1020     process_text(track, str);
1021     free(str);
1022 }
1023 
1024 /**
1025  * \brief Process CodecPrivate section of subtitle stream
1026  * \param track track
1027  * \param data string to parse
1028  * \param size length of data
1029  CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections
1030 */
ass_process_codec_private(ASS_Track * track,char * data,int size)1031 void ass_process_codec_private(ASS_Track *track, char *data, int size)
1032 {
1033     ass_process_data(track, data, size);
1034 
1035     // probably an mkv produced by ancient mkvtoolnix
1036     // such files don't have [Events] and Format: headers
1037     if (!track->event_format)
1038         event_format_fallback(track);
1039 
1040     ass_process_force_style(track);
1041 }
1042 
check_duplicate_event(ASS_Track * track,int ReadOrder)1043 static int check_duplicate_event(ASS_Track *track, int ReadOrder)
1044 {
1045     if (track->parser_priv->read_order_bitmap)
1046         return test_and_set_read_order_bit(track, ReadOrder) > 0;
1047     // ignoring last event, it is the one we are comparing with
1048     for (int i = 0; i < track->n_events - 1; i++)
1049         if (track->events[i].ReadOrder == ReadOrder)
1050             return 1;
1051     return 0;
1052 }
1053 
ass_set_check_readorder(ASS_Track * track,int check_readorder)1054 void ass_set_check_readorder(ASS_Track *track, int check_readorder)
1055 {
1056     track->parser_priv->check_readorder = check_readorder == 1;
1057 }
1058 
1059 /**
1060  * \brief Process a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary).
1061  * \param track track
1062  * \param data string to parse
1063  * \param size length of data
1064  * \param timecode starting time of the event (milliseconds)
1065  * \param duration duration of the event (milliseconds)
1066 */
ass_process_chunk(ASS_Track * track,char * data,int size,long long timecode,long long duration)1067 void ass_process_chunk(ASS_Track *track, char *data, int size,
1068                        long long timecode, long long duration)
1069 {
1070     char *str = NULL;
1071     int eid;
1072     char *p;
1073     char *token;
1074     ASS_Event *event;
1075     int check_readorder = track->parser_priv->check_readorder;
1076 
1077     if (check_readorder && !track->parser_priv->read_order_bitmap) {
1078         for (int i = 0; i < track->n_events; i++) {
1079             if (test_and_set_read_order_bit(track, track->events[i].ReadOrder) < 0)
1080                 break;
1081         }
1082     }
1083 
1084     if (!track->event_format) {
1085         ass_msg(track->library, MSGL_WARN, "Event format header missing");
1086         goto cleanup;
1087     }
1088 
1089     str = malloc(size + 1);
1090     if (!str)
1091         goto cleanup;
1092     memcpy(str, data, size);
1093     str[size] = '\0';
1094     ass_msg(track->library, MSGL_V, "Event at %" PRId64 ", +%" PRId64 ": %s",
1095            (int64_t) timecode, (int64_t) duration, str);
1096 
1097     eid = ass_alloc_event(track);
1098     if (eid < 0)
1099         goto cleanup;
1100     event = track->events + eid;
1101 
1102     p = str;
1103 
1104     do {
1105         NEXT(p, token);
1106         event->ReadOrder = atoi(token);
1107         if (check_readorder && check_duplicate_event(track, event->ReadOrder))
1108             break;
1109 
1110         NEXT(p, token);
1111         event->Layer = atoi(token);
1112 
1113         process_event_tail(track, event, p, 3);
1114 
1115         event->Start = timecode;
1116         event->Duration = duration;
1117 
1118         goto cleanup;
1119 //              dump_events(tid);
1120     } while (0);
1121     // some error
1122     ass_free_event(track, eid);
1123     track->n_events--;
1124 
1125 cleanup:
1126     free(str);
1127 }
1128 
1129 /**
1130  * \brief Flush buffered events.
1131  * \param track track
1132 */
ass_flush_events(ASS_Track * track)1133 void ass_flush_events(ASS_Track *track)
1134 {
1135     if (track->events) {
1136         int eid;
1137         for (eid = 0; eid < track->n_events; eid++)
1138             ass_free_event(track, eid);
1139         track->n_events = 0;
1140     }
1141     free(track->parser_priv->read_order_bitmap);
1142     track->parser_priv->read_order_bitmap = NULL;
1143     track->parser_priv->read_order_elems = 0;
1144 }
1145 
1146 #ifdef CONFIG_ICONV
1147 /** \brief recode buffer to utf-8
1148  * constraint: codepage != 0
1149  * \param data pointer to text buffer
1150  * \param size buffer size
1151  * \return a pointer to recoded buffer, caller is responsible for freeing it
1152 **/
sub_recode(ASS_Library * library,char * data,size_t size,char * codepage)1153 static char *sub_recode(ASS_Library *library, char *data, size_t size,
1154                         char *codepage)
1155 {
1156     iconv_t icdsc;
1157     char *tocp = "UTF-8";
1158     char *outbuf;
1159     assert(codepage);
1160 
1161     if ((icdsc = iconv_open(tocp, codepage)) != (iconv_t) (-1)) {
1162         ass_msg(library, MSGL_V, "Opened iconv descriptor");
1163     } else {
1164         ass_msg(library, MSGL_ERR, "Error opening iconv descriptor");
1165         return NULL;
1166     }
1167 
1168     {
1169         size_t osize = size;
1170         size_t ileft = size;
1171         size_t oleft = size - 1;
1172         char *ip;
1173         char *op;
1174         size_t rc;
1175         int clear = 0;
1176 
1177         outbuf = malloc(osize);
1178         if (!outbuf)
1179             goto out;
1180         ip = data;
1181         op = outbuf;
1182 
1183         while (1) {
1184             if (ileft)
1185                 rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
1186             else {              // clear the conversion state and leave
1187                 clear = 1;
1188                 rc = iconv(icdsc, NULL, NULL, &op, &oleft);
1189             }
1190             if (rc == (size_t) (-1)) {
1191                 if (errno == E2BIG) {
1192                     size_t offset = op - outbuf;
1193                     char *nbuf = realloc(outbuf, osize + size);
1194                     if (!nbuf) {
1195                         free(outbuf);
1196                         outbuf = 0;
1197                         goto out;
1198                     }
1199                     outbuf = nbuf;
1200                     op = outbuf + offset;
1201                     osize += size;
1202                     oleft += size;
1203                 } else {
1204                     ass_msg(library, MSGL_WARN, "Error recoding file");
1205                     free(outbuf);
1206                     outbuf = NULL;
1207                     goto out;
1208                 }
1209             } else if (clear)
1210                 break;
1211         }
1212         outbuf[osize - oleft - 1] = 0;
1213     }
1214 
1215 out:
1216     if (icdsc != (iconv_t) (-1)) {
1217         (void) iconv_close(icdsc);
1218         ass_msg(library, MSGL_V, "Closed iconv descriptor");
1219     }
1220 
1221     return outbuf;
1222 }
1223 #endif                          // ICONV
1224 
1225 /**
1226  * \brief read file contents into newly allocated buffer
1227  * \param fname file name
1228  * \param bufsize out: file size
1229  * \return pointer to file contents. Caller is responsible for its deallocation.
1230  */
read_file(ASS_Library * library,char * fname,size_t * bufsize)1231 char *read_file(ASS_Library *library, char *fname, size_t *bufsize)
1232 {
1233     int res;
1234     long sz;
1235     long bytes_read;
1236     char *buf;
1237 
1238     FILE *fp = fopen(fname, "rb");
1239     if (!fp) {
1240         ass_msg(library, MSGL_WARN,
1241                 "ass_read_file(%s): fopen failed", fname);
1242         return 0;
1243     }
1244     res = fseek(fp, 0, SEEK_END);
1245     if (res == -1) {
1246         ass_msg(library, MSGL_WARN,
1247                 "ass_read_file(%s): fseek failed", fname);
1248         fclose(fp);
1249         return 0;
1250     }
1251 
1252     sz = ftell(fp);
1253     rewind(fp);
1254 
1255     ass_msg(library, MSGL_V, "File size: %ld", sz);
1256 
1257     buf = sz < SIZE_MAX ? malloc(sz + 1) : NULL;
1258     if (!buf) {
1259         fclose(fp);
1260         return NULL;
1261     }
1262     assert(buf);
1263     bytes_read = 0;
1264     do {
1265         res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
1266         if (res <= 0) {
1267             ass_msg(library, MSGL_INFO, "Read failed, %d: %s", errno,
1268                     strerror(errno));
1269             fclose(fp);
1270             free(buf);
1271             return 0;
1272         }
1273         bytes_read += res;
1274     } while (sz - bytes_read > 0);
1275     buf[sz] = '\0';
1276     fclose(fp);
1277 
1278     if (bufsize)
1279         *bufsize = sz;
1280     return buf;
1281 }
1282 
1283 /*
1284  * \param buf pointer to subtitle text in utf-8
1285  */
parse_memory(ASS_Library * library,char * buf)1286 static ASS_Track *parse_memory(ASS_Library *library, char *buf)
1287 {
1288     ASS_Track *track;
1289     int i;
1290 
1291     track = ass_new_track(library);
1292     if (!track)
1293         return NULL;
1294 
1295     // process header
1296     process_text(track, buf);
1297 
1298     // external SSA/ASS subs does not have ReadOrder field
1299     for (i = 0; i < track->n_events; ++i)
1300         track->events[i].ReadOrder = i;
1301 
1302     if (track->track_type == TRACK_TYPE_UNKNOWN) {
1303         ass_free_track(track);
1304         return 0;
1305     }
1306 
1307     ass_process_force_style(track);
1308 
1309     return track;
1310 }
1311 
1312 /**
1313  * \brief Read subtitles from memory.
1314  * \param library libass library object
1315  * \param buf pointer to subtitles text
1316  * \param bufsize size of buffer
1317  * \param codepage recode buffer contents from given codepage
1318  * \return newly allocated track
1319 */
ass_read_memory(ASS_Library * library,char * buf,size_t bufsize,char * codepage)1320 ASS_Track *ass_read_memory(ASS_Library *library, char *buf,
1321                            size_t bufsize, char *codepage)
1322 {
1323     ASS_Track *track;
1324     int copied = 0;
1325 
1326     if (!buf)
1327         return 0;
1328 
1329 #ifdef CONFIG_ICONV
1330     if (codepage) {
1331         buf = sub_recode(library, buf, bufsize, codepage);
1332         if (!buf)
1333             return 0;
1334         else
1335             copied = 1;
1336     }
1337 #endif
1338     if (!copied) {
1339         char *newbuf = malloc(bufsize + 1);
1340         if (!newbuf)
1341             return 0;
1342         memcpy(newbuf, buf, bufsize);
1343         newbuf[bufsize] = '\0';
1344         buf = newbuf;
1345     }
1346     track = parse_memory(library, buf);
1347     free(buf);
1348     if (!track)
1349         return 0;
1350 
1351     ass_msg(library, MSGL_INFO, "Added subtitle file: "
1352             "<memory> (%d styles, %d events)",
1353             track->n_styles, track->n_events);
1354     return track;
1355 }
1356 
read_file_recode(ASS_Library * library,char * fname,char * codepage,size_t * size)1357 static char *read_file_recode(ASS_Library *library, char *fname,
1358                               char *codepage, size_t *size)
1359 {
1360     char *buf;
1361     size_t bufsize;
1362 
1363     buf = read_file(library, fname, &bufsize);
1364     if (!buf)
1365         return 0;
1366 #ifdef CONFIG_ICONV
1367     if (codepage) {
1368         char *tmpbuf = sub_recode(library, buf, bufsize, codepage);
1369         free(buf);
1370         buf = tmpbuf;
1371     }
1372     if (!buf)
1373         return 0;
1374 #endif
1375     *size = bufsize;
1376     return buf;
1377 }
1378 
1379 /**
1380  * \brief Read subtitles from file.
1381  * \param library libass library object
1382  * \param fname file name
1383  * \param codepage recode buffer contents from given codepage
1384  * \return newly allocated track
1385 */
ass_read_file(ASS_Library * library,char * fname,char * codepage)1386 ASS_Track *ass_read_file(ASS_Library *library, char *fname,
1387                          char *codepage)
1388 {
1389     char *buf;
1390     ASS_Track *track;
1391     size_t bufsize;
1392 
1393     buf = read_file_recode(library, fname, codepage, &bufsize);
1394     if (!buf)
1395         return 0;
1396     track = parse_memory(library, buf);
1397     free(buf);
1398     if (!track)
1399         return 0;
1400 
1401     track->name = strdup(fname);
1402 
1403     ass_msg(library, MSGL_INFO,
1404             "Added subtitle file: '%s' (%d styles, %d events)",
1405             fname, track->n_styles, track->n_events);
1406 
1407     return track;
1408 }
1409 
1410 /**
1411  * \brief read styles from file into already initialized track
1412  */
ass_read_styles(ASS_Track * track,char * fname,char * codepage)1413 int ass_read_styles(ASS_Track *track, char *fname, char *codepage)
1414 {
1415     char *buf;
1416     ParserState old_state;
1417     size_t sz;
1418 
1419     buf = read_file(track->library, fname, &sz);
1420     if (!buf)
1421         return 1;
1422 #ifdef CONFIG_ICONV
1423     if (codepage) {
1424         char *tmpbuf;
1425         tmpbuf = sub_recode(track->library, buf, sz, codepage);
1426         free(buf);
1427         buf = tmpbuf;
1428     }
1429     if (!buf)
1430         return 1;
1431 #endif
1432 
1433     old_state = track->parser_priv->state;
1434     track->parser_priv->state = PST_STYLES;
1435     process_text(track, buf);
1436     free(buf);
1437     track->parser_priv->state = old_state;
1438 
1439     return 0;
1440 }
1441 
ass_step_sub(ASS_Track * track,long long now,int movement)1442 long long ass_step_sub(ASS_Track *track, long long now, int movement)
1443 {
1444     int i;
1445     ASS_Event *best = NULL;
1446     long long target = now;
1447     int direction = (movement > 0 ? 1 : -1) * !!movement;
1448 
1449     if (track->n_events == 0)
1450         return 0;
1451 
1452     do {
1453         ASS_Event *closest = NULL;
1454         long long closest_time = now;
1455         for (i = 0; i < track->n_events; i++) {
1456             if (direction < 0) {
1457                 long long end =
1458                     track->events[i].Start + track->events[i].Duration;
1459                 if (end < target) {
1460                     if (!closest || end > closest_time) {
1461                         closest = &track->events[i];
1462                         closest_time = end;
1463                     }
1464                 }
1465             } else if (direction > 0) {
1466                 long long start = track->events[i].Start;
1467                 if (start > target) {
1468                     if (!closest || start < closest_time) {
1469                         closest = &track->events[i];
1470                         closest_time = start;
1471                     }
1472                 }
1473             } else {
1474                 long long start = track->events[i].Start;
1475                 if (start < target) {
1476                     if (!closest || start >= closest_time) {
1477                         closest = &track->events[i];
1478                         closest_time = start;
1479                     }
1480                 }
1481             }
1482         }
1483         target = closest_time + direction;
1484         movement -= direction;
1485         if (closest)
1486             best = closest;
1487     } while (movement);
1488 
1489     return best ? best->Start - now : 0;
1490 }
1491 
ass_new_track(ASS_Library * library)1492 ASS_Track *ass_new_track(ASS_Library *library)
1493 {
1494     int def_sid = -1;
1495     ASS_Track *track = calloc(1, sizeof(ASS_Track));
1496     if (!track)
1497         goto fail;
1498     track->library = library;
1499     track->ScaledBorderAndShadow = 0;
1500     track->parser_priv = calloc(1, sizeof(ASS_ParserPriv));
1501     if (!track->parser_priv)
1502         goto fail;
1503     def_sid = ass_alloc_style(track);
1504     if (def_sid < 0)
1505         goto fail;
1506     set_default_style(track->styles + def_sid);
1507     track->default_style = def_sid;
1508     if (!track->styles[def_sid].Name || !track->styles[def_sid].FontName)
1509         goto fail;
1510     track->parser_priv->check_readorder = 1;
1511     return track;
1512 
1513 fail:
1514     if (track) {
1515         if (def_sid >= 0)
1516             ass_free_style(track, def_sid);
1517         free(track->parser_priv);
1518         free(track);
1519     }
1520     return NULL;
1521 }
1522 
ass_track_set_feature(ASS_Track * track,ASS_Feature feature,int enable)1523 int ass_track_set_feature(ASS_Track *track, ASS_Feature feature, int enable)
1524 {
1525     switch (feature) {
1526     case ASS_FEATURE_INCOMPATIBLE_EXTENSIONS:
1527         //-fallthrough
1528 #ifdef USE_FRIBIDI_EX_API
1529     case ASS_FEATURE_BIDI_BRACKETS:
1530         track->parser_priv->bidi_brackets = !!enable;
1531 #endif
1532         return 0;
1533     default:
1534         return -1;
1535     }
1536 }
1537 
1538 /**
1539  * \brief Prepare track for rendering
1540  */
ass_lazy_track_init(ASS_Library * lib,ASS_Track * track)1541 void ass_lazy_track_init(ASS_Library *lib, ASS_Track *track)
1542 {
1543     if (track->PlayResX > 0 && track->PlayResY > 0)
1544         return;
1545     if (track->PlayResX <= 0 && track->PlayResY <= 0) {
1546         ass_msg(lib, MSGL_WARN,
1547                "Neither PlayResX nor PlayResY defined. Assuming 384x288");
1548         track->PlayResX = 384;
1549         track->PlayResY = 288;
1550     } else {
1551         if (track->PlayResY <= 0 && track->PlayResX == 1280) {
1552             track->PlayResY = 1024;
1553             ass_msg(lib, MSGL_WARN,
1554                    "PlayResY undefined, setting to %d", track->PlayResY);
1555         } else if (track->PlayResY <= 0) {
1556             track->PlayResY = FFMAX(1, track->PlayResX * 3LL / 4);
1557             ass_msg(lib, MSGL_WARN,
1558                    "PlayResY undefined, setting to %d", track->PlayResY);
1559         } else if (track->PlayResX <= 0 && track->PlayResY == 1024) {
1560             track->PlayResX = 1280;
1561             ass_msg(lib, MSGL_WARN,
1562                    "PlayResX undefined, setting to %d", track->PlayResX);
1563         } else if (track->PlayResX <= 0) {
1564             track->PlayResX = FFMAX(1, track->PlayResY * 4LL / 3);
1565             ass_msg(lib, MSGL_WARN,
1566                    "PlayResX undefined, setting to %d", track->PlayResX);
1567         }
1568     }
1569 }
1570