Relicense to ISC
[libass.git] / libass / ass.c
blob368377251e486cda276ef96f7374440c87d16320
1 /*
2 * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
4 * This file is part of libass.
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.
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.
19 #include "config.h"
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 #include <unistd.h>
30 #include <inttypes.h>
32 #ifdef CONFIG_ICONV
33 #include <iconv.h>
34 #endif
36 #include "ass.h"
37 #include "ass_utils.h"
38 #include "ass_library.h"
40 #define ass_atof(STR) (ass_strtod((STR),NULL))
42 typedef enum {
43 PST_UNKNOWN = 0,
44 PST_INFO,
45 PST_STYLES,
46 PST_EVENTS,
47 PST_FONTS
48 } ParserState;
50 struct parser_priv {
51 ParserState state;
52 char *fontname;
53 char *fontdata;
54 int fontdata_size;
55 int fontdata_used;
58 #define ASS_STYLES_ALLOC 20
59 #define ASS_EVENTS_ALLOC 200
61 void ass_free_track(ASS_Track *track)
63 int i;
65 if (track->parser_priv) {
66 free(track->parser_priv->fontname);
67 free(track->parser_priv->fontdata);
68 free(track->parser_priv);
70 free(track->style_format);
71 free(track->event_format);
72 if (track->styles) {
73 for (i = 0; i < track->n_styles; ++i)
74 ass_free_style(track, i);
76 free(track->styles);
77 if (track->events) {
78 for (i = 0; i < track->n_events; ++i)
79 ass_free_event(track, i);
81 free(track->events);
82 free(track->name);
83 free(track);
86 /// \brief Allocate a new style struct
87 /// \param track track
88 /// \return style id
89 int ass_alloc_style(ASS_Track *track)
91 int sid;
93 assert(track->n_styles <= track->max_styles);
95 if (track->n_styles == track->max_styles) {
96 track->max_styles += ASS_STYLES_ALLOC;
97 track->styles =
98 (ASS_Style *) realloc(track->styles,
99 sizeof(ASS_Style) *
100 track->max_styles);
103 sid = track->n_styles++;
104 memset(track->styles + sid, 0, sizeof(ASS_Style));
105 return sid;
108 /// \brief Allocate a new event struct
109 /// \param track track
110 /// \return event id
111 int ass_alloc_event(ASS_Track *track)
113 int eid;
115 assert(track->n_events <= track->max_events);
117 if (track->n_events == track->max_events) {
118 track->max_events += ASS_EVENTS_ALLOC;
119 track->events =
120 (ASS_Event *) realloc(track->events,
121 sizeof(ASS_Event) *
122 track->max_events);
125 eid = track->n_events++;
126 memset(track->events + eid, 0, sizeof(ASS_Event));
127 return eid;
130 void ass_free_event(ASS_Track *track, int eid)
132 ASS_Event *event = track->events + eid;
134 free(event->Name);
135 free(event->Effect);
136 free(event->Text);
137 free(event->render_priv);
140 void ass_free_style(ASS_Track *track, int sid)
142 ASS_Style *style = track->styles + sid;
144 free(style->Name);
145 free(style->FontName);
148 // ==============================================================================================
150 static void skip_spaces(char **str)
152 char *p = *str;
153 while ((*p == ' ') || (*p == '\t'))
154 ++p;
155 *str = p;
158 static void rskip_spaces(char **str, char *limit)
160 char *p = *str;
161 while ((p >= limit) && ((*p == ' ') || (*p == '\t')))
162 --p;
163 *str = p;
167 * \brief find style by name
168 * \param track track
169 * \param name style name
170 * \return index in track->styles
171 * Returnes 0 if no styles found => expects at least 1 style.
172 * Parsing code always adds "Default" style in the end.
174 static int lookup_style(ASS_Track *track, char *name)
176 int i;
177 if (*name == '*')
178 ++name; // FIXME: what does '*' really mean ?
179 for (i = track->n_styles - 1; i >= 0; --i) {
180 // FIXME: mb strcasecmp ?
181 if (strcmp(track->styles[i].Name, name) == 0)
182 return i;
184 i = track->default_style;
185 ass_msg(track->library, MSGL_WARN,
186 "[%p]: Warning: no style named '%s' found, using '%s'",
187 track, name, track->styles[i].Name);
188 return i; // use the first style
191 static uint32_t string2color(ASS_Library *library, char *p)
193 uint32_t tmp;
194 (void) strtocolor(library, &p, &tmp, 0);
195 return tmp;
198 static long long string2timecode(ASS_Library *library, char *p)
200 unsigned h, m, s, ms;
201 long long tm;
202 int res = sscanf(p, "%1d:%2d:%2d.%2d", &h, &m, &s, &ms);
203 if (res < 4) {
204 ass_msg(library, MSGL_WARN, "Bad timestamp");
205 return 0;
207 tm = ((h * 60 + m) * 60 + s) * 1000 + ms * 10;
208 return tm;
212 * \brief converts numpad-style align to align.
214 static int numpad2align(int val)
216 int res, v;
217 v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment
218 if (v != 0)
219 v = 3 - v;
220 res = ((val - 1) % 3) + 1; // horizontal alignment
221 res += v * 4;
222 return res;
225 #define NEXT(str,token) \
226 token = next_token(&str); \
227 if (!token) break;
229 #define ANYVAL(name,func) \
230 } else if (strcasecmp(tname, #name) == 0) { \
231 target->name = func(token); \
232 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
234 #define STRVAL(name) \
235 } else if (strcasecmp(tname, #name) == 0) { \
236 if (target->name != NULL) free(target->name); \
237 target->name = strdup(token); \
238 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
240 #define COLORVAL(name) \
241 } else if (strcasecmp(tname, #name) == 0) { \
242 target->name = string2color(track->library, token); \
243 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
245 #define INTVAL(name) ANYVAL(name,atoi)
246 #define FPVAL(name) ANYVAL(name,ass_atof)
247 #define TIMEVAL(name) \
248 } else if (strcasecmp(tname, #name) == 0) { \
249 target->name = string2timecode(track->library, token); \
250 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
252 #define STYLEVAL(name) \
253 } else if (strcasecmp(tname, #name) == 0) { \
254 target->name = lookup_style(track, token); \
255 ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
257 #define ALIAS(alias,name) \
258 if (strcasecmp(tname, #alias) == 0) {tname = #name;}
260 static char *next_token(char **str)
262 char *p = *str;
263 char *start;
264 skip_spaces(&p);
265 if (*p == '\0') {
266 *str = p;
267 return 0;
269 start = p; // start of the token
270 for (; (*p != '\0') && (*p != ','); ++p) {
272 if (*p == '\0') {
273 *str = p; // eos found, str will point to '\0' at exit
274 } else {
275 *p = '\0';
276 *str = p + 1; // ',' found, str will point to the next char (beginning of the next token)
278 --p; // end of current token
279 rskip_spaces(&p, start);
280 if (p < start)
281 p = start; // empty token
282 else
283 ++p; // the first space character, or '\0'
284 *p = '\0';
285 return start;
289 * \brief Parse the tail of Dialogue line
290 * \param track track
291 * \param event parsed data goes here
292 * \param str string to parse, zero-terminated
293 * \param n_ignored number of format options to skip at the beginning
295 static int process_event_tail(ASS_Track *track, ASS_Event *event,
296 char *str, int n_ignored)
298 char *token;
299 char *tname;
300 char *p = str;
301 int i;
302 ASS_Event *target = event;
304 char *format = strdup(track->event_format);
305 char *q = format; // format scanning pointer
307 if (track->n_styles == 0) {
308 // add "Default" style to the end
309 // will be used if track does not contain a default style (or even does not contain styles at all)
310 int sid = ass_alloc_style(track);
311 track->styles[sid].Name = strdup("Default");
312 track->styles[sid].FontName = strdup("Arial");
315 for (i = 0; i < n_ignored; ++i) {
316 NEXT(q, tname);
319 while (1) {
320 NEXT(q, tname);
321 if (strcasecmp(tname, "Text") == 0) {
322 char *last;
323 event->Text = strdup(p);
324 if (*event->Text != 0) {
325 last = event->Text + strlen(event->Text) - 1;
326 if (last >= event->Text && *last == '\r')
327 *last = 0;
329 ass_msg(track->library, MSGL_DBG2, "Text = %s", event->Text);
330 event->Duration -= event->Start;
331 free(format);
332 return 0; // "Text" is always the last
334 NEXT(p, token);
336 ALIAS(End, Duration) // temporarily store end timecode in event->Duration
337 if (0) { // cool ;)
338 INTVAL(Layer)
339 STYLEVAL(Style)
340 STRVAL(Name)
341 STRVAL(Effect)
342 INTVAL(MarginL)
343 INTVAL(MarginR)
344 INTVAL(MarginV)
345 TIMEVAL(Start)
346 TIMEVAL(Duration)
349 free(format);
350 return 1;
354 * \brief Parse command line style overrides (--ass-force-style option)
355 * \param track track to apply overrides to
356 * The format for overrides is [StyleName.]Field=Value
358 void ass_process_force_style(ASS_Track *track)
360 char **fs, *eq, *dt, *style, *tname, *token;
361 ASS_Style *target;
362 int sid;
363 char **list = track->library->style_overrides;
365 if (!list)
366 return;
368 for (fs = list; *fs; ++fs) {
369 eq = strrchr(*fs, '=');
370 if (!eq)
371 continue;
372 *eq = '\0';
373 token = eq + 1;
375 if (!strcasecmp(*fs, "PlayResX"))
376 track->PlayResX = atoi(token);
377 else if (!strcasecmp(*fs, "PlayResY"))
378 track->PlayResY = atoi(token);
379 else if (!strcasecmp(*fs, "Timer"))
380 track->Timer = ass_atof(token);
381 else if (!strcasecmp(*fs, "WrapStyle"))
382 track->WrapStyle = atoi(token);
383 else if (!strcasecmp(*fs, "ScaledBorderAndShadow"))
384 track->ScaledBorderAndShadow = parse_bool(token);
385 else if (!strcasecmp(*fs, "Kerning"))
386 track->Kerning = parse_bool(token);
388 dt = strrchr(*fs, '.');
389 if (dt) {
390 *dt = '\0';
391 style = *fs;
392 tname = dt + 1;
393 } else {
394 style = NULL;
395 tname = *fs;
397 for (sid = 0; sid < track->n_styles; ++sid) {
398 if (style == NULL
399 || strcasecmp(track->styles[sid].Name, style) == 0) {
400 target = track->styles + sid;
401 if (0) {
402 STRVAL(FontName)
403 COLORVAL(PrimaryColour)
404 COLORVAL(SecondaryColour)
405 COLORVAL(OutlineColour)
406 COLORVAL(BackColour)
407 FPVAL(FontSize)
408 INTVAL(Bold)
409 INTVAL(Italic)
410 INTVAL(Underline)
411 INTVAL(StrikeOut)
412 FPVAL(Spacing)
413 INTVAL(Angle)
414 INTVAL(BorderStyle)
415 INTVAL(Alignment)
416 INTVAL(MarginL)
417 INTVAL(MarginR)
418 INTVAL(MarginV)
419 INTVAL(Encoding)
420 FPVAL(ScaleX)
421 FPVAL(ScaleY)
422 FPVAL(Outline)
423 FPVAL(Shadow)
427 *eq = '=';
428 if (dt)
429 *dt = '.';
434 * \brief Parse the Style line
435 * \param track track
436 * \param str string to parse, zero-terminated
437 * Allocates a new style struct.
439 static int process_style(ASS_Track *track, char *str)
442 char *token;
443 char *tname;
444 char *p = str;
445 char *format;
446 char *q; // format scanning pointer
447 int sid;
448 ASS_Style *style;
449 ASS_Style *target;
451 if (!track->style_format) {
452 // no style format header
453 // probably an ancient script version
454 if (track->track_type == TRACK_TYPE_SSA)
455 track->style_format =
456 strdup
457 ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
458 "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline,"
459 "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
460 else
461 track->style_format =
462 strdup
463 ("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
464 "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut,"
465 "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow,"
466 "Alignment, MarginL, MarginR, MarginV, Encoding");
469 q = format = strdup(track->style_format);
471 ass_msg(track->library, MSGL_V, "[%p] Style: %s", track, str);
473 sid = ass_alloc_style(track);
475 style = track->styles + sid;
476 target = style;
478 // fill style with some default values
479 style->ScaleX = 100.;
480 style->ScaleY = 100.;
482 while (1) {
483 NEXT(q, tname);
484 NEXT(p, token);
486 if (0) { // cool ;)
487 STRVAL(Name)
488 if ((strcmp(target->Name, "Default") == 0)
489 || (strcmp(target->Name, "*Default") == 0))
490 track->default_style = sid;
491 STRVAL(FontName)
492 COLORVAL(PrimaryColour)
493 COLORVAL(SecondaryColour)
494 COLORVAL(OutlineColour) // TertiaryColor
495 COLORVAL(BackColour)
496 // SSA uses BackColour for both outline and shadow
497 // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
498 if (track->track_type == TRACK_TYPE_SSA)
499 target->OutlineColour = target->BackColour;
500 FPVAL(FontSize)
501 INTVAL(Bold)
502 INTVAL(Italic)
503 INTVAL(Underline)
504 INTVAL(StrikeOut)
505 FPVAL(Spacing)
506 INTVAL(Angle)
507 INTVAL(BorderStyle)
508 INTVAL(Alignment)
509 if (track->track_type == TRACK_TYPE_ASS)
510 target->Alignment = numpad2align(target->Alignment);
511 INTVAL(MarginL)
512 INTVAL(MarginR)
513 INTVAL(MarginV)
514 INTVAL(Encoding)
515 FPVAL(ScaleX)
516 FPVAL(ScaleY)
517 FPVAL(Outline)
518 FPVAL(Shadow)
521 style->ScaleX /= 100.;
522 style->ScaleY /= 100.;
523 style->Bold = !!style->Bold;
524 style->Italic = !!style->Italic;
525 style->Underline = !!style->Underline;
526 if (!style->Name)
527 style->Name = strdup("Default");
528 if (!style->FontName)
529 style->FontName = strdup("Arial");
530 free(format);
531 return 0;
535 static int process_styles_line(ASS_Track *track, char *str)
537 if (!strncmp(str, "Format:", 7)) {
538 char *p = str + 7;
539 skip_spaces(&p);
540 track->style_format = strdup(p);
541 ass_msg(track->library, MSGL_DBG2, "Style format: %s",
542 track->style_format);
543 } else if (!strncmp(str, "Style:", 6)) {
544 char *p = str + 6;
545 skip_spaces(&p);
546 process_style(track, p);
548 return 0;
551 static int process_info_line(ASS_Track *track, char *str)
553 if (!strncmp(str, "PlayResX:", 9)) {
554 track->PlayResX = atoi(str + 9);
555 } else if (!strncmp(str, "PlayResY:", 9)) {
556 track->PlayResY = atoi(str + 9);
557 } else if (!strncmp(str, "Timer:", 6)) {
558 track->Timer = ass_atof(str + 6);
559 } else if (!strncmp(str, "WrapStyle:", 10)) {
560 track->WrapStyle = atoi(str + 10);
561 } else if (!strncmp(str, "ScaledBorderAndShadow:", 22)) {
562 track->ScaledBorderAndShadow = parse_bool(str + 22);
563 } else if (!strncmp(str, "Kerning:", 8)) {
564 track->Kerning = parse_bool(str + 8);
566 return 0;
569 static void event_format_fallback(ASS_Track *track)
571 track->parser_priv->state = PST_EVENTS;
572 if (track->track_type == TRACK_TYPE_SSA)
573 track->event_format = strdup("Format: Marked, Start, End, Style, "
574 "Name, MarginL, MarginR, MarginV, Effect, Text");
575 else
576 track->event_format = strdup("Format: Layer, Start, End, Style, "
577 "Actor, MarginL, MarginR, MarginV, Effect, Text");
578 ass_msg(track->library, MSGL_V,
579 "No event format found, using fallback");
582 static int process_events_line(ASS_Track *track, char *str)
584 if (!strncmp(str, "Format:", 7)) {
585 char *p = str + 7;
586 skip_spaces(&p);
587 free(track->event_format);
588 track->event_format = strdup(p);
589 ass_msg(track->library, MSGL_DBG2, "Event format: %s", track->event_format);
590 } else if (!strncmp(str, "Dialogue:", 9)) {
591 // This should never be reached for embedded subtitles.
592 // They have slightly different format and are parsed in ass_process_chunk,
593 // called directly from demuxer
594 int eid;
595 ASS_Event *event;
597 str += 9;
598 skip_spaces(&str);
600 eid = ass_alloc_event(track);
601 event = track->events + eid;
603 // We can't parse events with event_format
604 if (!track->event_format)
605 event_format_fallback(track);
607 process_event_tail(track, event, str, 0);
608 } else {
609 ass_msg(track->library, MSGL_V, "Not understood: '%.30s'", str);
611 return 0;
614 // Copied from mkvtoolnix
615 static unsigned char *decode_chars(unsigned char c1, unsigned char c2,
616 unsigned char c3, unsigned char c4,
617 unsigned char *dst, int cnt)
619 uint32_t value;
620 unsigned char bytes[3];
621 int i;
623 value =
624 ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 -
625 33);
626 bytes[2] = value & 0xff;
627 bytes[1] = (value & 0xff00) >> 8;
628 bytes[0] = (value & 0xff0000) >> 16;
630 for (i = 0; i < cnt; ++i)
631 *dst++ = bytes[i];
632 return dst;
635 static int decode_font(ASS_Track *track)
637 unsigned char *p;
638 unsigned char *q;
639 int i;
640 int size; // original size
641 int dsize; // decoded size
642 unsigned char *buf = 0;
644 ass_msg(track->library, MSGL_V, "Font: %d bytes encoded data",
645 track->parser_priv->fontdata_used);
646 size = track->parser_priv->fontdata_used;
647 if (size % 4 == 1) {
648 ass_msg(track->library, MSGL_ERR, "Bad encoded data size");
649 goto error_decode_font;
651 buf = malloc(size / 4 * 3 + 2);
652 q = buf;
653 for (i = 0, p = (unsigned char *) track->parser_priv->fontdata;
654 i < size / 4; i++, p += 4) {
655 q = decode_chars(p[0], p[1], p[2], p[3], q, 3);
657 if (size % 4 == 2) {
658 q = decode_chars(p[0], p[1], 0, 0, q, 1);
659 } else if (size % 4 == 3) {
660 q = decode_chars(p[0], p[1], p[2], 0, q, 2);
662 dsize = q - buf;
663 assert(dsize <= size / 4 * 3 + 2);
665 if (track->library->extract_fonts) {
666 ass_add_font(track->library, track->parser_priv->fontname,
667 (char *) buf, dsize);
670 error_decode_font:
671 free(buf);
672 free(track->parser_priv->fontname);
673 free(track->parser_priv->fontdata);
674 track->parser_priv->fontname = 0;
675 track->parser_priv->fontdata = 0;
676 track->parser_priv->fontdata_size = 0;
677 track->parser_priv->fontdata_used = 0;
678 return 0;
681 static int process_fonts_line(ASS_Track *track, char *str)
683 int len;
685 if (!strncmp(str, "fontname:", 9)) {
686 char *p = str + 9;
687 skip_spaces(&p);
688 if (track->parser_priv->fontname) {
689 decode_font(track);
691 track->parser_priv->fontname = strdup(p);
692 ass_msg(track->library, MSGL_V, "Fontname: %s",
693 track->parser_priv->fontname);
694 return 0;
697 if (!track->parser_priv->fontname) {
698 ass_msg(track->library, MSGL_V, "Not understood: '%s'", str);
699 return 0;
702 len = strlen(str);
703 if (len > 80) {
704 ass_msg(track->library, MSGL_WARN, "Font line too long: %d, %s",
705 len, str);
706 return 0;
708 if (track->parser_priv->fontdata_used + len >
709 track->parser_priv->fontdata_size) {
710 track->parser_priv->fontdata_size += 100 * 1024;
711 track->parser_priv->fontdata =
712 realloc(track->parser_priv->fontdata,
713 track->parser_priv->fontdata_size);
715 memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used,
716 str, len);
717 track->parser_priv->fontdata_used += len;
719 return 0;
723 * \brief Parse a header line
724 * \param track track
725 * \param str string to parse, zero-terminated
727 static int process_line(ASS_Track *track, char *str)
729 if (!strncasecmp(str, "[Script Info]", 13)) {
730 track->parser_priv->state = PST_INFO;
731 } else if (!strncasecmp(str, "[V4 Styles]", 11)) {
732 track->parser_priv->state = PST_STYLES;
733 track->track_type = TRACK_TYPE_SSA;
734 } else if (!strncasecmp(str, "[V4+ Styles]", 12)) {
735 track->parser_priv->state = PST_STYLES;
736 track->track_type = TRACK_TYPE_ASS;
737 } else if (!strncasecmp(str, "[Events]", 8)) {
738 track->parser_priv->state = PST_EVENTS;
739 } else if (!strncasecmp(str, "[Fonts]", 7)) {
740 track->parser_priv->state = PST_FONTS;
741 } else {
742 switch (track->parser_priv->state) {
743 case PST_INFO:
744 process_info_line(track, str);
745 break;
746 case PST_STYLES:
747 process_styles_line(track, str);
748 break;
749 case PST_EVENTS:
750 process_events_line(track, str);
751 break;
752 case PST_FONTS:
753 process_fonts_line(track, str);
754 break;
755 default:
756 break;
760 // there is no explicit end-of-font marker in ssa/ass
761 if ((track->parser_priv->state != PST_FONTS)
762 && (track->parser_priv->fontname))
763 decode_font(track);
765 return 0;
768 static int process_text(ASS_Track *track, char *str)
770 char *p = str;
771 while (1) {
772 char *q;
773 while (1) {
774 if ((*p == '\r') || (*p == '\n'))
775 ++p;
776 else if (p[0] == '\xef' && p[1] == '\xbb' && p[2] == '\xbf')
777 p += 3; // U+FFFE (BOM)
778 else
779 break;
781 for (q = p; ((*q != '\0') && (*q != '\r') && (*q != '\n')); ++q) {
783 if (q == p)
784 break;
785 if (*q != '\0')
786 *(q++) = '\0';
787 process_line(track, p);
788 if (*q == '\0')
789 break;
790 p = q;
792 return 0;
796 * \brief Process a chunk of subtitle stream data.
797 * \param track track
798 * \param data string to parse
799 * \param size length of data
801 void ass_process_data(ASS_Track *track, char *data, int size)
803 char *str = malloc(size + 1);
805 memcpy(str, data, size);
806 str[size] = '\0';
808 ass_msg(track->library, MSGL_V, "Event: %s", str);
809 process_text(track, str);
810 free(str);
814 * \brief Process CodecPrivate section of subtitle stream
815 * \param track track
816 * \param data string to parse
817 * \param size length of data
818 CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections
820 void ass_process_codec_private(ASS_Track *track, char *data, int size)
822 ass_process_data(track, data, size);
824 // probably an mkv produced by ancient mkvtoolnix
825 // such files don't have [Events] and Format: headers
826 if (!track->event_format)
827 event_format_fallback(track);
829 ass_process_force_style(track);
832 static int check_duplicate_event(ASS_Track *track, int ReadOrder)
834 int i;
835 for (i = 0; i < track->n_events - 1; ++i) // ignoring last event, it is the one we are comparing with
836 if (track->events[i].ReadOrder == ReadOrder)
837 return 1;
838 return 0;
842 * \brief Process a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary).
843 * \param track track
844 * \param data string to parse
845 * \param size length of data
846 * \param timecode starting time of the event (milliseconds)
847 * \param duration duration of the event (milliseconds)
849 void ass_process_chunk(ASS_Track *track, char *data, int size,
850 long long timecode, long long duration)
852 char *str;
853 int eid;
854 char *p;
855 char *token;
856 ASS_Event *event;
858 if (!track->event_format) {
859 ass_msg(track->library, MSGL_WARN, "Event format header missing");
860 return;
863 str = malloc(size + 1);
864 memcpy(str, data, size);
865 str[size] = '\0';
866 ass_msg(track->library, MSGL_V, "Event at %" PRId64 ", +%" PRId64 ": %s",
867 (int64_t) timecode, (int64_t) duration, str);
869 eid = ass_alloc_event(track);
870 event = track->events + eid;
872 p = str;
874 do {
875 NEXT(p, token);
876 event->ReadOrder = atoi(token);
877 if (check_duplicate_event(track, event->ReadOrder))
878 break;
880 NEXT(p, token);
881 event->Layer = atoi(token);
883 process_event_tail(track, event, p, 3);
885 event->Start = timecode;
886 event->Duration = duration;
888 free(str);
889 return;
890 // dump_events(tid);
891 } while (0);
892 // some error
893 ass_free_event(track, eid);
894 track->n_events--;
895 free(str);
899 * \brief Flush buffered events.
900 * \param track track
902 void ass_flush_events(ASS_Track *track)
904 if (track->events) {
905 int eid;
906 for (eid = 0; eid < track->n_events; eid++)
907 ass_free_event(track, eid);
908 track->n_events = 0;
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 ass_msg(library, MSGL_V, "File size: %ld", sz);
1024 buf = malloc(sz + 1);
1025 assert(buf);
1026 bytes_read = 0;
1027 do {
1028 res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
1029 if (res <= 0) {
1030 ass_msg(library, MSGL_INFO, "Read failed, %d: %s", errno,
1031 strerror(errno));
1032 fclose(fp);
1033 free(buf);
1034 return 0;
1036 bytes_read += res;
1037 } while (sz - bytes_read > 0);
1038 buf[sz] = '\0';
1039 fclose(fp);
1041 if (bufsize)
1042 *bufsize = sz;
1043 return buf;
1047 * \param buf pointer to subtitle text in utf-8
1049 static ASS_Track *parse_memory(ASS_Library *library, char *buf)
1051 ASS_Track *track;
1052 int i;
1054 track = ass_new_track(library);
1056 // process header
1057 process_text(track, buf);
1059 // external SSA/ASS subs does not have ReadOrder field
1060 for (i = 0; i < track->n_events; ++i)
1061 track->events[i].ReadOrder = i;
1063 // there is no explicit end-of-font marker in ssa/ass
1064 if (track->parser_priv->fontname)
1065 decode_font(track);
1067 if (track->track_type == TRACK_TYPE_UNKNOWN) {
1068 ass_free_track(track);
1069 return 0;
1072 ass_process_force_style(track);
1074 return track;
1078 * \brief Read subtitles from memory.
1079 * \param library libass library object
1080 * \param buf pointer to subtitles text
1081 * \param bufsize size of buffer
1082 * \param codepage recode buffer contents from given codepage
1083 * \return newly allocated track
1085 ASS_Track *ass_read_memory(ASS_Library *library, char *buf,
1086 size_t bufsize, char *codepage)
1088 ASS_Track *track;
1089 int need_free = 0;
1091 if (!buf)
1092 return 0;
1094 #ifdef CONFIG_ICONV
1095 if (codepage) {
1096 buf = sub_recode(library, buf, bufsize, codepage);
1097 if (!buf)
1098 return 0;
1099 else
1100 need_free = 1;
1102 #endif
1103 track = parse_memory(library, buf);
1104 if (need_free)
1105 free(buf);
1106 if (!track)
1107 return 0;
1109 ass_msg(library, MSGL_INFO, "Added subtitle file: "
1110 "<memory> (%d styles, %d events)",
1111 track->n_styles, track->n_events);
1112 return track;
1115 static char *read_file_recode(ASS_Library *library, char *fname,
1116 char *codepage, size_t *size)
1118 char *buf;
1119 size_t bufsize;
1121 buf = read_file(library, fname, &bufsize);
1122 if (!buf)
1123 return 0;
1124 #ifdef CONFIG_ICONV
1125 if (codepage) {
1126 char *tmpbuf = sub_recode(library, buf, bufsize, codepage);
1127 free(buf);
1128 buf = tmpbuf;
1130 if (!buf)
1131 return 0;
1132 #endif
1133 *size = bufsize;
1134 return buf;
1138 * \brief Read subtitles from file.
1139 * \param library libass library object
1140 * \param fname file name
1141 * \param codepage recode buffer contents from given codepage
1142 * \return newly allocated track
1144 ASS_Track *ass_read_file(ASS_Library *library, char *fname,
1145 char *codepage)
1147 char *buf;
1148 ASS_Track *track;
1149 size_t bufsize;
1151 buf = read_file_recode(library, fname, codepage, &bufsize);
1152 if (!buf)
1153 return 0;
1154 track = parse_memory(library, buf);
1155 free(buf);
1156 if (!track)
1157 return 0;
1159 track->name = strdup(fname);
1161 ass_msg(library, MSGL_INFO,
1162 "Added subtitle file: '%s' (%d styles, %d events)",
1163 fname, track->n_styles, track->n_events);
1165 return track;
1169 * \brief read styles from file into already initialized track
1171 int ass_read_styles(ASS_Track *track, char *fname, char *codepage)
1173 char *buf;
1174 ParserState old_state;
1175 size_t sz;
1177 buf = read_file(track->library, fname, &sz);
1178 if (!buf)
1179 return 1;
1180 #ifdef CONFIG_ICONV
1181 if (codepage) {
1182 char *tmpbuf;
1183 tmpbuf = sub_recode(track->library, buf, sz, codepage);
1184 free(buf);
1185 buf = tmpbuf;
1187 if (!buf)
1188 return 0;
1189 #endif
1191 old_state = track->parser_priv->state;
1192 track->parser_priv->state = PST_STYLES;
1193 process_text(track, buf);
1194 track->parser_priv->state = old_state;
1196 return 0;
1199 long long ass_step_sub(ASS_Track *track, long long now, int movement)
1201 int i;
1203 if (movement == 0)
1204 return 0;
1205 if (track->n_events == 0)
1206 return 0;
1208 if (movement < 0)
1209 for (i = 0;
1210 (i < track->n_events)
1212 ((long long) (track->events[i].Start +
1213 track->events[i].Duration) <= now); ++i) {
1214 } else
1215 for (i = track->n_events - 1;
1216 (i >= 0) && ((long long) (track->events[i].Start) > now);
1217 --i) {
1220 // -1 and n_events are ok
1221 assert(i >= -1);
1222 assert(i <= track->n_events);
1223 i += movement;
1224 if (i < 0)
1225 i = 0;
1226 if (i >= track->n_events)
1227 i = track->n_events - 1;
1228 return ((long long) track->events[i].Start) - now;
1231 ASS_Track *ass_new_track(ASS_Library *library)
1233 ASS_Track *track = calloc(1, sizeof(ASS_Track));
1234 track->library = library;
1235 track->ScaledBorderAndShadow = 1;
1236 track->parser_priv = calloc(1, sizeof(ASS_ParserPriv));
1237 return track;