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