Remove unused functions.
[mplayer/glamo.git] / libass / ass.c
blob6becb39e8e990536f78d7b6b7309d94b669da292
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);
392 else if (!strcasecmp(*fs, "Kerning"))
393 track->Kerning = parse_bool(token);
395 dt = strrchr(*fs, '.');
396 if (dt) {
397 *dt = '\0';
398 style = *fs;
399 tname = dt + 1;
400 } else {
401 style = NULL;
402 tname = *fs;
404 for (sid = 0; sid < track->n_styles; ++sid) {
405 if (style == NULL
406 || strcasecmp(track->styles[sid].Name, style) == 0) {
407 target = track->styles + sid;
408 if (0) {
409 STRVAL(FontName)
410 COLORVAL(PrimaryColour)
411 COLORVAL(SecondaryColour)
412 COLORVAL(OutlineColour)
413 COLORVAL(BackColour)
414 FPVAL(FontSize)
415 INTVAL(Bold)
416 INTVAL(Italic)
417 INTVAL(Underline)
418 INTVAL(StrikeOut)
419 FPVAL(Spacing)
420 INTVAL(Angle)
421 INTVAL(BorderStyle)
422 INTVAL(Alignment)
423 INTVAL(MarginL)
424 INTVAL(MarginR)
425 INTVAL(MarginV)
426 INTVAL(Encoding)
427 FPVAL(ScaleX)
428 FPVAL(ScaleY)
429 FPVAL(Outline)
430 FPVAL(Shadow)
434 *eq = '=';
435 if (dt)
436 *dt = '.';
441 * \brief Parse the Style line
442 * \param track track
443 * \param str string to parse, zero-terminated
444 * Allocates a new style struct.
446 static int process_style(ASS_Track *track, char *str)
449 char *token;
450 char *tname;
451 char *p = str;
452 char *format;
453 char *q; // format scanning pointer
454 int sid;
455 ASS_Style *style;
456 ASS_Style *target;
458 if (!track->style_format) {
459 // no style format header
460 // probably an ancient script version
461 if (track->track_type == TRACK_TYPE_SSA)
462 track->style_format =
463 strdup
464 ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
465 "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline,"
466 "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
467 else
468 track->style_format =
469 strdup
470 ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
471 "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut,"
472 "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow,"
473 "Alignment, MarginL, MarginR, MarginV, Encoding");
476 q = format = strdup(track->style_format);
478 ass_msg(track->library, MSGL_V, "[%p] Style: %s", track, str);
480 sid = ass_alloc_style(track);
482 style = track->styles + sid;
483 target = style;
485 // fill style with some default values
486 style->ScaleX = 100.;
487 style->ScaleY = 100.;
489 while (1) {
490 NEXT(q, tname);
491 NEXT(p, token);
493 if (0) { // cool ;)
494 STRVAL(Name)
495 if ((strcmp(target->Name, "Default") == 0)
496 || (strcmp(target->Name, "*Default") == 0))
497 track->default_style = sid;
498 STRVAL(FontName)
499 COLORVAL(PrimaryColour)
500 COLORVAL(SecondaryColour)
501 COLORVAL(OutlineColour) // TertiaryColor
502 COLORVAL(BackColour)
503 // SSA uses BackColour for both outline and shadow
504 // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
505 if (track->track_type == TRACK_TYPE_SSA)
506 target->OutlineColour = target->BackColour;
507 FPVAL(FontSize)
508 INTVAL(Bold)
509 INTVAL(Italic)
510 INTVAL(Underline)
511 INTVAL(StrikeOut)
512 FPVAL(Spacing)
513 INTVAL(Angle)
514 INTVAL(BorderStyle)
515 INTVAL(Alignment)
516 if (track->track_type == TRACK_TYPE_ASS)
517 target->Alignment = numpad2align(target->Alignment);
518 INTVAL(MarginL)
519 INTVAL(MarginR)
520 INTVAL(MarginV)
521 INTVAL(Encoding)
522 FPVAL(ScaleX)
523 FPVAL(ScaleY)
524 FPVAL(Outline)
525 FPVAL(Shadow)
528 style->ScaleX /= 100.;
529 style->ScaleY /= 100.;
530 style->Bold = !!style->Bold;
531 style->Italic = !!style->Italic;
532 style->Underline = !!style->Underline;
533 if (!style->Name)
534 style->Name = strdup("Default");
535 if (!style->FontName)
536 style->FontName = strdup("Arial");
537 // skip '@' at the start of the font name
538 if (*style->FontName == '@') {
539 p = style->FontName;
540 style->FontName = strdup(p + 1);
541 free(p);
543 free(format);
544 return 0;
548 static int process_styles_line(ASS_Track *track, char *str)
550 if (!strncmp(str, "Format:", 7)) {
551 char *p = str + 7;
552 skip_spaces(&p);
553 track->style_format = strdup(p);
554 ass_msg(track->library, MSGL_DBG2, "Style format: %s",
555 track->style_format);
556 } else if (!strncmp(str, "Style:", 6)) {
557 char *p = str + 6;
558 skip_spaces(&p);
559 process_style(track, p);
561 return 0;
564 static int process_info_line(ASS_Track *track, char *str)
566 if (!strncmp(str, "PlayResX:", 9)) {
567 track->PlayResX = atoi(str + 9);
568 } else if (!strncmp(str, "PlayResY:", 9)) {
569 track->PlayResY = atoi(str + 9);
570 } else if (!strncmp(str, "Timer:", 6)) {
571 track->Timer = atof(str + 6);
572 } else if (!strncmp(str, "WrapStyle:", 10)) {
573 track->WrapStyle = atoi(str + 10);
574 } else if (!strncmp(str, "ScaledBorderAndShadow:", 22)) {
575 track->ScaledBorderAndShadow = parse_bool(str + 22);
576 } else if (!strncmp(str, "Kerning:", 8)) {
577 track->Kerning = parse_bool(str + 8);
579 return 0;
582 static void event_format_fallback(ASS_Track *track)
584 track->parser_priv->state = PST_EVENTS;
585 if (track->track_type == TRACK_TYPE_SSA)
586 track->event_format = strdup("Format: Marked, Start, End, Style, "
587 "Name, MarginL, MarginR, MarginV, Effect, Text");
588 else
589 track->event_format = strdup("Format: Layer, Start, End, Style, "
590 "Actor, MarginL, MarginR, MarginV, Effect, Text");
591 ass_msg(track->library, MSGL_V,
592 "No event format found, using fallback");
595 static int process_events_line(ASS_Track *track, char *str)
597 if (!strncmp(str, "Format:", 7)) {
598 char *p = str + 7;
599 skip_spaces(&p);
600 track->event_format = strdup(p);
601 ass_msg(track->library, MSGL_DBG2, "Event format: %s", track->event_format);
602 } else if (!strncmp(str, "Dialogue:", 9)) {
603 // This should never be reached for embedded subtitles.
604 // They have slightly different format and are parsed in ass_process_chunk,
605 // called directly from demuxer
606 int eid;
607 ASS_Event *event;
609 str += 9;
610 skip_spaces(&str);
612 eid = ass_alloc_event(track);
613 event = track->events + eid;
615 // We can't parse events with event_format
616 if (!track->event_format)
617 event_format_fallback(track);
619 process_event_tail(track, event, str, 0);
620 } else {
621 ass_msg(track->library, MSGL_V, "Not understood: '%s'", str);
623 return 0;
626 // Copied from mkvtoolnix
627 static unsigned char *decode_chars(unsigned char c1, unsigned char c2,
628 unsigned char c3, unsigned char c4,
629 unsigned char *dst, int cnt)
631 uint32_t value;
632 unsigned char bytes[3];
633 int i;
635 value =
636 ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 -
637 33);
638 bytes[2] = value & 0xff;
639 bytes[1] = (value & 0xff00) >> 8;
640 bytes[0] = (value & 0xff0000) >> 16;
642 for (i = 0; i < cnt; ++i)
643 *dst++ = bytes[i];
644 return dst;
647 static int decode_font(ASS_Track *track)
649 unsigned char *p;
650 unsigned char *q;
651 int i;
652 int size; // original size
653 int dsize; // decoded size
654 unsigned char *buf = 0;
656 ass_msg(track->library, MSGL_V, "Font: %d bytes encoded data",
657 track->parser_priv->fontdata_used);
658 size = track->parser_priv->fontdata_used;
659 if (size % 4 == 1) {
660 ass_msg(track->library, MSGL_ERR, "Bad encoded data size");
661 goto error_decode_font;
663 buf = malloc(size / 4 * 3 + 2);
664 q = buf;
665 for (i = 0, p = (unsigned char *) track->parser_priv->fontdata;
666 i < size / 4; i++, p += 4) {
667 q = decode_chars(p[0], p[1], p[2], p[3], q, 3);
669 if (size % 4 == 2) {
670 q = decode_chars(p[0], p[1], 0, 0, q, 1);
671 } else if (size % 4 == 3) {
672 q = decode_chars(p[0], p[1], p[2], 0, q, 2);
674 dsize = q - buf;
675 assert(dsize <= size / 4 * 3 + 2);
677 if (track->library->extract_fonts) {
678 ass_add_font(track->library, track->parser_priv->fontname,
679 (char *) buf, dsize);
680 buf = 0;
683 error_decode_font:
684 if (buf)
685 free(buf);
686 free(track->parser_priv->fontname);
687 free(track->parser_priv->fontdata);
688 track->parser_priv->fontname = 0;
689 track->parser_priv->fontdata = 0;
690 track->parser_priv->fontdata_size = 0;
691 track->parser_priv->fontdata_used = 0;
692 return 0;
695 static int process_fonts_line(ASS_Track *track, char *str)
697 int len;
699 if (!strncmp(str, "fontname:", 9)) {
700 char *p = str + 9;
701 skip_spaces(&p);
702 if (track->parser_priv->fontname) {
703 decode_font(track);
705 track->parser_priv->fontname = strdup(p);
706 ass_msg(track->library, MSGL_V, "Fontname: %s",
707 track->parser_priv->fontname);
708 return 0;
711 if (!track->parser_priv->fontname) {
712 ass_msg(track->library, MSGL_V, "Not understood: '%s'", str);
713 return 0;
716 len = strlen(str);
717 if (len > 80) {
718 ass_msg(track->library, MSGL_WARN, "Font line too long: %d, %s",
719 len, str);
720 return 0;
722 if (track->parser_priv->fontdata_used + len >
723 track->parser_priv->fontdata_size) {
724 track->parser_priv->fontdata_size += 100 * 1024;
725 track->parser_priv->fontdata =
726 realloc(track->parser_priv->fontdata,
727 track->parser_priv->fontdata_size);
729 memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used,
730 str, len);
731 track->parser_priv->fontdata_used += len;
733 return 0;
737 * \brief Parse a header line
738 * \param track track
739 * \param str string to parse, zero-terminated
741 static int process_line(ASS_Track *track, char *str)
743 if (!strncasecmp(str, "[Script Info]", 13)) {
744 track->parser_priv->state = PST_INFO;
745 } else if (!strncasecmp(str, "[V4 Styles]", 11)) {
746 track->parser_priv->state = PST_STYLES;
747 track->track_type = TRACK_TYPE_SSA;
748 } else if (!strncasecmp(str, "[V4+ Styles]", 12)) {
749 track->parser_priv->state = PST_STYLES;
750 track->track_type = TRACK_TYPE_ASS;
751 } else if (!strncasecmp(str, "[Events]", 8)) {
752 track->parser_priv->state = PST_EVENTS;
753 } else if (!strncasecmp(str, "[Fonts]", 7)) {
754 track->parser_priv->state = PST_FONTS;
755 } else {
756 switch (track->parser_priv->state) {
757 case PST_INFO:
758 process_info_line(track, str);
759 break;
760 case PST_STYLES:
761 process_styles_line(track, str);
762 break;
763 case PST_EVENTS:
764 process_events_line(track, str);
765 break;
766 case PST_FONTS:
767 process_fonts_line(track, str);
768 break;
769 default:
770 break;
774 // there is no explicit end-of-font marker in ssa/ass
775 if ((track->parser_priv->state != PST_FONTS)
776 && (track->parser_priv->fontname))
777 decode_font(track);
779 return 0;
782 static int process_text(ASS_Track *track, char *str)
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;
795 for (q = p; ((*q != '\0') && (*q != '\r') && (*q != '\n')); ++q) {
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;
806 return 0;
810 * \brief Process a chunk of subtitle stream data.
811 * \param track track
812 * \param data string to parse
813 * \param size length of data
815 void ass_process_data(ASS_Track *track, char *data, int size)
817 char *str = malloc(size + 1);
819 memcpy(str, data, size);
820 str[size] = '\0';
822 ass_msg(track->library, MSGL_V, "Event: %s", str);
823 process_text(track, str);
824 free(str);
828 * \brief Process CodecPrivate section of subtitle stream
829 * \param track track
830 * \param data string to parse
831 * \param size length of data
832 CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections
834 void ass_process_codec_private(ASS_Track *track, char *data, int size)
836 ass_process_data(track, data, size);
838 // probably an mkv produced by ancient mkvtoolnix
839 // such files don't have [Events] and Format: headers
840 if (!track->event_format)
841 event_format_fallback(track);
843 ass_process_force_style(track);
846 static int check_duplicate_event(ASS_Track *track, int ReadOrder)
848 int i;
849 for (i = 0; i < track->n_events - 1; ++i) // ignoring last event, it is the one we are comparing with
850 if (track->events[i].ReadOrder == ReadOrder)
851 return 1;
852 return 0;
856 * \brief Process a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary).
857 * \param track track
858 * \param data string to parse
859 * \param size length of data
860 * \param timecode starting time of the event (milliseconds)
861 * \param duration duration of the event (milliseconds)
863 void ass_process_chunk(ASS_Track *track, char *data, int size,
864 long long timecode, long long duration)
866 char *str;
867 int eid;
868 char *p;
869 char *token;
870 ASS_Event *event;
872 if (!track->event_format) {
873 ass_msg(track->library, MSGL_WARN, "Event format header missing");
874 return;
877 str = malloc(size + 1);
878 memcpy(str, data, size);
879 str[size] = '\0';
880 ass_msg(track->library, MSGL_V, "Event at %" PRId64 ", +%" PRId64 ": %s",
881 (int64_t) timecode, (int64_t) duration, str);
883 eid = ass_alloc_event(track);
884 event = track->events + eid;
886 p = str;
888 do {
889 NEXT(p, token);
890 event->ReadOrder = atoi(token);
891 if (check_duplicate_event(track, event->ReadOrder))
892 break;
894 NEXT(p, token);
895 event->Layer = atoi(token);
897 process_event_tail(track, event, p, 3);
899 event->Start = timecode;
900 event->Duration = duration;
902 free(str);
903 return;
904 // dump_events(tid);
905 } while (0);
906 // some error
907 ass_free_event(track, eid);
908 track->n_events--;
909 free(str);
912 #ifdef CONFIG_ICONV
913 /** \brief recode buffer to utf-8
914 * constraint: codepage != 0
915 * \param data pointer to text buffer
916 * \param size buffer size
917 * \return a pointer to recoded buffer, caller is responsible for freeing it
919 static char *sub_recode(ASS_Library *library, char *data, size_t size,
920 char *codepage)
922 iconv_t icdsc;
923 char *tocp = "UTF-8";
924 char *outbuf;
925 assert(codepage);
928 const char *cp_tmp = codepage;
929 #ifdef CONFIG_ENCA
930 char enca_lang[3], enca_fallback[100];
931 if (sscanf(codepage, "enca:%2s:%99s", enca_lang, enca_fallback) == 2
932 || sscanf(codepage, "ENCA:%2s:%99s", enca_lang,
933 enca_fallback) == 2) {
934 cp_tmp =
935 ass_guess_buffer_cp(library, (unsigned char *) data, size,
936 enca_lang, enca_fallback);
938 #endif
939 if ((icdsc = iconv_open(tocp, cp_tmp)) != (iconv_t) (-1)) {
940 ass_msg(library, MSGL_V, "Opened iconv descriptor");
941 } else
942 ass_msg(library, MSGL_ERR, "Error opening iconv descriptor");
946 size_t osize = size;
947 size_t ileft = size;
948 size_t oleft = size - 1;
949 char *ip;
950 char *op;
951 size_t rc;
952 int clear = 0;
954 outbuf = malloc(osize);
955 ip = data;
956 op = outbuf;
958 while (1) {
959 if (ileft)
960 rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
961 else { // clear the conversion state and leave
962 clear = 1;
963 rc = iconv(icdsc, NULL, NULL, &op, &oleft);
965 if (rc == (size_t) (-1)) {
966 if (errno == E2BIG) {
967 size_t offset = op - outbuf;
968 outbuf = (char *) realloc(outbuf, osize + size);
969 op = outbuf + offset;
970 osize += size;
971 oleft += size;
972 } else {
973 ass_msg(library, MSGL_WARN, "Error recoding file");
974 return NULL;
976 } else if (clear)
977 break;
979 outbuf[osize - oleft - 1] = 0;
982 if (icdsc != (iconv_t) (-1)) {
983 (void) iconv_close(icdsc);
984 icdsc = (iconv_t) (-1);
985 ass_msg(library, MSGL_V, "Closed iconv descriptor");
988 return outbuf;
990 #endif // ICONV
993 * \brief read file contents into newly allocated buffer
994 * \param fname file name
995 * \param bufsize out: file size
996 * \return pointer to file contents. Caller is responsible for its deallocation.
998 static char *read_file(ASS_Library *library, char *fname, size_t *bufsize)
1000 int res;
1001 long sz;
1002 long bytes_read;
1003 char *buf;
1005 FILE *fp = fopen(fname, "rb");
1006 if (!fp) {
1007 ass_msg(library, MSGL_WARN,
1008 "ass_read_file(%s): fopen failed", fname);
1009 return 0;
1011 res = fseek(fp, 0, SEEK_END);
1012 if (res == -1) {
1013 ass_msg(library, MSGL_WARN,
1014 "ass_read_file(%s): fseek failed", fname);
1015 fclose(fp);
1016 return 0;
1019 sz = ftell(fp);
1020 rewind(fp);
1022 if (sz > 10 * 1024 * 1024) {
1023 ass_msg(library, MSGL_INFO,
1024 "ass_read_file(%s): Refusing to load subtitles "
1025 "larger than 10MiB", fname);
1026 fclose(fp);
1027 return 0;
1030 ass_msg(library, MSGL_V, "File size: %ld", sz);
1032 buf = malloc(sz + 1);
1033 assert(buf);
1034 bytes_read = 0;
1035 do {
1036 res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
1037 if (res <= 0) {
1038 ass_msg(library, MSGL_INFO, "Read failed, %d: %s", errno,
1039 strerror(errno));
1040 fclose(fp);
1041 free(buf);
1042 return 0;
1044 bytes_read += res;
1045 } while (sz - bytes_read > 0);
1046 buf[sz] = '\0';
1047 fclose(fp);
1049 if (bufsize)
1050 *bufsize = sz;
1051 return buf;
1055 * \param buf pointer to subtitle text in utf-8
1057 static ASS_Track *parse_memory(ASS_Library *library, char *buf)
1059 ASS_Track *track;
1060 int i;
1062 track = ass_new_track(library);
1064 // process header
1065 process_text(track, buf);
1067 // external SSA/ASS subs does not have ReadOrder field
1068 for (i = 0; i < track->n_events; ++i)
1069 track->events[i].ReadOrder = i;
1071 // there is no explicit end-of-font marker in ssa/ass
1072 if (track->parser_priv->fontname)
1073 decode_font(track);
1075 if (track->track_type == TRACK_TYPE_UNKNOWN) {
1076 ass_free_track(track);
1077 return 0;
1080 ass_process_force_style(track);
1082 return track;
1086 * \brief Read subtitles from memory.
1087 * \param library libass library object
1088 * \param buf pointer to subtitles text
1089 * \param bufsize size of buffer
1090 * \param codepage recode buffer contents from given codepage
1091 * \return newly allocated track
1093 ASS_Track *ass_read_memory(ASS_Library *library, char *buf,
1094 size_t bufsize, char *codepage)
1096 ASS_Track *track;
1097 int need_free = 0;
1099 if (!buf)
1100 return 0;
1102 #ifdef CONFIG_ICONV
1103 if (codepage) {
1104 buf = sub_recode(library, buf, bufsize, codepage);
1105 if (!buf)
1106 return 0;
1107 else
1108 need_free = 1;
1110 #endif
1111 track = parse_memory(library, buf);
1112 if (need_free)
1113 free(buf);
1114 if (!track)
1115 return 0;
1117 ass_msg(library, MSGL_INFO, "Added subtitle file: "
1118 "<memory> (%d styles, %d events)",
1119 track->n_styles, track->n_events);
1120 return track;
1123 static char *read_file_recode(ASS_Library *library, char *fname,
1124 char *codepage, size_t *size)
1126 char *buf;
1127 size_t bufsize;
1129 buf = read_file(library, fname, &bufsize);
1130 if (!buf)
1131 return 0;
1132 #ifdef CONFIG_ICONV
1133 if (codepage) {
1134 char *tmpbuf = sub_recode(library, buf, bufsize, codepage);
1135 free(buf);
1136 buf = tmpbuf;
1138 if (!buf)
1139 return 0;
1140 #endif
1141 *size = bufsize;
1142 return buf;
1146 * \brief Read subtitles from file.
1147 * \param library libass library object
1148 * \param fname file name
1149 * \param codepage recode buffer contents from given codepage
1150 * \return newly allocated track
1152 ASS_Track *ass_read_file(ASS_Library *library, char *fname,
1153 char *codepage)
1155 char *buf;
1156 ASS_Track *track;
1157 size_t bufsize;
1159 buf = read_file_recode(library, fname, codepage, &bufsize);
1160 if (!buf)
1161 return 0;
1162 track = parse_memory(library, buf);
1163 free(buf);
1164 if (!track)
1165 return 0;
1167 track->name = strdup(fname);
1169 ass_msg(library, MSGL_INFO,
1170 "Added subtitle file: '%s' (%d styles, %d events)",
1171 fname, track->n_styles, track->n_events);
1173 return track;
1177 * \brief read styles from file into already initialized track
1179 int ass_read_styles(ASS_Track *track, char *fname, char *codepage)
1181 char *buf;
1182 ParserState old_state;
1183 size_t sz;
1185 buf = read_file(track->library, fname, &sz);
1186 if (!buf)
1187 return 1;
1188 #ifdef CONFIG_ICONV
1189 if (codepage) {
1190 char *tmpbuf;
1191 tmpbuf = sub_recode(track->library, buf, sz, codepage);
1192 free(buf);
1193 buf = tmpbuf;
1195 if (!buf)
1196 return 0;
1197 #endif
1199 old_state = track->parser_priv->state;
1200 track->parser_priv->state = PST_STYLES;
1201 process_text(track, buf);
1202 track->parser_priv->state = old_state;
1204 return 0;
1207 long long ass_step_sub(ASS_Track *track, long long now, int movement)
1209 int i;
1211 if (movement == 0)
1212 return 0;
1213 if (track->n_events == 0)
1214 return 0;
1216 if (movement < 0)
1217 for (i = 0;
1218 (i < track->n_events)
1220 ((long long) (track->events[i].Start +
1221 track->events[i].Duration) <= now); ++i) {
1222 } else
1223 for (i = track->n_events - 1;
1224 (i >= 0) && ((long long) (track->events[i].Start) > now);
1225 --i) {
1228 // -1 and n_events are ok
1229 assert(i >= -1);
1230 assert(i <= track->n_events);
1231 i += movement;
1232 if (i < 0)
1233 i = 0;
1234 if (i >= track->n_events)
1235 i = track->n_events - 1;
1236 return ((long long) track->events[i].Start) - now;
1239 ASS_Track *ass_new_track(ASS_Library *library)
1241 ASS_Track *track = calloc(1, sizeof(ASS_Track));
1242 track->library = library;
1243 track->ScaledBorderAndShadow = 1;
1244 track->parser_priv = calloc(1, sizeof(ASS_ParserPriv));
1245 return track;