Add const where appropriate, also gets rid of a compiler warning.
[mplayer/glamo.git] / libass / ass.c
blob822f1e03f8a3dadf243f76d847d1bb83bae26d84
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 = strdup(track->event_format);
274 char* q = format; // format scanning pointer
276 if (track->n_styles == 0) {
277 // add "Default" style to the end
278 // will be used if track does not contain a default style (or even does not contain styles at all)
279 int sid = ass_alloc_style(track);
280 track->styles[sid].Name = strdup("Default");
281 track->styles[sid].FontName = strdup("Arial");
284 for (i = 0; i < n_ignored; ++i) {
285 NEXT(q, tname);
288 while (1) {
289 NEXT(q, tname);
290 if (strcasecmp(tname, "Text") == 0) {
291 char* last;
292 event->Text = strdup(p);
293 if (*event->Text != 0) {
294 last = event->Text + strlen(event->Text) - 1;
295 if (last >= event->Text && *last == '\r')
296 *last = 0;
298 mp_msg(MSGT_ASS, MSGL_DBG2, "Text = %s\n", event->Text);
299 event->Duration -= event->Start;
300 free(format);
301 return 0; // "Text" is always the last
303 NEXT(p, token);
305 ALIAS(End,Duration) // temporarily store end timecode in event->Duration
306 if (0) { // cool ;)
307 INTVAL(Layer)
308 STYLEVAL(Style)
309 STRVAL(Name)
310 STRVAL(Effect)
311 INTVAL(MarginL)
312 INTVAL(MarginR)
313 INTVAL(MarginV)
314 TIMEVAL(Start)
315 TIMEVAL(Duration)
318 free(format);
319 return 1;
323 * \brief Parse command line style overrides (--ass-force-style option)
324 * \param track track to apply overrides to
325 * The format for overrides is [StyleName.]Field=Value
327 void process_force_style(ass_track_t* track) {
328 char **fs, *eq, *dt, *style, *tname, *token;
329 ass_style_t* target;
330 int sid;
331 char** list = track->library->style_overrides;
333 if (!list) return;
335 for (fs = list; *fs; ++fs) {
336 eq = strrchr(*fs, '=');
337 if (!eq)
338 continue;
339 *eq = '\0';
340 token = eq + 1;
342 if(!strcasecmp(*fs, "PlayResX"))
343 track->PlayResX = atoi(token);
344 else if(!strcasecmp(*fs, "PlayResY"))
345 track->PlayResY = atoi(token);
346 else if(!strcasecmp(*fs, "Timer"))
347 track->Timer = atof(token);
348 else if(!strcasecmp(*fs, "WrapStyle"))
349 track->WrapStyle = atoi(token);
350 else if(!strcasecmp(*fs, "ScaledBorderAndShadow"))
351 track->ScaledBorderAndShadow = parse_bool(token);
353 dt = strrchr(*fs, '.');
354 if (dt) {
355 *dt = '\0';
356 style = *fs;
357 tname = dt + 1;
358 } else {
359 style = NULL;
360 tname = *fs;
362 for (sid = 0; sid < track->n_styles; ++sid) {
363 if (style == NULL || strcasecmp(track->styles[sid].Name, style) == 0) {
364 target = track->styles + sid;
365 if (0) {
366 STRVAL(FontName)
367 COLORVAL(PrimaryColour)
368 COLORVAL(SecondaryColour)
369 COLORVAL(OutlineColour)
370 COLORVAL(BackColour)
371 FPVAL(FontSize)
372 INTVAL(Bold)
373 INTVAL(Italic)
374 INTVAL(Underline)
375 INTVAL(StrikeOut)
376 FPVAL(Spacing)
377 INTVAL(Angle)
378 INTVAL(BorderStyle)
379 INTVAL(Alignment)
380 INTVAL(MarginL)
381 INTVAL(MarginR)
382 INTVAL(MarginV)
383 INTVAL(Encoding)
384 FPVAL(ScaleX)
385 FPVAL(ScaleY)
386 FPVAL(Outline)
387 FPVAL(Shadow)
391 *eq = '=';
392 if (dt) *dt = '.';
397 * \brief Parse the Style line
398 * \param track track
399 * \param str string to parse, zero-terminated
400 * Allocates a new style struct.
402 static int process_style(ass_track_t* track, char *str)
405 char* token;
406 char* tname;
407 char* p = str;
408 char* format;
409 char* q; // format scanning pointer
410 int sid;
411 ass_style_t* style;
412 ass_style_t* target;
414 if (!track->style_format) {
415 // no style format header
416 // probably an ancient script version
417 if (track->track_type == TRACK_TYPE_SSA)
418 track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
419 "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline,"
420 "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
421 else
422 track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
423 "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut,"
424 "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow,"
425 "Alignment, MarginL, MarginR, MarginV, Encoding");
428 q = format = strdup(track->style_format);
430 mp_msg(MSGT_ASS, MSGL_V, "[%p] Style: %s\n", track, str);
432 sid = ass_alloc_style(track);
434 style = track->styles + sid;
435 target = style;
436 // fill style with some default values
437 style->ScaleX = 100.;
438 style->ScaleY = 100.;
440 while (1) {
441 NEXT(q, tname);
442 NEXT(p, token);
444 // ALIAS(TertiaryColour,OutlineColour) // ignore TertiaryColour; it appears only in SSA, and is overridden by BackColour
446 if (0) { // cool ;)
447 STRVAL(Name)
448 if ((strcmp(target->Name, "Default")==0) || (strcmp(target->Name, "*Default")==0))
449 track->default_style = sid;
450 STRVAL(FontName)
451 COLORVAL(PrimaryColour)
452 COLORVAL(SecondaryColour)
453 COLORVAL(OutlineColour) // TertiaryColor
454 COLORVAL(BackColour)
455 // SSA uses BackColour for both outline and shadow
456 // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
457 if (track->track_type == TRACK_TYPE_SSA)
458 target->OutlineColour = target->BackColour;
459 FPVAL(FontSize)
460 INTVAL(Bold)
461 INTVAL(Italic)
462 INTVAL(Underline)
463 INTVAL(StrikeOut)
464 FPVAL(Spacing)
465 INTVAL(Angle)
466 INTVAL(BorderStyle)
467 INTVAL(Alignment)
468 if (track->track_type == TRACK_TYPE_ASS)
469 target->Alignment = numpad2align(target->Alignment);
470 INTVAL(MarginL)
471 INTVAL(MarginR)
472 INTVAL(MarginV)
473 INTVAL(Encoding)
474 FPVAL(ScaleX)
475 FPVAL(ScaleY)
476 FPVAL(Outline)
477 FPVAL(Shadow)
480 style->ScaleX /= 100.;
481 style->ScaleY /= 100.;
482 style->Bold = !!style->Bold;
483 style->Italic = !!style->Italic;
484 style->Underline = !!style->Underline;
485 if (!style->Name)
486 style->Name = strdup("Default");
487 if (!style->FontName)
488 style->FontName = strdup("Arial");
489 // skip '@' at the start of the font name
490 if (*style->FontName == '@') {
491 p = style->FontName;
492 style->FontName = strdup(p + 1);
493 free(p);
495 free(format);
496 return 0;
500 static int process_styles_line(ass_track_t* track, char *str)
502 if (!strncmp(str,"Format:", 7)) {
503 char* p = str + 7;
504 skip_spaces(&p);
505 track->style_format = strdup(p);
506 mp_msg(MSGT_ASS, MSGL_DBG2, "Style format: %s\n", track->style_format);
507 } else if (!strncmp(str,"Style:", 6)) {
508 char* p = str + 6;
509 skip_spaces(&p);
510 process_style(track, p);
512 return 0;
515 static int process_info_line(ass_track_t* track, char *str)
517 if (!strncmp(str, "PlayResX:", 9)) {
518 track->PlayResX = atoi(str + 9);
519 } else if (!strncmp(str,"PlayResY:", 9)) {
520 track->PlayResY = atoi(str + 9);
521 } else if (!strncmp(str,"Timer:", 6)) {
522 track->Timer = atof(str + 6);
523 } else if (!strncmp(str,"WrapStyle:", 10)) {
524 track->WrapStyle = atoi(str + 10);
525 } else if (!strncmp(str, "ScaledBorderAndShadow:", 22)) {
526 track->ScaledBorderAndShadow = parse_bool(str + 22);
528 return 0;
531 static int process_events_line(ass_track_t* track, char *str)
533 if (!strncmp(str, "Format:", 7)) {
534 char* p = str + 7;
535 skip_spaces(&p);
536 track->event_format = strdup(p);
537 mp_msg(MSGT_ASS, MSGL_DBG2, "Event format: %s\n", track->event_format);
538 } else if (!strncmp(str, "Dialogue:", 9)) {
539 // This should never be reached for embedded subtitles.
540 // They have slightly different format and are parsed in ass_process_chunk,
541 // called directly from demuxer
542 int eid;
543 ass_event_t* event;
545 str += 9;
546 skip_spaces(&str);
548 eid = ass_alloc_event(track);
549 event = track->events + eid;
551 process_event_tail(track, event, str, 0);
552 } else {
553 mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s \n", str);
555 return 0;
558 // Copied from mkvtoolnix
559 static unsigned char* decode_chars(unsigned char c1, unsigned char c2,
560 unsigned char c3, unsigned char c4, unsigned char* dst, int cnt)
562 uint32_t value;
563 unsigned char bytes[3];
564 int i;
566 value = ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 - 33);
567 bytes[2] = value & 0xff;
568 bytes[1] = (value & 0xff00) >> 8;
569 bytes[0] = (value & 0xff0000) >> 16;
571 for (i = 0; i < cnt; ++i)
572 *dst++ = bytes[i];
573 return dst;
576 static int decode_font(ass_track_t* track)
578 unsigned char* p;
579 unsigned char* q;
580 int i;
581 int size; // original size
582 int dsize; // decoded size
583 unsigned char* buf = 0;
585 mp_msg(MSGT_ASS, MSGL_V, "font: %d bytes encoded data \n", track->parser_priv->fontdata_used);
586 size = track->parser_priv->fontdata_used;
587 if (size % 4 == 1) {
588 mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_BadEncodedDataSize);
589 goto error_decode_font;
591 buf = malloc(size / 4 * 3 + 2);
592 q = buf;
593 for (i = 0, p = (unsigned char*)track->parser_priv->fontdata; i < size / 4; i++, p+=4) {
594 q = decode_chars(p[0], p[1], p[2], p[3], q, 3);
596 if (size % 4 == 2) {
597 q = decode_chars(p[0], p[1], 0, 0, q, 1);
598 } else if (size % 4 == 3) {
599 q = decode_chars(p[0], p[1], p[2], 0, q, 2);
601 dsize = q - buf;
602 assert(dsize <= size / 4 * 3 + 2);
604 if (track->library->extract_fonts) {
605 ass_add_font(track->library, track->parser_priv->fontname, (char*)buf, dsize);
606 buf = 0;
609 error_decode_font:
610 if (buf) free(buf);
611 free(track->parser_priv->fontname);
612 free(track->parser_priv->fontdata);
613 track->parser_priv->fontname = 0;
614 track->parser_priv->fontdata = 0;
615 track->parser_priv->fontdata_size = 0;
616 track->parser_priv->fontdata_used = 0;
617 return 0;
620 static int process_fonts_line(ass_track_t* track, char *str)
622 int len;
624 if (!strncmp(str, "fontname:", 9)) {
625 char* p = str + 9;
626 skip_spaces(&p);
627 if (track->parser_priv->fontname) {
628 decode_font(track);
630 track->parser_priv->fontname = strdup(p);
631 mp_msg(MSGT_ASS, MSGL_V, "fontname: %s\n", track->parser_priv->fontname);
632 return 0;
635 if (!track->parser_priv->fontname) {
636 mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s \n", str);
637 return 0;
640 len = strlen(str);
641 if (len > 80) {
642 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FontLineTooLong, len, str);
643 return 0;
645 if (track->parser_priv->fontdata_used + len > track->parser_priv->fontdata_size) {
646 track->parser_priv->fontdata_size += 100 * 1024;
647 track->parser_priv->fontdata = realloc(track->parser_priv->fontdata, track->parser_priv->fontdata_size);
649 memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used, str, len);
650 track->parser_priv->fontdata_used += len;
652 return 0;
656 * \brief Parse a header line
657 * \param track track
658 * \param str string to parse, zero-terminated
660 static int process_line(ass_track_t* track, char *str)
662 if (!strncasecmp(str, "[Script Info]", 13)) {
663 track->parser_priv->state = PST_INFO;
664 } else if (!strncasecmp(str, "[V4 Styles]", 11)) {
665 track->parser_priv->state = PST_STYLES;
666 track->track_type = TRACK_TYPE_SSA;
667 } else if (!strncasecmp(str, "[V4+ Styles]", 12)) {
668 track->parser_priv->state = PST_STYLES;
669 track->track_type = TRACK_TYPE_ASS;
670 } else if (!strncasecmp(str, "[Events]", 8)) {
671 track->parser_priv->state = PST_EVENTS;
672 } else if (!strncasecmp(str, "[Fonts]", 7)) {
673 track->parser_priv->state = PST_FONTS;
674 } else {
675 switch (track->parser_priv->state) {
676 case PST_INFO:
677 process_info_line(track, str);
678 break;
679 case PST_STYLES:
680 process_styles_line(track, str);
681 break;
682 case PST_EVENTS:
683 process_events_line(track, str);
684 break;
685 case PST_FONTS:
686 process_fonts_line(track, str);
687 break;
688 default:
689 break;
693 // there is no explicit end-of-font marker in ssa/ass
694 if ((track->parser_priv->state != PST_FONTS) && (track->parser_priv->fontname))
695 decode_font(track);
697 return 0;
700 static int process_text(ass_track_t* track, char* str)
702 char* p = str;
703 while(1) {
704 char* q;
705 while (1) {
706 if ((*p=='\r')||(*p=='\n')) ++p;
707 else if (p[0]=='\xef' && p[1]=='\xbb' && p[2]=='\xbf') p+=3; // U+FFFE (BOM)
708 else break;
710 for (q=p; ((*q!='\0')&&(*q!='\r')&&(*q!='\n')); ++q) {};
711 if (q==p)
712 break;
713 if (*q != '\0')
714 *(q++) = '\0';
715 process_line(track, p);
716 if (*q == '\0')
717 break;
718 p = q;
720 return 0;
724 * \brief Process a chunk of subtitle stream data.
725 * \param track track
726 * \param data string to parse
727 * \param size length of data
729 void ass_process_data(ass_track_t* track, char* data, int size)
731 char* str = malloc(size + 1);
733 memcpy(str, data, size);
734 str[size] = '\0';
736 mp_msg(MSGT_ASS, MSGL_V, "event: %s\n", str);
737 process_text(track, str);
738 free(str);
742 * \brief Process CodecPrivate section of subtitle stream
743 * \param track track
744 * \param data string to parse
745 * \param size length of data
746 CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections
748 void ass_process_codec_private(ass_track_t* track, char *data, int size)
750 ass_process_data(track, data, size);
752 if (!track->event_format) {
753 // probably an mkv produced by ancient mkvtoolnix
754 // such files don't have [Events] and Format: headers
755 track->parser_priv->state = PST_EVENTS;
756 if (track->track_type == TRACK_TYPE_SSA)
757 track->event_format = strdup("Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text");
758 else
759 track->event_format = strdup("Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text");
762 process_force_style(track);
765 static int check_duplicate_event(ass_track_t* track, int ReadOrder)
767 int i;
768 for (i = 0; i<track->n_events - 1; ++i) // ignoring last event, it is the one we are comparing with
769 if (track->events[i].ReadOrder == ReadOrder)
770 return 1;
771 return 0;
775 * \brief Process a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary).
776 * \param track track
777 * \param data string to parse
778 * \param size length of data
779 * \param timecode starting time of the event (milliseconds)
780 * \param duration duration of the event (milliseconds)
782 void ass_process_chunk(ass_track_t* track, char *data, int size, long long timecode, long long duration)
784 char* str;
785 int eid;
786 char* p;
787 char* token;
788 ass_event_t* event;
790 if (!track->event_format) {
791 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EventFormatHeaderMissing);
792 return;
795 str = malloc(size + 1);
796 memcpy(str, data, size);
797 str[size] = '\0';
798 mp_msg(MSGT_ASS, MSGL_V, "event at %" PRId64 ", +%" PRId64 ": %s \n", (int64_t)timecode, (int64_t)duration, str);
800 eid = ass_alloc_event(track);
801 event = track->events + eid;
803 p = str;
805 do {
806 NEXT(p, token);
807 event->ReadOrder = atoi(token);
808 if (check_duplicate_event(track, event->ReadOrder))
809 break;
811 NEXT(p, token);
812 event->Layer = atoi(token);
814 process_event_tail(track, event, p, 3);
816 event->Start = timecode;
817 event->Duration = duration;
819 free(str);
820 return;
821 // dump_events(tid);
822 } while (0);
823 // some error
824 ass_free_event(track, eid);
825 track->n_events--;
826 free(str);
829 #ifdef CONFIG_ICONV
830 /** \brief recode buffer to utf-8
831 * constraint: codepage != 0
832 * \param data pointer to text buffer
833 * \param size buffer size
834 * \return a pointer to recoded buffer, caller is responsible for freeing it
836 static char* sub_recode(char* data, size_t size, char* codepage)
838 static iconv_t icdsc = (iconv_t)(-1);
839 char* tocp = "UTF-8";
840 char* outbuf;
841 assert(codepage);
844 const char* cp_tmp = codepage;
845 #ifdef CONFIG_ENCA
846 char enca_lang[3], enca_fallback[100];
847 if (sscanf(codepage, "enca:%2s:%99s", enca_lang, enca_fallback) == 2
848 || sscanf(codepage, "ENCA:%2s:%99s", enca_lang, enca_fallback) == 2) {
849 cp_tmp = guess_buffer_cp((unsigned char*)data, size, enca_lang, enca_fallback);
851 #endif
852 if ((icdsc = iconv_open (tocp, cp_tmp)) != (iconv_t)(-1)){
853 mp_msg(MSGT_ASS,MSGL_V,"LIBSUB: opened iconv descriptor.\n");
854 } else
855 mp_msg(MSGT_ASS,MSGL_ERR,MSGTR_LIBASS_ErrorOpeningIconvDescriptor);
859 size_t osize = size;
860 size_t ileft = size;
861 size_t oleft = size - 1;
862 char* ip;
863 char* op;
864 size_t rc;
865 int clear = 0;
867 outbuf = malloc(osize);
868 ip = data;
869 op = outbuf;
871 while (1) {
872 if (ileft)
873 rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
874 else {// clear the conversion state and leave
875 clear = 1;
876 rc = iconv(icdsc, NULL, NULL, &op, &oleft);
878 if (rc == (size_t)(-1)) {
879 if (errno == E2BIG) {
880 size_t offset = op - outbuf;
881 outbuf = (char*)realloc(outbuf, osize + size);
882 op = outbuf + offset;
883 osize += size;
884 oleft += size;
885 } else {
886 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorRecodingFile);
887 return NULL;
889 } else
890 if (clear)
891 break;
893 outbuf[osize - oleft - 1] = 0;
896 if (icdsc != (iconv_t)(-1)) {
897 (void)iconv_close(icdsc);
898 icdsc = (iconv_t)(-1);
899 mp_msg(MSGT_ASS,MSGL_V,"LIBSUB: closed iconv descriptor.\n");
902 return outbuf;
904 #endif // ICONV
907 * \brief read file contents into newly allocated buffer
908 * \param fname file name
909 * \param bufsize out: file size
910 * \return pointer to file contents. Caller is responsible for its deallocation.
912 static char* read_file(char* fname, size_t *bufsize)
914 int res;
915 long sz;
916 long bytes_read;
917 char* buf;
919 FILE* fp = fopen(fname, "rb");
920 if (!fp) {
921 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FopenFailed, fname);
922 return 0;
924 res = fseek(fp, 0, SEEK_END);
925 if (res == -1) {
926 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FseekFailed, fname);
927 fclose(fp);
928 return 0;
931 sz = ftell(fp);
932 rewind(fp);
934 if (sz > 10*1024*1024) {
935 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_RefusingToLoadSubtitlesLargerThan10M, fname);
936 fclose(fp);
937 return 0;
940 mp_msg(MSGT_ASS, MSGL_V, "file size: %ld\n", sz);
942 buf = malloc(sz + 1);
943 assert(buf);
944 bytes_read = 0;
945 do {
946 res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
947 if (res <= 0) {
948 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_ReadFailed, errno, strerror(errno));
949 fclose(fp);
950 free(buf);
951 return 0;
953 bytes_read += res;
954 } while (sz - bytes_read > 0);
955 buf[sz] = '\0';
956 fclose(fp);
958 if (bufsize)
959 *bufsize = sz;
960 return buf;
964 * \param buf pointer to subtitle text in utf-8
966 static ass_track_t* parse_memory(ass_library_t* library, char* buf)
968 ass_track_t* track;
969 int i;
971 track = ass_new_track(library);
973 // process header
974 process_text(track, buf);
976 // external SSA/ASS subs does not have ReadOrder field
977 for (i = 0; i < track->n_events; ++i)
978 track->events[i].ReadOrder = i;
980 // there is no explicit end-of-font marker in ssa/ass
981 if (track->parser_priv->fontname)
982 decode_font(track);
984 if (track->track_type == TRACK_TYPE_UNKNOWN) {
985 ass_free_track(track);
986 return 0;
989 process_force_style(track);
991 return track;
995 * \brief Read subtitles from memory.
996 * \param library libass library object
997 * \param buf pointer to subtitles text
998 * \param bufsize size of buffer
999 * \param codepage recode buffer contents from given codepage
1000 * \return newly allocated track
1002 ass_track_t* ass_read_memory(ass_library_t* library, char* buf, size_t bufsize, char* codepage)
1004 ass_track_t* track;
1005 int need_free = 0;
1007 if (!buf)
1008 return 0;
1010 #ifdef CONFIG_ICONV
1011 if (codepage)
1012 buf = sub_recode(buf, bufsize, codepage);
1013 if (!buf)
1014 return 0;
1015 else
1016 need_free = 1;
1017 #endif
1018 track = parse_memory(library, buf);
1019 if (need_free)
1020 free(buf);
1021 if (!track)
1022 return 0;
1024 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_AddedSubtitleFileMemory, track->n_styles, track->n_events);
1025 return track;
1028 char* read_file_recode(char* fname, char* codepage, size_t* size)
1030 char* buf;
1031 size_t bufsize;
1033 buf = read_file(fname, &bufsize);
1034 if (!buf)
1035 return 0;
1036 #ifdef CONFIG_ICONV
1037 if (codepage) {
1038 char* tmpbuf = sub_recode(buf, bufsize, codepage);
1039 free(buf);
1040 buf = tmpbuf;
1042 if (!buf)
1043 return 0;
1044 #endif
1045 *size = bufsize;
1046 return buf;
1050 * \brief Read subtitles from file.
1051 * \param library libass library object
1052 * \param fname file name
1053 * \param codepage recode buffer contents from given codepage
1054 * \return newly allocated track
1056 ass_track_t* ass_read_file(ass_library_t* library, char* fname, char* codepage)
1058 char* buf;
1059 ass_track_t* track;
1060 size_t bufsize;
1062 buf = read_file_recode(fname, codepage, &bufsize);
1063 if (!buf)
1064 return 0;
1065 track = parse_memory(library, buf);
1066 free(buf);
1067 if (!track)
1068 return 0;
1070 track->name = strdup(fname);
1072 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_AddedSubtitleFileFname, fname, track->n_styles, track->n_events);
1074 // dump_events(forced_tid);
1075 return track;
1079 * \brief read styles from file into already initialized track
1081 int ass_read_styles(ass_track_t* track, char* fname, char* codepage)
1083 char* buf;
1084 parser_state_t old_state;
1085 size_t sz;
1087 buf = read_file(fname, &sz);
1088 if (!buf)
1089 return 1;
1090 #ifdef CONFIG_ICONV
1091 if (codepage) {
1092 char* tmpbuf;
1093 tmpbuf = sub_recode(buf, sz, codepage);
1094 free(buf);
1095 buf = tmpbuf;
1097 if (!buf)
1098 return 0;
1099 #endif
1101 old_state = track->parser_priv->state;
1102 track->parser_priv->state = PST_STYLES;
1103 process_text(track, buf);
1104 track->parser_priv->state = old_state;
1106 return 0;
1109 long long ass_step_sub(ass_track_t* track, long long now, int movement) {
1110 int i;
1112 if (movement == 0) return 0;
1113 if (track->n_events == 0) return 0;
1115 if (movement < 0)
1116 for (i = 0; (i < track->n_events) && ((long long)(track->events[i].Start + track->events[i].Duration) <= now); ++i) {}
1117 else
1118 for (i = track->n_events - 1; (i >= 0) && ((long long)(track->events[i].Start) > now); --i) {}
1120 // -1 and n_events are ok
1121 assert(i >= -1); assert(i <= track->n_events);
1122 i += movement;
1123 if (i < 0) i = 0;
1124 if (i >= track->n_events) i = track->n_events - 1;
1125 return ((long long)track->events[i].Start) - now;
1128 ass_track_t* ass_new_track(ass_library_t* library) {
1129 ass_track_t* track = calloc(1, sizeof(ass_track_t));
1130 track->library = library;
1131 track->ScaledBorderAndShadow = 1;
1132 track->parser_priv = calloc(1, sizeof(parser_priv_t));
1133 return track;