Handle illegal \a tags like VSFilter
[libass.git] / libass / ass.c
blob057a6e349b8fcc31f120e5e41a1e22d8dd7c145c
1 /*
2 * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
4 * This file is part of libass.
6 * libass is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * libass is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with libass; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "config.h"
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <assert.h>
27 #include <errno.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <unistd.h>
31 #include <inttypes.h>
33 #ifdef CONFIG_ICONV
34 #include <iconv.h>
35 #endif
37 #include "ass.h"
38 #include "ass_utils.h"
39 #include "ass_library.h"
41 typedef enum {
42 PST_UNKNOWN = 0,
43 PST_INFO,
44 PST_STYLES,
45 PST_EVENTS,
46 PST_FONTS
47 } ParserState;
49 struct parser_priv {
50 ParserState state;
51 char *fontname;
52 char *fontdata;
53 int fontdata_size;
54 int fontdata_used;
57 #define ASS_STYLES_ALLOC 20
58 #define ASS_EVENTS_ALLOC 200
60 void ass_free_track(ASS_Track *track)
62 int i;
64 if (track->parser_priv) {
65 if (track->parser_priv->fontname)
66 free(track->parser_priv->fontname);
67 if (track->parser_priv->fontdata)
68 free(track->parser_priv->fontdata);
69 free(track->parser_priv);
71 if (track->style_format)
72 free(track->style_format);
73 if (track->event_format)
74 free(track->event_format);
75 if (track->styles) {
76 for (i = 0; i < track->n_styles; ++i)
77 ass_free_style(track, i);
78 free(track->styles);
80 if (track->events) {
81 for (i = 0; i < track->n_events; ++i)
82 ass_free_event(track, i);
83 free(track->events);
85 free(track->name);
86 free(track);
89 /// \brief Allocate a new style struct
90 /// \param track track
91 /// \return style id
92 int ass_alloc_style(ASS_Track *track)
94 int sid;
96 assert(track->n_styles <= track->max_styles);
98 if (track->n_styles == track->max_styles) {
99 track->max_styles += ASS_STYLES_ALLOC;
100 track->styles =
101 (ASS_Style *) realloc(track->styles,
102 sizeof(ASS_Style) *
103 track->max_styles);
106 sid = track->n_styles++;
107 memset(track->styles + sid, 0, sizeof(ASS_Style));
108 return sid;
111 /// \brief Allocate a new event struct
112 /// \param track track
113 /// \return event id
114 int ass_alloc_event(ASS_Track *track)
116 int eid;
118 assert(track->n_events <= track->max_events);
120 if (track->n_events == track->max_events) {
121 track->max_events += ASS_EVENTS_ALLOC;
122 track->events =
123 (ASS_Event *) realloc(track->events,
124 sizeof(ASS_Event) *
125 track->max_events);
128 eid = track->n_events++;
129 memset(track->events + eid, 0, sizeof(ASS_Event));
130 return eid;
133 void ass_free_event(ASS_Track *track, int eid)
135 ASS_Event *event = track->events + eid;
136 if (event->Name)
137 free(event->Name);
138 if (event->Effect)
139 free(event->Effect);
140 if (event->Text)
141 free(event->Text);
142 if (event->render_priv)
143 free(event->render_priv);
146 void ass_free_style(ASS_Track *track, int sid)
148 ASS_Style *style = track->styles + sid;
149 if (style->Name)
150 free(style->Name);
151 if (style->FontName)
152 free(style->FontName);
155 // ==============================================================================================
157 static void skip_spaces(char **str)
159 char *p = *str;
160 while ((*p == ' ') || (*p == '\t'))
161 ++p;
162 *str = p;
165 static void rskip_spaces(char **str, char *limit)
167 char *p = *str;
168 while ((p >= limit) && ((*p == ' ') || (*p == '\t')))
169 --p;
170 *str = p;
174 * \brief find style by name
175 * \param track track
176 * \param name style name
177 * \return index in track->styles
178 * Returnes 0 if no styles found => expects at least 1 style.
179 * Parsing code always adds "Default" style in the end.
181 static int lookup_style(ASS_Track *track, char *name)
183 int i;
184 if (*name == '*')
185 ++name; // FIXME: what does '*' really mean ?
186 for (i = track->n_styles - 1; i >= 0; --i) {
187 // FIXME: mb strcasecmp ?
188 if (strcmp(track->styles[i].Name, name) == 0)
189 return i;
191 i = track->default_style;
192 ass_msg(track->library, MSGL_WARN,
193 "[%p]: Warning: no style named '%s' found, using '%s'",
194 track, name, track->styles[i].Name);
195 return i; // use the first style
198 static uint32_t string2color(ASS_Library *library, char *p)
200 uint32_t tmp;
201 (void) strtocolor(library, &p, &tmp, 0);
202 return tmp;
205 static long long string2timecode(ASS_Library *library, char *p)
207 unsigned h, m, s, ms;
208 long long tm;
209 int res = sscanf(p, "%1d:%2d:%2d.%2d", &h, &m, &s, &ms);
210 if (res < 4) {
211 ass_msg(library, MSGL_WARN, "Bad timestamp");
212 return 0;
214 tm = ((h * 60 + m) * 60 + s) * 1000 + ms * 10;
215 return tm;
219 * \brief converts numpad-style align to align.
221 static int numpad2align(int val)
223 int res, v;
224 v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment
225 if (v != 0)
226 v = 3 - v;
227 res = ((val - 1) % 3) + 1; // horizontal alignment
228 res += v * 4;
229 return res;
232 #define NEXT(str,token) \
233 token = next_token(&str); \
234 if (!token) break;
236 #define ANYVAL(name,func) \
237 } else if (strcasecmp(tname, #name) == 0) { \
238 target->name = func(token); \
239 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
241 #define STRVAL(name) \
242 } else if (strcasecmp(tname, #name) == 0) { \
243 if (target->name != NULL) free(target->name); \
244 target->name = strdup(token); \
245 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
247 #define COLORVAL(name) \
248 } else if (strcasecmp(tname, #name) == 0) { \
249 target->name = string2color(track->library, token); \
250 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
252 #define INTVAL(name) ANYVAL(name,atoi)
253 #define FPVAL(name) ANYVAL(name,atof)
254 #define TIMEVAL(name) \
255 } else if (strcasecmp(tname, #name) == 0) { \
256 target->name = string2timecode(track->library, token); \
257 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
259 #define STYLEVAL(name) \
260 } else if (strcasecmp(tname, #name) == 0) { \
261 target->name = lookup_style(track, token); \
262 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
264 #define ALIAS(alias,name) \
265 if (strcasecmp(tname, #alias) == 0) {tname = #name;}
267 static char *next_token(char **str)
269 char *p = *str;
270 char *start;
271 skip_spaces(&p);
272 if (*p == '\0') {
273 *str = p;
274 return 0;
276 start = p; // start of the token
277 for (; (*p != '\0') && (*p != ','); ++p) {
279 if (*p == '\0') {
280 *str = p; // eos found, str will point to '\0' at exit
281 } else {
282 *p = '\0';
283 *str = p + 1; // ',' found, str will point to the next char (beginning of the next token)
285 --p; // end of current token
286 rskip_spaces(&p, start);
287 if (p < start)
288 p = start; // empty token
289 else
290 ++p; // the first space character, or '\0'
291 *p = '\0';
292 return start;
296 * \brief Parse the tail of Dialogue line
297 * \param track track
298 * \param event parsed data goes here
299 * \param str string to parse, zero-terminated
300 * \param n_ignored number of format options to skip at the beginning
302 static int process_event_tail(ASS_Track *track, ASS_Event *event,
303 char *str, int n_ignored)
305 char *token;
306 char *tname;
307 char *p = str;
308 int i;
309 ASS_Event *target = event;
311 char *format = strdup(track->event_format);
312 char *q = format; // format scanning pointer
314 if (track->n_styles == 0) {
315 // add "Default" style to the end
316 // will be used if track does not contain a default style (or even does not contain styles at all)
317 int sid = ass_alloc_style(track);
318 track->styles[sid].Name = strdup("Default");
319 track->styles[sid].FontName = strdup("Arial");
322 for (i = 0; i < n_ignored; ++i) {
323 NEXT(q, tname);
326 while (1) {
327 NEXT(q, tname);
328 if (strcasecmp(tname, "Text") == 0) {
329 char *last;
330 event->Text = strdup(p);
331 if (*event->Text != 0) {
332 last = event->Text + strlen(event->Text) - 1;
333 if (last >= event->Text && *last == '\r')
334 *last = 0;
336 ass_msg(track->library, MSGL_DBG2, "Text = %s", event->Text);
337 event->Duration -= event->Start;
338 free(format);
339 return 0; // "Text" is always the last
341 NEXT(p, token);
343 ALIAS(End, Duration) // temporarily store end timecode in event->Duration
344 if (0) { // cool ;)
345 INTVAL(Layer)
346 STYLEVAL(Style)
347 STRVAL(Name)
348 STRVAL(Effect)
349 INTVAL(MarginL)
350 INTVAL(MarginR)
351 INTVAL(MarginV)
352 TIMEVAL(Start)
353 TIMEVAL(Duration)
356 free(format);
357 return 1;
361 * \brief Parse command line style overrides (--ass-force-style option)
362 * \param track track to apply overrides to
363 * The format for overrides is [StyleName.]Field=Value
365 void ass_process_force_style(ASS_Track *track)
367 char **fs, *eq, *dt, *style, *tname, *token;
368 ASS_Style *target;
369 int sid;
370 char **list = track->library->style_overrides;
372 if (!list)
373 return;
375 for (fs = list; *fs; ++fs) {
376 eq = strrchr(*fs, '=');
377 if (!eq)
378 continue;
379 *eq = '\0';
380 token = eq + 1;
382 if (!strcasecmp(*fs, "PlayResX"))
383 track->PlayResX = atoi(token);
384 else if (!strcasecmp(*fs, "PlayResY"))
385 track->PlayResY = atoi(token);
386 else if (!strcasecmp(*fs, "Timer"))
387 track->Timer = atof(token);
388 else if (!strcasecmp(*fs, "WrapStyle"))
389 track->WrapStyle = atoi(token);
390 else if (!strcasecmp(*fs, "ScaledBorderAndShadow"))
391 track->ScaledBorderAndShadow = parse_bool(token);
393 dt = strrchr(*fs, '.');
394 if (dt) {
395 *dt = '\0';
396 style = *fs;
397 tname = dt + 1;
398 } else {
399 style = NULL;
400 tname = *fs;
402 for (sid = 0; sid < track->n_styles; ++sid) {
403 if (style == NULL
404 || strcasecmp(track->styles[sid].Name, style) == 0) {
405 target = track->styles + sid;
406 if (0) {
407 STRVAL(FontName)
408 COLORVAL(PrimaryColour)
409 COLORVAL(SecondaryColour)
410 COLORVAL(OutlineColour)
411 COLORVAL(BackColour)
412 FPVAL(FontSize)
413 INTVAL(Bold)
414 INTVAL(Italic)
415 INTVAL(Underline)
416 INTVAL(StrikeOut)
417 FPVAL(Spacing)
418 INTVAL(Angle)
419 INTVAL(BorderStyle)
420 INTVAL(Alignment)
421 INTVAL(MarginL)
422 INTVAL(MarginR)
423 INTVAL(MarginV)
424 INTVAL(Encoding)
425 FPVAL(ScaleX)
426 FPVAL(ScaleY)
427 FPVAL(Outline)
428 FPVAL(Shadow)
432 *eq = '=';
433 if (dt)
434 *dt = '.';
439 * \brief Parse the Style line
440 * \param track track
441 * \param str string to parse, zero-terminated
442 * Allocates a new style struct.
444 static int process_style(ASS_Track *track, char *str)
447 char *token;
448 char *tname;
449 char *p = str;
450 char *format;
451 char *q; // format scanning pointer
452 int sid;
453 ASS_Style *style;
454 ASS_Style *target;
456 if (!track->style_format) {
457 // no style format header
458 // probably an ancient script version
459 if (track->track_type == TRACK_TYPE_SSA)
460 track->style_format =
461 strdup
462 ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
463 "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline,"
464 "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
465 else
466 track->style_format =
467 strdup
468 ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
469 "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut,"
470 "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow,"
471 "Alignment, MarginL, MarginR, MarginV, Encoding");
474 q = format = strdup(track->style_format);
476 ass_msg(track->library, MSGL_V, "[%p] Style: %s", track, str);
478 sid = ass_alloc_style(track);
480 style = track->styles + sid;
481 target = style;
483 // fill style with some default values
484 style->ScaleX = 100.;
485 style->ScaleY = 100.;
487 while (1) {
488 NEXT(q, tname);
489 NEXT(p, token);
491 if (0) { // cool ;)
492 STRVAL(Name)
493 if ((strcmp(target->Name, "Default") == 0)
494 || (strcmp(target->Name, "*Default") == 0))
495 track->default_style = sid;
496 STRVAL(FontName)
497 COLORVAL(PrimaryColour)
498 COLORVAL(SecondaryColour)
499 COLORVAL(OutlineColour) // TertiaryColor
500 COLORVAL(BackColour)
501 // SSA uses BackColour for both outline and shadow
502 // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
503 if (track->track_type == TRACK_TYPE_SSA)
504 target->OutlineColour = target->BackColour;
505 FPVAL(FontSize)
506 INTVAL(Bold)
507 INTVAL(Italic)
508 INTVAL(Underline)
509 INTVAL(StrikeOut)
510 FPVAL(Spacing)
511 INTVAL(Angle)
512 INTVAL(BorderStyle)
513 INTVAL(Alignment)
514 if (track->track_type == TRACK_TYPE_ASS)
515 target->Alignment = numpad2align(target->Alignment);
516 INTVAL(MarginL)
517 INTVAL(MarginR)
518 INTVAL(MarginV)
519 INTVAL(Encoding)
520 FPVAL(ScaleX)
521 FPVAL(ScaleY)
522 FPVAL(Outline)
523 FPVAL(Shadow)
526 style->ScaleX /= 100.;
527 style->ScaleY /= 100.;
528 style->Bold = !!style->Bold;
529 style->Italic = !!style->Italic;
530 style->Underline = !!style->Underline;
531 if (!style->Name)
532 style->Name = strdup("Default");
533 if (!style->FontName)
534 style->FontName = strdup("Arial");
535 // skip '@' at the start of the font name
536 if (*style->FontName == '@') {
537 p = style->FontName;
538 style->FontName = strdup(p + 1);
539 free(p);
541 free(format);
542 return 0;
546 static int process_styles_line(ASS_Track *track, char *str)
548 if (!strncmp(str, "Format:", 7)) {
549 char *p = str + 7;
550 skip_spaces(&p);
551 track->style_format = strdup(p);
552 ass_msg(track->library, MSGL_DBG2, "Style format: %s",
553 track->style_format);
554 } else if (!strncmp(str, "Style:", 6)) {
555 char *p = str + 6;
556 skip_spaces(&p);
557 process_style(track, p);
559 return 0;
562 static int process_info_line(ASS_Track *track, char *str)
564 if (!strncmp(str, "PlayResX:", 9)) {
565 track->PlayResX = atoi(str + 9);
566 } else if (!strncmp(str, "PlayResY:", 9)) {
567 track->PlayResY = atoi(str + 9);
568 } else if (!strncmp(str, "Timer:", 6)) {
569 track->Timer = atof(str + 6);
570 } else if (!strncmp(str, "WrapStyle:", 10)) {
571 track->WrapStyle = atoi(str + 10);
572 } else if (!strncmp(str, "ScaledBorderAndShadow:", 22)) {
573 track->ScaledBorderAndShadow = parse_bool(str + 22);
575 return 0;
578 static void event_format_fallback(ASS_Track *track)
580 track->parser_priv->state = PST_EVENTS;
581 if (track->track_type == TRACK_TYPE_SSA)
582 track->event_format = strdup("Format: Marked, Start, End, Style, "
583 "Name, MarginL, MarginR, MarginV, Effect, Text");
584 else
585 track->event_format = strdup("Format: Layer, Start, End, Style, "
586 "Actor, MarginL, MarginR, MarginV, Effect, Text");
587 ass_msg(track->library, MSGL_V,
588 "No event format found, using fallback");
591 static int process_events_line(ASS_Track *track, char *str)
593 if (!strncmp(str, "Format:", 7)) {
594 char *p = str + 7;
595 skip_spaces(&p);
596 track->event_format = strdup(p);
597 ass_msg(track->library, MSGL_DBG2, "Event format: %s", track->event_format);
598 } else if (!strncmp(str, "Dialogue:", 9)) {
599 // This should never be reached for embedded subtitles.
600 // They have slightly different format and are parsed in ass_process_chunk,
601 // called directly from demuxer
602 int eid;
603 ASS_Event *event;
605 str += 9;
606 skip_spaces(&str);
608 eid = ass_alloc_event(track);
609 event = track->events + eid;
611 // We can't parse events with event_format
612 if (!track->event_format)
613 event_format_fallback(track);
615 process_event_tail(track, event, str, 0);
616 } else {
617 ass_msg(track->library, MSGL_V, "Not understood: '%s'", str);
619 return 0;
622 // Copied from mkvtoolnix
623 static unsigned char *decode_chars(unsigned char c1, unsigned char c2,
624 unsigned char c3, unsigned char c4,
625 unsigned char *dst, int cnt)
627 uint32_t value;
628 unsigned char bytes[3];
629 int i;
631 value =
632 ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 -
633 33);
634 bytes[2] = value & 0xff;
635 bytes[1] = (value & 0xff00) >> 8;
636 bytes[0] = (value & 0xff0000) >> 16;
638 for (i = 0; i < cnt; ++i)
639 *dst++ = bytes[i];
640 return dst;
643 static int decode_font(ASS_Track *track)
645 unsigned char *p;
646 unsigned char *q;
647 int i;
648 int size; // original size
649 int dsize; // decoded size
650 unsigned char *buf = 0;
652 ass_msg(track->library, MSGL_V, "Font: %d bytes encoded data",
653 track->parser_priv->fontdata_used);
654 size = track->parser_priv->fontdata_used;
655 if (size % 4 == 1) {
656 ass_msg(track->library, MSGL_ERR, "Bad encoded data size");
657 goto error_decode_font;
659 buf = malloc(size / 4 * 3 + 2);
660 q = buf;
661 for (i = 0, p = (unsigned char *) track->parser_priv->fontdata;
662 i < size / 4; i++, p += 4) {
663 q = decode_chars(p[0], p[1], p[2], p[3], q, 3);
665 if (size % 4 == 2) {
666 q = decode_chars(p[0], p[1], 0, 0, q, 1);
667 } else if (size % 4 == 3) {
668 q = decode_chars(p[0], p[1], p[2], 0, q, 2);
670 dsize = q - buf;
671 assert(dsize <= size / 4 * 3 + 2);
673 if (track->library->extract_fonts) {
674 ass_add_font(track->library, track->parser_priv->fontname,
675 (char *) buf, dsize);
676 buf = 0;
679 error_decode_font:
680 if (buf)
681 free(buf);
682 free(track->parser_priv->fontname);
683 free(track->parser_priv->fontdata);
684 track->parser_priv->fontname = 0;
685 track->parser_priv->fontdata = 0;
686 track->parser_priv->fontdata_size = 0;
687 track->parser_priv->fontdata_used = 0;
688 return 0;
691 static int process_fonts_line(ASS_Track *track, char *str)
693 int len;
695 if (!strncmp(str, "fontname:", 9)) {
696 char *p = str + 9;
697 skip_spaces(&p);
698 if (track->parser_priv->fontname) {
699 decode_font(track);
701 track->parser_priv->fontname = strdup(p);
702 ass_msg(track->library, MSGL_V, "Fontname: %s",
703 track->parser_priv->fontname);
704 return 0;
707 if (!track->parser_priv->fontname) {
708 ass_msg(track->library, MSGL_V, "Not understood: '%s'", str);
709 return 0;
712 len = strlen(str);
713 if (len > 80) {
714 ass_msg(track->library, MSGL_WARN, "Font line too long: %d, %s",
715 len, str);
716 return 0;
718 if (track->parser_priv->fontdata_used + len >
719 track->parser_priv->fontdata_size) {
720 track->parser_priv->fontdata_size += 100 * 1024;
721 track->parser_priv->fontdata =
722 realloc(track->parser_priv->fontdata,
723 track->parser_priv->fontdata_size);
725 memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used,
726 str, len);
727 track->parser_priv->fontdata_used += len;
729 return 0;
733 * \brief Parse a header line
734 * \param track track
735 * \param str string to parse, zero-terminated
737 static int process_line(ASS_Track *track, char *str)
739 if (!strncasecmp(str, "[Script Info]", 13)) {
740 track->parser_priv->state = PST_INFO;
741 } else if (!strncasecmp(str, "[V4 Styles]", 11)) {
742 track->parser_priv->state = PST_STYLES;
743 track->track_type = TRACK_TYPE_SSA;
744 } else if (!strncasecmp(str, "[V4+ Styles]", 12)) {
745 track->parser_priv->state = PST_STYLES;
746 track->track_type = TRACK_TYPE_ASS;
747 } else if (!strncasecmp(str, "[Events]", 8)) {
748 track->parser_priv->state = PST_EVENTS;
749 } else if (!strncasecmp(str, "[Fonts]", 7)) {
750 track->parser_priv->state = PST_FONTS;
751 } else {
752 switch (track->parser_priv->state) {
753 case PST_INFO:
754 process_info_line(track, str);
755 break;
756 case PST_STYLES:
757 process_styles_line(track, str);
758 break;
759 case PST_EVENTS:
760 process_events_line(track, str);
761 break;
762 case PST_FONTS:
763 process_fonts_line(track, str);
764 break;
765 default:
766 break;
770 // there is no explicit end-of-font marker in ssa/ass
771 if ((track->parser_priv->state != PST_FONTS)
772 && (track->parser_priv->fontname))
773 decode_font(track);
775 return 0;
778 static int process_text(ASS_Track *track, char *str)
780 char *p = str;
781 while (1) {
782 char *q;
783 while (1) {
784 if ((*p == '\r') || (*p == '\n'))
785 ++p;
786 else if (p[0] == '\xef' && p[1] == '\xbb' && p[2] == '\xbf')
787 p += 3; // U+FFFE (BOM)
788 else
789 break;
791 for (q = p; ((*q != '\0') && (*q != '\r') && (*q != '\n')); ++q) {
793 if (q == p)
794 break;
795 if (*q != '\0')
796 *(q++) = '\0';
797 process_line(track, p);
798 if (*q == '\0')
799 break;
800 p = q;
802 return 0;
806 * \brief Process a chunk of subtitle stream data.
807 * \param track track
808 * \param data string to parse
809 * \param size length of data
811 void ass_process_data(ASS_Track *track, char *data, int size)
813 char *str = malloc(size + 1);
815 memcpy(str, data, size);
816 str[size] = '\0';
818 ass_msg(track->library, MSGL_V, "Event: %s", str);
819 process_text(track, str);
820 free(str);
824 * \brief Process CodecPrivate section of subtitle stream
825 * \param track track
826 * \param data string to parse
827 * \param size length of data
828 CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections
830 void ass_process_codec_private(ASS_Track *track, char *data, int size)
832 ass_process_data(track, data, size);
834 // probably an mkv produced by ancient mkvtoolnix
835 // such files don't have [Events] and Format: headers
836 if (!track->event_format)
837 event_format_fallback(track);
839 ass_process_force_style(track);
842 static int check_duplicate_event(ASS_Track *track, int ReadOrder)
844 int i;
845 for (i = 0; i < track->n_events - 1; ++i) // ignoring last event, it is the one we are comparing with
846 if (track->events[i].ReadOrder == ReadOrder)
847 return 1;
848 return 0;
852 * \brief Process a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary).
853 * \param track track
854 * \param data string to parse
855 * \param size length of data
856 * \param timecode starting time of the event (milliseconds)
857 * \param duration duration of the event (milliseconds)
859 void ass_process_chunk(ASS_Track *track, char *data, int size,
860 long long timecode, long long duration)
862 char *str;
863 int eid;
864 char *p;
865 char *token;
866 ASS_Event *event;
868 if (!track->event_format) {
869 ass_msg(track->library, MSGL_WARN, "Event format header missing");
870 return;
873 str = malloc(size + 1);
874 memcpy(str, data, size);
875 str[size] = '\0';
876 ass_msg(track->library, MSGL_V, "Event at %" PRId64 ", +%" PRId64 ": %s",
877 (int64_t) timecode, (int64_t) duration, str);
879 eid = ass_alloc_event(track);
880 event = track->events + eid;
882 p = str;
884 do {
885 NEXT(p, token);
886 event->ReadOrder = atoi(token);
887 if (check_duplicate_event(track, event->ReadOrder))
888 break;
890 NEXT(p, token);
891 event->Layer = atoi(token);
893 process_event_tail(track, event, p, 3);
895 event->Start = timecode;
896 event->Duration = duration;
898 free(str);
899 return;
900 // dump_events(tid);
901 } while (0);
902 // some error
903 ass_free_event(track, eid);
904 track->n_events--;
905 free(str);
908 #ifdef CONFIG_ICONV
909 /** \brief recode buffer to utf-8
910 * constraint: codepage != 0
911 * \param data pointer to text buffer
912 * \param size buffer size
913 * \return a pointer to recoded buffer, caller is responsible for freeing it
915 static char *sub_recode(ASS_Library *library, char *data, size_t size,
916 char *codepage)
918 iconv_t icdsc;
919 char *tocp = "UTF-8";
920 char *outbuf;
921 assert(codepage);
924 const char *cp_tmp = codepage;
925 #ifdef CONFIG_ENCA
926 char enca_lang[3], enca_fallback[100];
927 if (sscanf(codepage, "enca:%2s:%99s", enca_lang, enca_fallback) == 2
928 || sscanf(codepage, "ENCA:%2s:%99s", enca_lang,
929 enca_fallback) == 2) {
930 cp_tmp =
931 ass_guess_buffer_cp(library, (unsigned char *) data, size,
932 enca_lang, enca_fallback);
934 #endif
935 if ((icdsc = iconv_open(tocp, cp_tmp)) != (iconv_t) (-1)) {
936 ass_msg(library, MSGL_V, "Opened iconv descriptor");
937 } else
938 ass_msg(library, MSGL_ERR, "Error opening iconv descriptor");
942 size_t osize = size;
943 size_t ileft = size;
944 size_t oleft = size - 1;
945 char *ip;
946 char *op;
947 size_t rc;
948 int clear = 0;
950 outbuf = malloc(osize);
951 ip = data;
952 op = outbuf;
954 while (1) {
955 if (ileft)
956 rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
957 else { // clear the conversion state and leave
958 clear = 1;
959 rc = iconv(icdsc, NULL, NULL, &op, &oleft);
961 if (rc == (size_t) (-1)) {
962 if (errno == E2BIG) {
963 size_t offset = op - outbuf;
964 outbuf = (char *) realloc(outbuf, osize + size);
965 op = outbuf + offset;
966 osize += size;
967 oleft += size;
968 } else {
969 ass_msg(library, MSGL_WARN, "Error recoding file");
970 return NULL;
972 } else if (clear)
973 break;
975 outbuf[osize - oleft - 1] = 0;
978 if (icdsc != (iconv_t) (-1)) {
979 (void) iconv_close(icdsc);
980 icdsc = (iconv_t) (-1);
981 ass_msg(library, MSGL_V, "Closed iconv descriptor");
984 return outbuf;
986 #endif // ICONV
989 * \brief read file contents into newly allocated buffer
990 * \param fname file name
991 * \param bufsize out: file size
992 * \return pointer to file contents. Caller is responsible for its deallocation.
994 static char *read_file(ASS_Library *library, char *fname, size_t *bufsize)
996 int res;
997 long sz;
998 long bytes_read;
999 char *buf;
1001 FILE *fp = fopen(fname, "rb");
1002 if (!fp) {
1003 ass_msg(library, MSGL_WARN,
1004 "ass_read_file(%s): fopen failed", fname);
1005 return 0;
1007 res = fseek(fp, 0, SEEK_END);
1008 if (res == -1) {
1009 ass_msg(library, MSGL_WARN,
1010 "ass_read_file(%s): fseek failed", fname);
1011 fclose(fp);
1012 return 0;
1015 sz = ftell(fp);
1016 rewind(fp);
1018 if (sz > 10 * 1024 * 1024) {
1019 ass_msg(library, MSGL_INFO,
1020 "ass_read_file(%s): Refusing to load subtitles "
1021 "larger than 10MiB", fname);
1022 fclose(fp);
1023 return 0;
1026 ass_msg(library, MSGL_V, "File size: %ld", sz);
1028 buf = malloc(sz + 1);
1029 assert(buf);
1030 bytes_read = 0;
1031 do {
1032 res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
1033 if (res <= 0) {
1034 ass_msg(library, MSGL_INFO, "Read failed, %d: %s", errno,
1035 strerror(errno));
1036 fclose(fp);
1037 free(buf);
1038 return 0;
1040 bytes_read += res;
1041 } while (sz - bytes_read > 0);
1042 buf[sz] = '\0';
1043 fclose(fp);
1045 if (bufsize)
1046 *bufsize = sz;
1047 return buf;
1051 * \param buf pointer to subtitle text in utf-8
1053 static ASS_Track *parse_memory(ASS_Library *library, char *buf)
1055 ASS_Track *track;
1056 int i;
1058 track = ass_new_track(library);
1060 // process header
1061 process_text(track, buf);
1063 // external SSA/ASS subs does not have ReadOrder field
1064 for (i = 0; i < track->n_events; ++i)
1065 track->events[i].ReadOrder = i;
1067 // there is no explicit end-of-font marker in ssa/ass
1068 if (track->parser_priv->fontname)
1069 decode_font(track);
1071 if (track->track_type == TRACK_TYPE_UNKNOWN) {
1072 ass_free_track(track);
1073 return 0;
1076 ass_process_force_style(track);
1078 return track;
1082 * \brief Read subtitles from memory.
1083 * \param library libass library object
1084 * \param buf pointer to subtitles text
1085 * \param bufsize size of buffer
1086 * \param codepage recode buffer contents from given codepage
1087 * \return newly allocated track
1089 ASS_Track *ass_read_memory(ASS_Library *library, char *buf,
1090 size_t bufsize, char *codepage)
1092 ASS_Track *track;
1093 int need_free = 0;
1095 if (!buf)
1096 return 0;
1098 #ifdef CONFIG_ICONV
1099 if (codepage)
1100 buf = sub_recode(library, buf, bufsize, codepage);
1101 if (!buf)
1102 return 0;
1103 else
1104 need_free = 1;
1105 #endif
1106 track = parse_memory(library, buf);
1107 if (need_free)
1108 free(buf);
1109 if (!track)
1110 return 0;
1112 ass_msg(library, MSGL_INFO, "Added subtitle file: "
1113 "<memory> (%d styles, %d events)",
1114 track->n_styles, track->n_events);
1115 return track;
1118 static char *read_file_recode(ASS_Library *library, char *fname,
1119 char *codepage, size_t *size)
1121 char *buf;
1122 size_t bufsize;
1124 buf = read_file(library, fname, &bufsize);
1125 if (!buf)
1126 return 0;
1127 #ifdef CONFIG_ICONV
1128 if (codepage) {
1129 char *tmpbuf = sub_recode(library, buf, bufsize, codepage);
1130 free(buf);
1131 buf = tmpbuf;
1133 if (!buf)
1134 return 0;
1135 #endif
1136 *size = bufsize;
1137 return buf;
1141 * \brief Read subtitles from file.
1142 * \param library libass library object
1143 * \param fname file name
1144 * \param codepage recode buffer contents from given codepage
1145 * \return newly allocated track
1147 ASS_Track *ass_read_file(ASS_Library *library, char *fname,
1148 char *codepage)
1150 char *buf;
1151 ASS_Track *track;
1152 size_t bufsize;
1154 buf = read_file_recode(library, fname, codepage, &bufsize);
1155 if (!buf)
1156 return 0;
1157 track = parse_memory(library, buf);
1158 free(buf);
1159 if (!track)
1160 return 0;
1162 track->name = strdup(fname);
1164 ass_msg(library, MSGL_INFO,
1165 "Added subtitle file: '%s' (%d styles, %d events)",
1166 fname, track->n_styles, track->n_events);
1168 return track;
1172 * \brief read styles from file into already initialized track
1174 int ass_read_styles(ASS_Track *track, char *fname, char *codepage)
1176 char *buf;
1177 ParserState old_state;
1178 size_t sz;
1180 buf = read_file(track->library, fname, &sz);
1181 if (!buf)
1182 return 1;
1183 #ifdef CONFIG_ICONV
1184 if (codepage) {
1185 char *tmpbuf;
1186 tmpbuf = sub_recode(track->library, buf, sz, codepage);
1187 free(buf);
1188 buf = tmpbuf;
1190 if (!buf)
1191 return 0;
1192 #endif
1194 old_state = track->parser_priv->state;
1195 track->parser_priv->state = PST_STYLES;
1196 process_text(track, buf);
1197 track->parser_priv->state = old_state;
1199 return 0;
1202 long long ass_step_sub(ASS_Track *track, long long now, int movement)
1204 int i;
1206 if (movement == 0)
1207 return 0;
1208 if (track->n_events == 0)
1209 return 0;
1211 if (movement < 0)
1212 for (i = 0;
1213 (i < track->n_events)
1215 ((long long) (track->events[i].Start +
1216 track->events[i].Duration) <= now); ++i) {
1217 } else
1218 for (i = track->n_events - 1;
1219 (i >= 0) && ((long long) (track->events[i].Start) > now);
1220 --i) {
1223 // -1 and n_events are ok
1224 assert(i >= -1);
1225 assert(i <= track->n_events);
1226 i += movement;
1227 if (i < 0)
1228 i = 0;
1229 if (i >= track->n_events)
1230 i = track->n_events - 1;
1231 return ((long long) track->events[i].Start) - now;
1234 ASS_Track *ass_new_track(ASS_Library *library)
1236 ASS_Track *track = calloc(1, sizeof(ASS_Track));
1237 track->library = library;
1238 track->ScaledBorderAndShadow = 1;
1239 track->parser_priv = calloc(1, sizeof(ASS_ParserPriv));
1240 return track;