Simplify: use &= instead of a = b & a;
[mplayer/greg.git] / libass / ass.c
blob370063aacf8aa772f98b28a431fb8de54bac7ec8
1 // -*- c-basic-offset: 8; indent-tabs-mode: t -*-
2 // vim:ts=8:sw=8:noet:ai:
3 /*
4 * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
6 * This file is part of libass.
8 * libass is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * libass is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License along
19 * with libass; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "config.h"
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <assert.h>
29 #include <errno.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <unistd.h>
33 #include <inttypes.h>
35 #ifdef CONFIG_ICONV
36 #include <iconv.h>
37 #endif
39 #include "ass.h"
40 #include "ass_utils.h"
41 #include "ass_library.h"
42 #include "mputils.h"
44 typedef enum {PST_UNKNOWN = 0, PST_INFO, PST_STYLES, PST_EVENTS, PST_FONTS} parser_state_t;
46 struct parser_priv_s {
47 parser_state_t state;
48 char* fontname;
49 char* fontdata;
50 int fontdata_size;
51 int fontdata_used;
54 #define ASS_STYLES_ALLOC 20
55 #define ASS_EVENTS_ALLOC 200
57 void ass_free_track(ass_track_t* track) {
58 int i;
60 if (track->parser_priv) {
61 if (track->parser_priv->fontname)
62 free(track->parser_priv->fontname);
63 if (track->parser_priv->fontdata)
64 free(track->parser_priv->fontdata);
65 free(track->parser_priv);
67 if (track->style_format)
68 free(track->style_format);
69 if (track->event_format)
70 free(track->event_format);
71 if (track->styles) {
72 for (i = 0; i < track->n_styles; ++i)
73 ass_free_style(track, i);
74 free(track->styles);
76 if (track->events) {
77 for (i = 0; i < track->n_events; ++i)
78 ass_free_event(track, i);
79 free(track->events);
83 /// \brief Allocate a new style struct
84 /// \param track track
85 /// \return style id
86 int ass_alloc_style(ass_track_t* track) {
87 int sid;
89 assert(track->n_styles <= track->max_styles);
91 if (track->n_styles == track->max_styles) {
92 track->max_styles += ASS_STYLES_ALLOC;
93 track->styles = (ass_style_t*)realloc(track->styles, sizeof(ass_style_t)*track->max_styles);
96 sid = track->n_styles++;
97 memset(track->styles + sid, 0, sizeof(ass_style_t));
98 return sid;
101 /// \brief Allocate a new event struct
102 /// \param track track
103 /// \return event id
104 int ass_alloc_event(ass_track_t* track) {
105 int eid;
107 assert(track->n_events <= track->max_events);
109 if (track->n_events == track->max_events) {
110 track->max_events += ASS_EVENTS_ALLOC;
111 track->events = (ass_event_t*)realloc(track->events, sizeof(ass_event_t)*track->max_events);
114 eid = track->n_events++;
115 memset(track->events + eid, 0, sizeof(ass_event_t));
116 return eid;
119 void ass_free_event(ass_track_t* track, int eid) {
120 ass_event_t* event = track->events + eid;
121 if (event->Name)
122 free(event->Name);
123 if (event->Effect)
124 free(event->Effect);
125 if (event->Text)
126 free(event->Text);
127 if (event->render_priv)
128 free(event->render_priv);
131 void ass_free_style(ass_track_t* track, int sid) {
132 ass_style_t* style = track->styles + sid;
133 if (style->Name)
134 free(style->Name);
135 if (style->FontName)
136 free(style->FontName);
139 // ==============================================================================================
141 static void skip_spaces(char** str) {
142 char* p = *str;
143 while ((*p==' ') || (*p=='\t'))
144 ++p;
145 *str = p;
148 static void rskip_spaces(char** str, char* limit) {
149 char* p = *str;
150 while ((p >= limit) && ((*p==' ') || (*p=='\t')))
151 --p;
152 *str = p;
156 * \brief find style by name
157 * \param track track
158 * \param name style name
159 * \return index in track->styles
160 * Returnes 0 if no styles found => expects at least 1 style.
161 * Parsing code always adds "Default" style in the end.
163 static int lookup_style(ass_track_t* track, char* name) {
164 int i;
165 if (*name == '*') ++name; // FIXME: what does '*' really mean ?
166 for (i = track->n_styles - 1; i >= 0; --i) {
167 // FIXME: mb strcasecmp ?
168 if (strcmp(track->styles[i].Name, name) == 0)
169 return i;
171 i = track->default_style;
172 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NoStyleNamedXFoundUsingY, track, name, track->styles[i].Name);
173 return i; // use the first style
176 static uint32_t string2color(char* p) {
177 uint32_t tmp;
178 (void)strtocolor(&p, &tmp);
179 return tmp;
182 static long long string2timecode(char* p) {
183 unsigned h, m, s, ms;
184 long long tm;
185 int res = sscanf(p, "%1d:%2d:%2d.%2d", &h, &m, &s, &ms);
186 if (res < 4) {
187 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_BadTimestamp);
188 return 0;
190 tm = ((h * 60 + m) * 60 + s) * 1000 + ms * 10;
191 return tm;
195 * \brief converts numpad-style align to align.
197 static int numpad2align(int val) {
198 int res, v;
199 v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment
200 if (v != 0) v = 3 - v;
201 res = ((val - 1) % 3) + 1; // horizontal alignment
202 res += v*4;
203 return res;
206 #define NEXT(str,token) \
207 token = next_token(&str); \
208 if (!token) break;
210 #define ANYVAL(name,func) \
211 } else if (strcasecmp(tname, #name) == 0) { \
212 target->name = func(token); \
213 mp_msg(MSGT_ASS, MSGL_DBG2, "%s = %s\n", #name, token);
215 #define STRVAL(name) \
216 } else if (strcasecmp(tname, #name) == 0) { \
217 if (target->name != NULL) free(target->name); \
218 target->name = strdup(token); \
219 mp_msg(MSGT_ASS, MSGL_DBG2, "%s = %s\n", #name, token);
221 #define COLORVAL(name) ANYVAL(name,string2color)
222 #define INTVAL(name) ANYVAL(name,atoi)
223 #define FPVAL(name) ANYVAL(name,atof)
224 #define TIMEVAL(name) ANYVAL(name,string2timecode)
225 #define STYLEVAL(name) \
226 } else if (strcasecmp(tname, #name) == 0) { \
227 target->name = lookup_style(track, token); \
228 mp_msg(MSGT_ASS, MSGL_DBG2, "%s = %s\n", #name, token);
230 #define ALIAS(alias,name) \
231 if (strcasecmp(tname, #alias) == 0) {tname = #name;}
233 static char* next_token(char** str) {
234 char* p = *str;
235 char* start;
236 skip_spaces(&p);
237 if (*p == '\0') {
238 *str = p;
239 return 0;
241 start = p; // start of the token
242 for (; (*p != '\0') && (*p != ','); ++p) {}
243 if (*p == '\0') {
244 *str = p; // eos found, str will point to '\0' at exit
245 } else {
246 *p = '\0';
247 *str = p + 1; // ',' found, str will point to the next char (beginning of the next token)
249 --p; // end of current token
250 rskip_spaces(&p, start);
251 if (p < start)
252 p = start; // empty token
253 else
254 ++p; // the first space character, or '\0'
255 *p = '\0';
256 return start;
259 * \brief Parse the tail of Dialogue line
260 * \param track track
261 * \param event parsed data goes here
262 * \param str string to parse, zero-terminated
263 * \param n_ignored number of format options to skip at the beginning
265 static int process_event_tail(ass_track_t* track, ass_event_t* event, char* str, int n_ignored)
267 char* token;
268 char* tname;
269 char* p = str;
270 int i;
271 ass_event_t* target = event;
273 char* format;
274 char* q; // format scanning pointer
276 if (!track->event_format) {
277 track->event_format = strdup("Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text");
278 mp_msg(MSGT_ASS, MSGL_V, "Event format is broken, reseting to defaults.\n");
281 q = format = strdup(track->event_format);
283 if (track->n_styles == 0) {
284 // add "Default" style to the end
285 // will be used if track does not contain a default style (or even does not contain styles at all)
286 int sid = ass_alloc_style(track);
287 track->styles[sid].Name = strdup("Default");
288 track->styles[sid].FontName = strdup("Arial");
291 for (i = 0; i < n_ignored; ++i) {
292 NEXT(q, tname);
295 while (1) {
296 NEXT(q, tname);
297 if (strcasecmp(tname, "Text") == 0) {
298 char* last;
299 event->Text = strdup(p);
300 if (*event->Text != 0) {
301 last = event->Text + strlen(event->Text) - 1;
302 if (last >= event->Text && *last == '\r')
303 *last = 0;
305 mp_msg(MSGT_ASS, MSGL_DBG2, "Text = %s\n", event->Text);
306 event->Duration -= event->Start;
307 free(format);
308 return 0; // "Text" is always the last
310 NEXT(p, token);
312 ALIAS(End,Duration) // temporarily store end timecode in event->Duration
313 if (0) { // cool ;)
314 INTVAL(Layer)
315 STYLEVAL(Style)
316 STRVAL(Name)
317 STRVAL(Effect)
318 INTVAL(MarginL)
319 INTVAL(MarginR)
320 INTVAL(MarginV)
321 TIMEVAL(Start)
322 TIMEVAL(Duration)
325 free(format);
326 return 1;
330 * \brief Parse command line style overrides (--ass-force-style option)
331 * \param track track to apply overrides to
332 * The format for overrides is [StyleName.]Field=Value
334 void process_force_style(ass_track_t* track) {
335 char **fs, *eq, *dt, *style, *tname, *token;
336 ass_style_t* target;
337 int sid;
338 char** list = track->library->style_overrides;
340 if (!list) return;
342 for (fs = list; *fs; ++fs) {
343 eq = strrchr(*fs, '=');
344 if (!eq)
345 continue;
346 *eq = '\0';
347 token = eq + 1;
349 if(!strcasecmp(*fs, "PlayResX"))
350 track->PlayResX = atoi(token);
351 else if(!strcasecmp(*fs, "PlayResY"))
352 track->PlayResY = atoi(token);
353 else if(!strcasecmp(*fs, "Timer"))
354 track->Timer = atof(token);
355 else if(!strcasecmp(*fs, "WrapStyle"))
356 track->WrapStyle = atoi(token);
357 else if(!strcasecmp(*fs, "ScaledBorderAndShadow"))
358 track->ScaledBorderAndShadow = parse_bool(token);
360 dt = strrchr(*fs, '.');
361 if (dt) {
362 *dt = '\0';
363 style = *fs;
364 tname = dt + 1;
365 } else {
366 style = NULL;
367 tname = *fs;
369 for (sid = 0; sid < track->n_styles; ++sid) {
370 if (style == NULL || strcasecmp(track->styles[sid].Name, style) == 0) {
371 target = track->styles + sid;
372 if (0) {
373 STRVAL(FontName)
374 COLORVAL(PrimaryColour)
375 COLORVAL(SecondaryColour)
376 COLORVAL(OutlineColour)
377 COLORVAL(BackColour)
378 FPVAL(FontSize)
379 INTVAL(Bold)
380 INTVAL(Italic)
381 INTVAL(Underline)
382 INTVAL(StrikeOut)
383 FPVAL(Spacing)
384 INTVAL(Angle)
385 INTVAL(BorderStyle)
386 INTVAL(Alignment)
387 INTVAL(MarginL)
388 INTVAL(MarginR)
389 INTVAL(MarginV)
390 INTVAL(Encoding)
391 FPVAL(ScaleX)
392 FPVAL(ScaleY)
393 FPVAL(Outline)
394 FPVAL(Shadow)
398 *eq = '=';
399 if (dt) *dt = '.';
404 * \brief Parse the Style line
405 * \param track track
406 * \param str string to parse, zero-terminated
407 * Allocates a new style struct.
409 static int process_style(ass_track_t* track, char *str)
412 char* token;
413 char* tname;
414 char* p = str;
415 char* format;
416 char* q; // format scanning pointer
417 int sid;
418 ass_style_t* style;
419 ass_style_t* target;
421 if (!track->style_format) {
422 // no style format header
423 // probably an ancient script version
424 if (track->track_type == TRACK_TYPE_SSA)
425 track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
426 "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline,"
427 "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
428 else
429 track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
430 "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut,"
431 "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow,"
432 "Alignment, MarginL, MarginR, MarginV, Encoding");
435 q = format = strdup(track->style_format);
437 mp_msg(MSGT_ASS, MSGL_V, "[%p] Style: %s\n", track, str);
439 sid = ass_alloc_style(track);
441 style = track->styles + sid;
442 target = style;
443 // fill style with some default values
444 style->ScaleX = 100.;
445 style->ScaleY = 100.;
447 while (1) {
448 NEXT(q, tname);
449 NEXT(p, token);
451 // ALIAS(TertiaryColour,OutlineColour) // ignore TertiaryColour; it appears only in SSA, and is overridden by BackColour
453 if (0) { // cool ;)
454 STRVAL(Name)
455 if ((strcmp(target->Name, "Default")==0) || (strcmp(target->Name, "*Default")==0))
456 track->default_style = sid;
457 STRVAL(FontName)
458 COLORVAL(PrimaryColour)
459 COLORVAL(SecondaryColour)
460 COLORVAL(OutlineColour) // TertiaryColor
461 COLORVAL(BackColour)
462 // SSA uses BackColour for both outline and shadow
463 // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
464 if (track->track_type == TRACK_TYPE_SSA)
465 target->OutlineColour = target->BackColour;
466 FPVAL(FontSize)
467 INTVAL(Bold)
468 INTVAL(Italic)
469 INTVAL(Underline)
470 INTVAL(StrikeOut)
471 FPVAL(Spacing)
472 INTVAL(Angle)
473 INTVAL(BorderStyle)
474 INTVAL(Alignment)
475 if (track->track_type == TRACK_TYPE_ASS)
476 target->Alignment = numpad2align(target->Alignment);
477 INTVAL(MarginL)
478 INTVAL(MarginR)
479 INTVAL(MarginV)
480 INTVAL(Encoding)
481 FPVAL(ScaleX)
482 FPVAL(ScaleY)
483 FPVAL(Outline)
484 FPVAL(Shadow)
487 style->ScaleX /= 100.;
488 style->ScaleY /= 100.;
489 style->Bold = !!style->Bold;
490 style->Italic = !!style->Italic;
491 style->Underline = !!style->Underline;
492 if (!style->Name)
493 style->Name = strdup("Default");
494 if (!style->FontName)
495 style->FontName = strdup("Arial");
496 // skip '@' at the start of the font name
497 if (*style->FontName == '@') {
498 p = style->FontName;
499 style->FontName = strdup(p + 1);
500 free(p);
502 free(format);
503 return 0;
507 static int process_styles_line(ass_track_t* track, char *str)
509 if (!strncmp(str,"Format:", 7)) {
510 char* p = str + 7;
511 skip_spaces(&p);
512 track->style_format = strdup(p);
513 mp_msg(MSGT_ASS, MSGL_DBG2, "Style format: %s\n", track->style_format);
514 } else if (!strncmp(str,"Style:", 6)) {
515 char* p = str + 6;
516 skip_spaces(&p);
517 process_style(track, p);
519 return 0;
522 static int process_info_line(ass_track_t* track, char *str)
524 if (!strncmp(str, "PlayResX:", 9)) {
525 track->PlayResX = atoi(str + 9);
526 } else if (!strncmp(str,"PlayResY:", 9)) {
527 track->PlayResY = atoi(str + 9);
528 } else if (!strncmp(str,"Timer:", 6)) {
529 track->Timer = atof(str + 6);
530 } else if (!strncmp(str,"WrapStyle:", 10)) {
531 track->WrapStyle = atoi(str + 10);
532 } else if (!strncmp(str, "ScaledBorderAndShadow:", 22)) {
533 track->ScaledBorderAndShadow = parse_bool(str + 22);
535 return 0;
538 static int process_events_line(ass_track_t* track, char *str)
540 if (!strncmp(str, "Format:", 7)) {
541 char* p = str + 7;
542 skip_spaces(&p);
543 track->event_format = strdup(p);
544 mp_msg(MSGT_ASS, MSGL_DBG2, "Event format: %s\n", track->event_format);
545 } else if (!strncmp(str, "Dialogue:", 9)) {
546 // This should never be reached for embedded subtitles.
547 // They have slightly different format and are parsed in ass_process_chunk,
548 // called directly from demuxer
549 int eid;
550 ass_event_t* event;
552 str += 9;
553 skip_spaces(&str);
555 eid = ass_alloc_event(track);
556 event = track->events + eid;
558 process_event_tail(track, event, str, 0);
559 } else {
560 mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s \n", str);
562 return 0;
565 // Copied from mkvtoolnix
566 static unsigned char* decode_chars(unsigned char c1, unsigned char c2,
567 unsigned char c3, unsigned char c4, unsigned char* dst, int cnt)
569 uint32_t value;
570 unsigned char bytes[3];
571 int i;
573 value = ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 - 33);
574 bytes[2] = value & 0xff;
575 bytes[1] = (value & 0xff00) >> 8;
576 bytes[0] = (value & 0xff0000) >> 16;
578 for (i = 0; i < cnt; ++i)
579 *dst++ = bytes[i];
580 return dst;
583 static int decode_font(ass_track_t* track)
585 unsigned char* p;
586 unsigned char* q;
587 int i;
588 int size; // original size
589 int dsize; // decoded size
590 unsigned char* buf = 0;
592 mp_msg(MSGT_ASS, MSGL_V, "font: %d bytes encoded data \n", track->parser_priv->fontdata_used);
593 size = track->parser_priv->fontdata_used;
594 if (size % 4 == 1) {
595 mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_BadEncodedDataSize);
596 goto error_decode_font;
598 buf = malloc(size / 4 * 3 + 2);
599 q = buf;
600 for (i = 0, p = (unsigned char*)track->parser_priv->fontdata; i < size / 4; i++, p+=4) {
601 q = decode_chars(p[0], p[1], p[2], p[3], q, 3);
603 if (size % 4 == 2) {
604 q = decode_chars(p[0], p[1], 0, 0, q, 1);
605 } else if (size % 4 == 3) {
606 q = decode_chars(p[0], p[1], p[2], 0, q, 2);
608 dsize = q - buf;
609 assert(dsize <= size / 4 * 3 + 2);
611 if (track->library->extract_fonts) {
612 ass_add_font(track->library, track->parser_priv->fontname, (char*)buf, dsize);
613 buf = 0;
616 error_decode_font:
617 if (buf) free(buf);
618 free(track->parser_priv->fontname);
619 free(track->parser_priv->fontdata);
620 track->parser_priv->fontname = 0;
621 track->parser_priv->fontdata = 0;
622 track->parser_priv->fontdata_size = 0;
623 track->parser_priv->fontdata_used = 0;
624 return 0;
627 static int process_fonts_line(ass_track_t* track, char *str)
629 int len;
631 if (!strncmp(str, "fontname:", 9)) {
632 char* p = str + 9;
633 skip_spaces(&p);
634 if (track->parser_priv->fontname) {
635 decode_font(track);
637 track->parser_priv->fontname = strdup(p);
638 mp_msg(MSGT_ASS, MSGL_V, "fontname: %s\n", track->parser_priv->fontname);
639 return 0;
642 if (!track->parser_priv->fontname) {
643 mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s \n", str);
644 return 0;
647 len = strlen(str);
648 if (len > 80) {
649 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FontLineTooLong, len, str);
650 return 0;
652 if (track->parser_priv->fontdata_used + len > track->parser_priv->fontdata_size) {
653 track->parser_priv->fontdata_size += 100 * 1024;
654 track->parser_priv->fontdata = realloc(track->parser_priv->fontdata, track->parser_priv->fontdata_size);
656 memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used, str, len);
657 track->parser_priv->fontdata_used += len;
659 return 0;
663 * \brief Parse a header line
664 * \param track track
665 * \param str string to parse, zero-terminated
667 static int process_line(ass_track_t* track, char *str)
669 if (!strncasecmp(str, "[Script Info]", 13)) {
670 track->parser_priv->state = PST_INFO;
671 } else if (!strncasecmp(str, "[V4 Styles]", 11)) {
672 track->parser_priv->state = PST_STYLES;
673 track->track_type = TRACK_TYPE_SSA;
674 } else if (!strncasecmp(str, "[V4+ Styles]", 12)) {
675 track->parser_priv->state = PST_STYLES;
676 track->track_type = TRACK_TYPE_ASS;
677 } else if (!strncasecmp(str, "[Events]", 8)) {
678 track->parser_priv->state = PST_EVENTS;
679 } else if (!strncasecmp(str, "[Fonts]", 7)) {
680 track->parser_priv->state = PST_FONTS;
681 } else {
682 switch (track->parser_priv->state) {
683 case PST_INFO:
684 process_info_line(track, str);
685 break;
686 case PST_STYLES:
687 process_styles_line(track, str);
688 break;
689 case PST_EVENTS:
690 process_events_line(track, str);
691 break;
692 case PST_FONTS:
693 process_fonts_line(track, str);
694 break;
695 default:
696 break;
700 // there is no explicit end-of-font marker in ssa/ass
701 if ((track->parser_priv->state != PST_FONTS) && (track->parser_priv->fontname))
702 decode_font(track);
704 return 0;
707 static int process_text(ass_track_t* track, char* str)
709 char* p = str;
710 while(1) {
711 char* q;
712 while (1) {
713 if ((*p=='\r')||(*p=='\n')) ++p;
714 else if (p[0]=='\xef' && p[1]=='\xbb' && p[2]=='\xbf') p+=3; // U+FFFE (BOM)
715 else break;
717 for (q=p; ((*q!='\0')&&(*q!='\r')&&(*q!='\n')); ++q) {};
718 if (q==p)
719 break;
720 if (*q != '\0')
721 *(q++) = '\0';
722 process_line(track, p);
723 if (*q == '\0')
724 break;
725 p = q;
727 return 0;
731 * \brief Process a chunk of subtitle stream data.
732 * \param track track
733 * \param data string to parse
734 * \param size length of data
736 void ass_process_data(ass_track_t* track, char* data, int size)
738 char* str = malloc(size + 1);
740 memcpy(str, data, size);
741 str[size] = '\0';
743 mp_msg(MSGT_ASS, MSGL_V, "event: %s\n", str);
744 process_text(track, str);
745 free(str);
749 * \brief Process CodecPrivate section of subtitle stream
750 * \param track track
751 * \param data string to parse
752 * \param size length of data
753 CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections
755 void ass_process_codec_private(ass_track_t* track, char *data, int size)
757 ass_process_data(track, data, size);
759 if (!track->event_format) {
760 // probably an mkv produced by ancient mkvtoolnix
761 // such files don't have [Events] and Format: headers
762 track->parser_priv->state = PST_EVENTS;
763 if (track->track_type == TRACK_TYPE_SSA)
764 track->event_format = strdup("Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text");
765 else
766 track->event_format = strdup("Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text");
769 process_force_style(track);
772 static int check_duplicate_event(ass_track_t* track, int ReadOrder)
774 int i;
775 for (i = 0; i<track->n_events - 1; ++i) // ignoring last event, it is the one we are comparing with
776 if (track->events[i].ReadOrder == ReadOrder)
777 return 1;
778 return 0;
782 * \brief Process a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary).
783 * \param track track
784 * \param data string to parse
785 * \param size length of data
786 * \param timecode starting time of the event (milliseconds)
787 * \param duration duration of the event (milliseconds)
789 void ass_process_chunk(ass_track_t* track, char *data, int size, long long timecode, long long duration)
791 char* str;
792 int eid;
793 char* p;
794 char* token;
795 ass_event_t* event;
797 if (!track->event_format) {
798 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EventFormatHeaderMissing);
799 return;
802 str = malloc(size + 1);
803 memcpy(str, data, size);
804 str[size] = '\0';
805 mp_msg(MSGT_ASS, MSGL_V, "event at %" PRId64 ", +%" PRId64 ": %s \n", (int64_t)timecode, (int64_t)duration, str);
807 eid = ass_alloc_event(track);
808 event = track->events + eid;
810 p = str;
812 do {
813 NEXT(p, token);
814 event->ReadOrder = atoi(token);
815 if (check_duplicate_event(track, event->ReadOrder))
816 break;
818 NEXT(p, token);
819 event->Layer = atoi(token);
821 process_event_tail(track, event, p, 3);
823 event->Start = timecode;
824 event->Duration = duration;
826 free(str);
827 return;
828 // dump_events(tid);
829 } while (0);
830 // some error
831 ass_free_event(track, eid);
832 track->n_events--;
833 free(str);
836 #ifdef CONFIG_ICONV
837 /** \brief recode buffer to utf-8
838 * constraint: codepage != 0
839 * \param data pointer to text buffer
840 * \param size buffer size
841 * \return a pointer to recoded buffer, caller is responsible for freeing it
843 static char* sub_recode(char* data, size_t size, char* codepage)
845 static iconv_t icdsc = (iconv_t)(-1);
846 char* tocp = "UTF-8";
847 char* outbuf;
848 assert(codepage);
851 const char* cp_tmp = codepage;
852 #ifdef CONFIG_ENCA
853 char enca_lang[3], enca_fallback[100];
854 if (sscanf(codepage, "enca:%2s:%99s", enca_lang, enca_fallback) == 2
855 || sscanf(codepage, "ENCA:%2s:%99s", enca_lang, enca_fallback) == 2) {
856 cp_tmp = guess_buffer_cp((unsigned char*)data, size, enca_lang, enca_fallback);
858 #endif
859 if ((icdsc = iconv_open (tocp, cp_tmp)) != (iconv_t)(-1)){
860 mp_msg(MSGT_ASS,MSGL_V,"LIBSUB: opened iconv descriptor.\n");
861 } else
862 mp_msg(MSGT_ASS,MSGL_ERR,MSGTR_LIBASS_ErrorOpeningIconvDescriptor);
866 size_t osize = size;
867 size_t ileft = size;
868 size_t oleft = size - 1;
869 char* ip;
870 char* op;
871 size_t rc;
872 int clear = 0;
874 outbuf = malloc(osize);
875 ip = data;
876 op = outbuf;
878 while (1) {
879 if (ileft)
880 rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
881 else {// clear the conversion state and leave
882 clear = 1;
883 rc = iconv(icdsc, NULL, NULL, &op, &oleft);
885 if (rc == (size_t)(-1)) {
886 if (errno == E2BIG) {
887 size_t offset = op - outbuf;
888 outbuf = (char*)realloc(outbuf, osize + size);
889 op = outbuf + offset;
890 osize += size;
891 oleft += size;
892 } else {
893 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorRecodingFile);
894 return NULL;
896 } else
897 if (clear)
898 break;
900 outbuf[osize - oleft - 1] = 0;
903 if (icdsc != (iconv_t)(-1)) {
904 (void)iconv_close(icdsc);
905 icdsc = (iconv_t)(-1);
906 mp_msg(MSGT_ASS,MSGL_V,"LIBSUB: closed iconv descriptor.\n");
909 return outbuf;
911 #endif // ICONV
914 * \brief read file contents into newly allocated buffer
915 * \param fname file name
916 * \param bufsize out: file size
917 * \return pointer to file contents. Caller is responsible for its deallocation.
919 static char* read_file(char* fname, size_t *bufsize)
921 int res;
922 long sz;
923 long bytes_read;
924 char* buf;
926 FILE* fp = fopen(fname, "rb");
927 if (!fp) {
928 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FopenFailed, fname);
929 return 0;
931 res = fseek(fp, 0, SEEK_END);
932 if (res == -1) {
933 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FseekFailed, fname);
934 fclose(fp);
935 return 0;
938 sz = ftell(fp);
939 rewind(fp);
941 if (sz > 10*1024*1024) {
942 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_RefusingToLoadSubtitlesLargerThan10M, fname);
943 fclose(fp);
944 return 0;
947 mp_msg(MSGT_ASS, MSGL_V, "file size: %ld\n", sz);
949 buf = malloc(sz + 1);
950 assert(buf);
951 bytes_read = 0;
952 do {
953 res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
954 if (res <= 0) {
955 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_ReadFailed, errno, strerror(errno));
956 fclose(fp);
957 free(buf);
958 return 0;
960 bytes_read += res;
961 } while (sz - bytes_read > 0);
962 buf[sz] = '\0';
963 fclose(fp);
965 if (bufsize)
966 *bufsize = sz;
967 return buf;
971 * \param buf pointer to subtitle text in utf-8
973 static ass_track_t* parse_memory(ass_library_t* library, char* buf)
975 ass_track_t* track;
976 int i;
978 track = ass_new_track(library);
980 // process header
981 process_text(track, buf);
983 // external SSA/ASS subs does not have ReadOrder field
984 for (i = 0; i < track->n_events; ++i)
985 track->events[i].ReadOrder = i;
987 // there is no explicit end-of-font marker in ssa/ass
988 if (track->parser_priv->fontname)
989 decode_font(track);
991 if (track->track_type == TRACK_TYPE_UNKNOWN) {
992 ass_free_track(track);
993 return 0;
996 process_force_style(track);
998 return track;
1002 * \brief Read subtitles from memory.
1003 * \param library libass library object
1004 * \param buf pointer to subtitles text
1005 * \param bufsize size of buffer
1006 * \param codepage recode buffer contents from given codepage
1007 * \return newly allocated track
1009 ass_track_t* ass_read_memory(ass_library_t* library, char* buf, size_t bufsize, char* codepage)
1011 ass_track_t* track;
1012 int need_free = 0;
1014 if (!buf)
1015 return 0;
1017 #ifdef CONFIG_ICONV
1018 if (codepage)
1019 buf = sub_recode(buf, bufsize, codepage);
1020 if (!buf)
1021 return 0;
1022 else
1023 need_free = 1;
1024 #endif
1025 track = parse_memory(library, buf);
1026 if (need_free)
1027 free(buf);
1028 if (!track)
1029 return 0;
1031 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_AddedSubtitleFileMemory, track->n_styles, track->n_events);
1032 return track;
1035 char* read_file_recode(char* fname, char* codepage, size_t* size)
1037 char* buf;
1038 size_t bufsize;
1040 buf = read_file(fname, &bufsize);
1041 if (!buf)
1042 return 0;
1043 #ifdef CONFIG_ICONV
1044 if (codepage) {
1045 char* tmpbuf = sub_recode(buf, bufsize, codepage);
1046 free(buf);
1047 buf = tmpbuf;
1049 if (!buf)
1050 return 0;
1051 #endif
1052 *size = bufsize;
1053 return buf;
1057 * \brief Read subtitles from file.
1058 * \param library libass library object
1059 * \param fname file name
1060 * \param codepage recode buffer contents from given codepage
1061 * \return newly allocated track
1063 ass_track_t* ass_read_file(ass_library_t* library, char* fname, char* codepage)
1065 char* buf;
1066 ass_track_t* track;
1067 size_t bufsize;
1069 buf = read_file_recode(fname, codepage, &bufsize);
1070 if (!buf)
1071 return 0;
1072 track = parse_memory(library, buf);
1073 free(buf);
1074 if (!track)
1075 return 0;
1077 track->name = strdup(fname);
1079 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_AddedSubtitleFileFname, fname, track->n_styles, track->n_events);
1081 // dump_events(forced_tid);
1082 return track;
1086 * \brief read styles from file into already initialized track
1088 int ass_read_styles(ass_track_t* track, char* fname, char* codepage)
1090 char* buf;
1091 parser_state_t old_state;
1092 size_t sz;
1094 buf = read_file(fname, &sz);
1095 if (!buf)
1096 return 1;
1097 #ifdef CONFIG_ICONV
1098 if (codepage) {
1099 char* tmpbuf;
1100 tmpbuf = sub_recode(buf, sz, codepage);
1101 free(buf);
1102 buf = tmpbuf;
1104 if (!buf)
1105 return 0;
1106 #endif
1108 old_state = track->parser_priv->state;
1109 track->parser_priv->state = PST_STYLES;
1110 process_text(track, buf);
1111 track->parser_priv->state = old_state;
1113 return 0;
1116 long long ass_step_sub(ass_track_t* track, long long now, int movement) {
1117 int i;
1119 if (movement == 0) return 0;
1120 if (track->n_events == 0) return 0;
1122 if (movement < 0)
1123 for (i = 0; (i < track->n_events) && ((long long)(track->events[i].Start + track->events[i].Duration) <= now); ++i) {}
1124 else
1125 for (i = track->n_events - 1; (i >= 0) && ((long long)(track->events[i].Start) > now); --i) {}
1127 // -1 and n_events are ok
1128 assert(i >= -1); assert(i <= track->n_events);
1129 i += movement;
1130 if (i < 0) i = 0;
1131 if (i >= track->n_events) i = track->n_events - 1;
1132 return ((long long)track->events[i].Start) - now;
1135 ass_track_t* ass_new_track(ass_library_t* library) {
1136 ass_track_t* track = calloc(1, sizeof(ass_track_t));
1137 track->library = library;
1138 track->ScaledBorderAndShadow = 1;
1139 track->parser_priv = calloc(1, sizeof(parser_priv_t));
1140 return track;