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