synced with r22428
[mplayer/greg.git] / libass / ass.c
blob546e417972f1ac8376c8ab90af3f6c92207a7414
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 program 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 This program 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
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin St, 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 USE_ICONV
34 #include <iconv.h>
35 #endif
37 #include "ass.h"
38 #include "ass_utils.h"
39 #include "ass_library.h"
40 #include "mputils.h"
42 typedef enum {PST_UNKNOWN = 0, PST_INFO, PST_STYLES, PST_EVENTS, PST_FONTS} parser_state_t;
44 struct parser_priv_s {
45 parser_state_t state;
46 char* fontname;
47 char* fontdata;
48 int fontdata_size;
49 int fontdata_used;
52 #define ASS_STYLES_ALLOC 20
53 #define ASS_EVENTS_ALLOC 200
55 void ass_free_track(ass_track_t* track) {
56 int i;
58 if (track->parser_priv) {
59 if (track->parser_priv->fontname)
60 free(track->parser_priv->fontname);
61 if (track->parser_priv->fontdata)
62 free(track->parser_priv->fontdata);
63 free(track->parser_priv);
65 if (track->style_format)
66 free(track->style_format);
67 if (track->event_format)
68 free(track->event_format);
69 if (track->styles) {
70 for (i = 0; i < track->n_styles; ++i)
71 ass_free_style(track, i);
72 free(track->styles);
74 if (track->events) {
75 for (i = 0; i < track->n_events; ++i)
76 ass_free_event(track, i);
77 free(track->events);
81 /// \brief Allocate a new style struct
82 /// \param track track
83 /// \return style id
84 int ass_alloc_style(ass_track_t* track) {
85 int sid;
87 assert(track->n_styles <= track->max_styles);
89 if (track->n_styles == track->max_styles) {
90 track->max_styles += ASS_STYLES_ALLOC;
91 track->styles = (ass_style_t*)realloc(track->styles, sizeof(ass_style_t)*track->max_styles);
94 sid = track->n_styles++;
95 memset(track->styles + sid, 0, sizeof(ass_style_t));
96 return sid;
99 /// \brief Allocate a new event struct
100 /// \param track track
101 /// \return event id
102 int ass_alloc_event(ass_track_t* track) {
103 int eid;
105 assert(track->n_events <= track->max_events);
107 if (track->n_events == track->max_events) {
108 track->max_events += ASS_EVENTS_ALLOC;
109 track->events = (ass_event_t*)realloc(track->events, sizeof(ass_event_t)*track->max_events);
112 eid = track->n_events++;
113 memset(track->events + eid, 0, sizeof(ass_event_t));
114 return eid;
117 void ass_free_event(ass_track_t* track, int eid) {
118 ass_event_t* event = track->events + eid;
119 if (event->Name)
120 free(event->Name);
121 if (event->Effect)
122 free(event->Effect);
123 if (event->Text)
124 free(event->Text);
125 if (event->render_priv)
126 free(event->render_priv);
129 void ass_free_style(ass_track_t* track, int sid) {
130 ass_style_t* style = track->styles + sid;
131 if (style->Name)
132 free(style->Name);
133 if (style->FontName)
134 free(style->FontName);
137 // ==============================================================================================
139 static void skip_spaces(char** str) {
140 char* p = *str;
141 while ((*p==' ') || (*p=='\t'))
142 ++p;
143 *str = p;
146 static void rskip_spaces(char** str, char* limit) {
147 char* p = *str;
148 while ((p >= limit) && ((*p==' ') || (*p=='\t')))
149 --p;
150 *str = p;
154 * \brief find style by name
155 * \param track track
156 * \param name style name
157 * \return index in track->styles
158 * Returnes 0 if no styles found => expects at least 1 style.
159 * Parsing code always adds "Default" style in the end.
161 static int lookup_style(ass_track_t* track, char* name) {
162 int i;
163 if (*name == '*') ++name; // FIXME: what does '*' really mean ?
164 for (i=0; i<track->n_styles; ++i) {
165 // FIXME: mb strcasecmp ?
166 if (strcmp(track->styles[i].Name, name) == 0)
167 return i;
169 i = track->default_style;
170 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_NoStyleNamedXFoundUsingY, track, name, track->styles[i].Name);
171 return i; // use the first style
174 static uint32_t string2color(char* p) {
175 uint32_t tmp;
176 (void)strtocolor(&p, &tmp);
177 return tmp;
180 static long long string2timecode(char* p) {
181 unsigned h, m, s, ms;
182 long long tm;
183 int res = sscanf(p, "%1d:%2d:%2d.%2d", &h, &m, &s, &ms);
184 if (res < 4) {
185 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_BadTimestamp);
186 return 0;
188 tm = ((h * 60 + m) * 60 + s) * 1000 + ms * 10;
189 return tm;
193 * \brief converts numpad-style align to align.
195 static int numpad2align(int val) {
196 int res, v;
197 v = (val - 1) / 3; // 0, 1 or 2 for vertical alignment
198 if (v != 0) v = 3 - v;
199 res = ((val - 1) % 3) + 1; // horizontal alignment
200 res += v*4;
201 return res;
204 #define NEXT(str,token) \
205 token = next_token(&str); \
206 if (!token) break;
208 #define ANYVAL(name,func) \
209 } else if (strcasecmp(tname, #name) == 0) { \
210 target->name = func(token); \
211 mp_msg(MSGT_ASS, MSGL_DBG2, "%s = %s\n", #name, token);
213 #define STRVAL(name) \
214 } else if (strcasecmp(tname, #name) == 0) { \
215 if (target->name != NULL) free(target->name); \
216 target->name = strdup(token); \
217 mp_msg(MSGT_ASS, MSGL_DBG2, "%s = %s\n", #name, token);
219 #define COLORVAL(name) ANYVAL(name,string2color)
220 #define INTVAL(name) ANYVAL(name,atoi)
221 #define FPVAL(name) ANYVAL(name,atof)
222 #define TIMEVAL(name) ANYVAL(name,string2timecode)
223 #define STYLEVAL(name) \
224 } else if (strcasecmp(tname, #name) == 0) { \
225 target->name = lookup_style(track, token); \
226 mp_msg(MSGT_ASS, MSGL_DBG2, "%s = %s\n", #name, token);
228 #define ALIAS(alias,name) \
229 if (strcasecmp(tname, #alias) == 0) {tname = #name;}
231 static char* next_token(char** str) {
232 char* p = *str;
233 char* start;
234 skip_spaces(&p);
235 if (*p == '\0') {
236 *str = p;
237 return 0;
239 start = p; // start of the token
240 for (; (*p != '\0') && (*p != ','); ++p) {}
241 if (*p == '\0') {
242 *str = p; // eos found, str will point to '\0' at exit
243 } else {
244 *p = '\0';
245 *str = p + 1; // ',' found, str will point to the next char (beginning of the next token)
247 --p; // end of current token
248 rskip_spaces(&p, start);
249 if (p < start)
250 p = start; // empty token
251 else
252 ++p; // the first space character, or '\0'
253 *p = '\0';
254 return start;
257 * \brief Parse the tail of Dialogue line
258 * \param track track
259 * \param event parsed data goes here
260 * \param str string to parse, zero-terminated
261 * \param n_ignored number of format options to skip at the beginning
263 static int process_event_tail(ass_track_t* track, ass_event_t* event, char* str, int n_ignored)
265 char* token;
266 char* tname;
267 char* p = str;
268 int i;
269 ass_event_t* target = event;
271 char* format = strdup(track->event_format);
272 char* q = format; // format scanning pointer
274 for (i = 0; i < n_ignored; ++i) {
275 NEXT(q, tname);
278 while (1) {
279 NEXT(q, tname);
280 if (strcasecmp(tname, "Text") == 0) {
281 char* last;
282 event->Text = strdup(p);
283 if (*event->Text != 0) {
284 last = event->Text + strlen(event->Text) - 1;
285 if (last >= event->Text && *last == '\r')
286 *last = 0;
288 mp_msg(MSGT_ASS, MSGL_DBG2, "Text = %s\n", event->Text);
289 event->Duration -= event->Start;
290 free(format);
291 return 0; // "Text" is always the last
293 NEXT(p, token);
295 ALIAS(End,Duration) // temporarily store end timecode in event->Duration
296 if (0) { // cool ;)
297 INTVAL(Layer)
298 STYLEVAL(Style)
299 STRVAL(Name)
300 STRVAL(Effect)
301 INTVAL(MarginL)
302 INTVAL(MarginR)
303 INTVAL(MarginV)
304 TIMEVAL(Start)
305 TIMEVAL(Duration)
308 free(format);
309 return 1;
313 * \brief Parse command line style overrides (--ass-force-style option)
314 * \param track track to apply overrides to
315 * The format for overrides is [StyleName.]Field=Value
317 void process_force_style(ass_track_t* track) {
318 char **fs, *eq, *dt, *style, *tname, *token;
319 ass_style_t* target;
320 int sid;
321 char** list = track->library->style_overrides;
323 if (!list) return;
325 for (fs = list; *fs; ++fs) {
326 eq = strchr(*fs, '=');
327 if (!eq)
328 continue;
329 *eq = '\0';
330 token = eq + 1;
332 dt = strchr(*fs, '.');
333 if (dt) {
334 *dt = '\0';
335 style = *fs;
336 tname = dt + 1;
337 } else {
338 style = NULL;
339 tname = *fs;
341 for (sid = 0; sid < track->n_styles; ++sid) {
342 if (style == NULL || strcasecmp(track->styles[sid].Name, style) == 0) {
343 target = track->styles + sid;
344 if (0) {
345 STRVAL(FontName)
346 COLORVAL(PrimaryColour)
347 COLORVAL(SecondaryColour)
348 COLORVAL(OutlineColour)
349 COLORVAL(BackColour)
350 INTVAL(FontSize)
351 INTVAL(Bold)
352 INTVAL(Italic)
353 INTVAL(Underline)
354 INTVAL(StrikeOut)
355 FPVAL(Spacing)
356 INTVAL(Angle)
357 INTVAL(BorderStyle)
358 INTVAL(Alignment)
359 INTVAL(MarginL)
360 INTVAL(MarginR)
361 INTVAL(MarginV)
362 INTVAL(Encoding)
363 FPVAL(ScaleX)
364 FPVAL(ScaleY)
365 FPVAL(Outline)
366 FPVAL(Shadow)
370 *eq = '=';
371 if (dt) *dt = '.';
376 * \brief Parse the Style line
377 * \param track track
378 * \param str string to parse, zero-terminated
379 * Allocates a new style struct.
381 static int process_style(ass_track_t* track, char *str)
384 char* token;
385 char* tname;
386 char* p = str;
387 char* format;
388 char* q; // format scanning pointer
389 int sid;
390 ass_style_t* style;
391 ass_style_t* target;
393 if (!track->style_format) {
394 // no style format header
395 // probably an ancient script version
396 if (track->track_type == TRACK_TYPE_SSA)
397 track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
398 "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline,"
399 "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
400 else
401 track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
402 "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut,"
403 "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow,"
404 "Alignment, MarginL, MarginR, MarginV, Encoding");
407 q = format = strdup(track->style_format);
409 mp_msg(MSGT_ASS, MSGL_V, "[%p] Style: %s\n", track, str);
411 sid = ass_alloc_style(track);
413 style = track->styles + sid;
414 target = style;
415 // fill style with some default values
416 style->ScaleX = 100.;
417 style->ScaleY = 100.;
419 while (1) {
420 NEXT(q, tname);
421 NEXT(p, token);
423 // ALIAS(TertiaryColour,OutlineColour) // ignore TertiaryColour; it appears only in SSA, and is overridden by BackColour
425 if (0) { // cool ;)
426 STRVAL(Name)
427 if ((strcmp(target->Name, "Default")==0) || (strcmp(target->Name, "*Default")==0))
428 track->default_style = sid;
429 STRVAL(FontName)
430 COLORVAL(PrimaryColour)
431 COLORVAL(SecondaryColour)
432 COLORVAL(OutlineColour) // TertiaryColor
433 COLORVAL(BackColour)
434 // SSA uses BackColour for both outline and shadow
435 // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
436 if (track->track_type == TRACK_TYPE_SSA)
437 target->OutlineColour = target->BackColour;
438 INTVAL(FontSize)
439 INTVAL(Bold)
440 INTVAL(Italic)
441 INTVAL(Underline)
442 INTVAL(StrikeOut)
443 FPVAL(Spacing)
444 INTVAL(Angle)
445 INTVAL(BorderStyle)
446 INTVAL(Alignment)
447 if (track->track_type == TRACK_TYPE_ASS)
448 target->Alignment = numpad2align(target->Alignment);
449 INTVAL(MarginL)
450 INTVAL(MarginR)
451 INTVAL(MarginV)
452 INTVAL(Encoding)
453 FPVAL(ScaleX)
454 FPVAL(ScaleY)
455 FPVAL(Outline)
456 FPVAL(Shadow)
459 style->ScaleX /= 100.;
460 style->ScaleY /= 100.;
461 style->Bold = !!style->Bold;
462 style->Italic = !!style->Italic;
463 style->Underline = !!style->Underline;
464 if (!style->Name)
465 style->Name = strdup("Default");
466 if (!style->FontName)
467 style->FontName = strdup("Arial");
468 free(format);
469 return 0;
473 static int process_styles_line(ass_track_t* track, char *str)
475 if (!strncmp(str,"Format:", 7)) {
476 char* p = str + 7;
477 skip_spaces(&p);
478 track->style_format = strdup(p);
479 mp_msg(MSGT_ASS, MSGL_DBG2, "Style format: %s\n", track->style_format);
480 } else if (!strncmp(str,"Style:", 6)) {
481 char* p = str + 6;
482 skip_spaces(&p);
483 process_style(track, p);
485 return 0;
488 static int process_info_line(ass_track_t* track, char *str)
490 if (!strncmp(str, "PlayResX:", 9)) {
491 track->PlayResX = atoi(str + 9);
492 } else if (!strncmp(str,"PlayResY:", 9)) {
493 track->PlayResY = atoi(str + 9);
494 } else if (!strncmp(str,"Timer:", 6)) {
495 track->Timer = atof(str + 6);
496 } else if (!strncmp(str,"WrapStyle:", 10)) {
497 track->WrapStyle = atoi(str + 10);
499 return 0;
502 static int process_events_line(ass_track_t* track, char *str)
504 if (!strncmp(str, "Format:", 7)) {
505 char* p = str + 7;
506 skip_spaces(&p);
507 track->event_format = strdup(p);
508 mp_msg(MSGT_ASS, MSGL_DBG2, "Event format: %s\n", track->event_format);
509 } else if (!strncmp(str, "Dialogue:", 9)) {
510 // This should never be reached for embedded subtitles.
511 // They have slightly different format and are parsed in ass_process_chunk,
512 // called directly from demuxer
513 int eid;
514 ass_event_t* event;
516 str += 9;
517 skip_spaces(&str);
519 eid = ass_alloc_event(track);
520 event = track->events + eid;
522 process_event_tail(track, event, str, 0);
523 } else {
524 mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s \n", str);
526 return 0;
529 // Copied from mkvtoolnix
530 static unsigned char* decode_chars(unsigned char c1, unsigned char c2,
531 unsigned char c3, unsigned char c4, unsigned char* dst, int cnt)
533 uint32_t value;
534 unsigned char bytes[3];
535 int i;
537 value = ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 - 33);
538 bytes[2] = value & 0xff;
539 bytes[1] = (value & 0xff00) >> 8;
540 bytes[0] = (value & 0xff0000) >> 16;
542 for (i = 0; i < cnt; ++i)
543 *dst++ = bytes[i];
544 return dst;
547 static int decode_font(ass_track_t* track)
549 unsigned char* p;
550 unsigned char* q;
551 int i;
552 int size; // original size
553 int dsize; // decoded size
554 unsigned char* buf = 0;
556 mp_msg(MSGT_ASS, MSGL_V, "font: %d bytes encoded data \n", track->parser_priv->fontdata_used);
557 size = track->parser_priv->fontdata_used;
558 if (size % 4 == 1) {
559 mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_BadEncodedDataSize);
560 goto error_decode_font;
562 buf = malloc(size / 4 * 3 + 2);
563 q = buf;
564 for (i = 0, p = (unsigned char*)track->parser_priv->fontdata; i < size / 4; i++, p+=4) {
565 q = decode_chars(p[0], p[1], p[2], p[3], q, 3);
567 if (size % 4 == 2) {
568 q = decode_chars(p[0], p[1], 0, 0, q, 1);
569 } else if (size % 4 == 3) {
570 q = decode_chars(p[0], p[1], p[2], 0, q, 2);
572 dsize = q - buf;
573 assert(dsize <= size / 4 * 3 + 2);
575 if (track->library->extract_fonts)
576 ass_add_font(track->library, track->parser_priv->fontname, (char*)buf, dsize);
578 error_decode_font:
579 if (buf) free(buf);
580 free(track->parser_priv->fontname);
581 free(track->parser_priv->fontdata);
582 track->parser_priv->fontname = 0;
583 track->parser_priv->fontdata = 0;
584 track->parser_priv->fontdata_size = 0;
585 track->parser_priv->fontdata_used = 0;
586 return 0;
589 static int process_fonts_line(ass_track_t* track, char *str)
591 int len;
593 if (!strncmp(str, "fontname:", 9)) {
594 char* p = str + 9;
595 skip_spaces(&p);
596 if (track->parser_priv->fontname) {
597 decode_font(track);
599 track->parser_priv->fontname = strdup(p);
600 mp_msg(MSGT_ASS, MSGL_V, "fontname: %s\n", track->parser_priv->fontname);
601 return 0;
604 if (!track->parser_priv->fontname) {
605 mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s \n", str);
606 return 0;
609 len = strlen(str);
610 if (len > 80) {
611 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FontLineTooLong, len, str);
612 return 0;
614 if (track->parser_priv->fontdata_used + len > track->parser_priv->fontdata_size) {
615 track->parser_priv->fontdata_size += 100 * 1024;
616 track->parser_priv->fontdata = realloc(track->parser_priv->fontdata, track->parser_priv->fontdata_size);
618 memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used, str, len);
619 track->parser_priv->fontdata_used += len;
621 return 0;
625 * \brief Parse a header line
626 * \param track track
627 * \param str string to parse, zero-terminated
629 static int process_line(ass_track_t* track, char *str)
631 if (strstr(str, "[Script Info]")) { // FIXME: strstr to skip possible BOM at the beginning of the script
632 track->parser_priv->state = PST_INFO;
633 } else if (!strncmp(str, "[V4 Styles]", 11)) {
634 track->parser_priv->state = PST_STYLES;
635 track->track_type = TRACK_TYPE_SSA;
636 } else if (!strncmp(str, "[V4+ Styles]", 12)) {
637 track->parser_priv->state = PST_STYLES;
638 track->track_type = TRACK_TYPE_ASS;
639 } else if (!strncmp(str, "[Events]", 8)) {
640 track->parser_priv->state = PST_EVENTS;
641 } else if (!strncmp(str, "[Fonts]", 7)) {
642 track->parser_priv->state = PST_FONTS;
643 } else {
644 switch (track->parser_priv->state) {
645 case PST_INFO:
646 process_info_line(track, str);
647 break;
648 case PST_STYLES:
649 process_styles_line(track, str);
650 break;
651 case PST_EVENTS:
652 process_events_line(track, str);
653 break;
654 case PST_FONTS:
655 process_fonts_line(track, str);
656 break;
657 default:
658 break;
662 // there is no explicit end-of-font marker in ssa/ass
663 if ((track->parser_priv->state != PST_FONTS) && (track->parser_priv->fontname))
664 decode_font(track);
666 return 0;
669 static int process_text(ass_track_t* track, char* str)
671 char* p = str;
672 while(1) {
673 char* q;
674 for (;((*p=='\r')||(*p=='\n'));++p) {}
675 for (q=p; ((*q!='\0')&&(*q!='\r')&&(*q!='\n')); ++q) {};
676 if (q==p)
677 break;
678 if (*q != '\0')
679 *(q++) = '\0';
680 process_line(track, p);
681 if (*q == '\0')
682 break;
683 p = q;
685 return 0;
689 * \brief Process CodecPrivate section of subtitle stream
690 * \param track track
691 * \param data string to parse
692 * \param size length of data
693 CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections
695 void ass_process_codec_private(ass_track_t* track, char *data, int size)
697 char* str = malloc(size + 1);
698 int sid;
700 memcpy(str, data, size);
701 str[size] = '\0';
703 process_text(track, str);
704 free(str);
706 // add "Default" style to the end
707 // will be used if track does not contain a default style (or even does not contain styles at all)
708 sid = ass_alloc_style(track);
709 track->styles[sid].Name = strdup("Default");
710 track->styles[sid].FontName = strdup("Arial");
712 if (!track->event_format) {
713 // probably an mkv produced by ancient mkvtoolnix
714 // such files don't have [Events] and Format: headers
715 track->parser_priv->state = PST_EVENTS;
716 if (track->track_type == TRACK_TYPE_SSA)
717 track->event_format = strdup("Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text");
718 else
719 track->event_format = strdup("Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text");
722 process_force_style(track);
725 static int check_duplicate_event(ass_track_t* track, int ReadOrder)
727 int i;
728 for (i = 0; i<track->n_events - 1; ++i) // ignoring last event, it is the one we are comparing with
729 if (track->events[i].ReadOrder == ReadOrder)
730 return 1;
731 return 0;
735 * \brief Process a chunk of subtitle stream data. In matroska, this containes exactly 1 event (or a commentary)
736 * \param track track
737 * \param data string to parse
738 * \param size length of data
739 * \param timecode starting time of the event (milliseconds)
740 * \param duration duration of the event (milliseconds)
742 void ass_process_chunk(ass_track_t* track, char *data, int size, long long timecode, long long duration)
744 char* str;
745 int eid;
746 char* p;
747 char* token;
748 ass_event_t* event;
750 if (!track->event_format) {
751 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EventFormatHeaderMissing);
752 return;
755 str = malloc(size + 1);
756 memcpy(str, data, size);
757 str[size] = '\0';
758 mp_msg(MSGT_ASS, MSGL_V, "event at %" PRId64 ", +%" PRId64 ": %s \n", (int64_t)timecode, (int64_t)duration, str);
760 eid = ass_alloc_event(track);
761 event = track->events + eid;
763 p = str;
765 do {
766 NEXT(p, token);
767 event->ReadOrder = atoi(token);
768 if (check_duplicate_event(track, event->ReadOrder))
769 break;
771 NEXT(p, token);
772 event->Layer = atoi(token);
774 process_event_tail(track, event, p, 3);
776 event->Start = timecode;
777 event->Duration = duration;
779 free(str);
780 return;
781 // dump_events(tid);
782 } while (0);
783 // some error
784 ass_free_event(track, eid);
785 track->n_events--;
786 free(str);
789 #ifdef USE_ICONV
790 /** \brief recode buffer to utf-8
791 * constraint: codepage != 0
792 * \param data pointer to text buffer
793 * \param size buffer size
794 * \return a pointer to recoded buffer, caller is responsible for freeing it
796 static char* sub_recode(char* data, size_t size, char* codepage)
798 static iconv_t icdsc = (iconv_t)(-1);
799 char* tocp = "UTF-8";
800 char* outbuf;
801 assert(codepage);
804 char* cp_tmp = codepage ? strdup(codepage) : 0;
805 #ifdef HAVE_ENCA
806 char enca_lang[3], enca_fallback[100];
807 if (sscanf(codepage, "enca:%2s:%99s", enca_lang, enca_fallback) == 2
808 || sscanf(codepage, "ENCA:%2s:%99s", enca_lang, enca_fallback) == 2) {
809 cp_tmp = guess_buffer_cp((unsigned char*)data, size, enca_lang, enca_fallback);
811 #endif
812 if ((icdsc = iconv_open (tocp, cp_tmp)) != (iconv_t)(-1)){
813 mp_msg(MSGT_ASS,MSGL_V,"LIBSUB: opened iconv descriptor.\n");
814 } else
815 mp_msg(MSGT_ASS,MSGL_ERR,MSGTR_LIBASS_ErrorOpeningIconvDescriptor);
816 #ifdef HAVE_ENCA
817 if (cp_tmp) free(cp_tmp);
818 #endif
822 size_t osize = size;
823 size_t ileft = size;
824 size_t oleft = size - 1;
825 char* ip;
826 char* op;
827 size_t rc;
829 outbuf = malloc(size);
830 ip = data;
831 op = outbuf;
833 while (ileft) {
834 rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
835 if (rc == (size_t)(-1)) {
836 if (errno == E2BIG) {
837 int offset = op - outbuf;
838 outbuf = (char*)realloc(outbuf, osize + size);
839 op = outbuf + offset;
840 osize += size;
841 oleft += size;
842 } else {
843 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorRecodingFile);
844 return NULL;
848 outbuf[osize - oleft - 1] = 0;
851 if (icdsc != (iconv_t)(-1)) {
852 (void)iconv_close(icdsc);
853 icdsc = (iconv_t)(-1);
854 mp_msg(MSGT_ASS,MSGL_V,"LIBSUB: closed iconv descriptor.\n");
857 return outbuf;
859 #endif // ICONV
862 * \brief read file contents into newly allocated buffer
863 * \param fname file name
864 * \param bufsize out: file size
865 * \return pointer to file contents. Caller is responsible for its deallocation.
867 static char* read_file(char* fname, size_t *bufsize)
869 int res;
870 long sz;
871 long bytes_read;
872 char* buf;
874 FILE* fp = fopen(fname, "rb");
875 if (!fp) {
876 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FopenFailed, fname);
877 return 0;
879 res = fseek(fp, 0, SEEK_END);
880 if (res == -1) {
881 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FseekFailed, fname);
882 fclose(fp);
883 return 0;
886 sz = ftell(fp);
887 rewind(fp);
889 if (sz > 10*1024*1024) {
890 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_RefusingToLoadSubtitlesLargerThan10M, fname);
891 fclose(fp);
892 return 0;
895 mp_msg(MSGT_ASS, MSGL_V, "file size: %ld\n", sz);
897 buf = malloc(sz + 1);
898 assert(buf);
899 bytes_read = 0;
900 do {
901 res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
902 if (res <= 0) {
903 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_ReadFailed, errno, strerror(errno));
904 fclose(fp);
905 free(buf);
906 return 0;
908 bytes_read += res;
909 } while (sz - bytes_read > 0);
910 buf[sz] = '\0';
911 fclose(fp);
913 if (bufsize)
914 *bufsize = sz;
915 return buf;
919 * \param buf pointer to subtitle text in utf-8
921 static ass_track_t* parse_memory(ass_library_t* library, char* buf)
923 ass_track_t* track;
924 int i;
926 track = ass_new_track(library);
928 // process header
929 process_text(track, buf);
931 // external SSA/ASS subs does not have ReadOrder field
932 for (i = 0; i < track->n_events; ++i)
933 track->events[i].ReadOrder = i;
935 // there is no explicit end-of-font marker in ssa/ass
936 if (track->parser_priv->fontname)
937 decode_font(track);
939 if (track->track_type == TRACK_TYPE_UNKNOWN) {
940 ass_free_track(track);
941 return 0;
944 process_force_style(track);
946 return track;
950 * \brief Read subtitles from memory.
951 * \param library libass library object
952 * \param buf pointer to subtitles text
953 * \param bufsize size of buffer
954 * \param codepage recode buffer contents from given codepage
955 * \return newly allocated track
957 ass_track_t* ass_read_memory(ass_library_t* library, char* buf, size_t bufsize, char* codepage)
959 ass_track_t* track;
960 int need_free = 0;
962 if (!buf)
963 return 0;
965 #ifdef USE_ICONV
966 if (codepage)
967 buf = sub_recode(buf, bufsize, codepage);
968 if (!buf)
969 return 0;
970 else
971 need_free = 1;
972 #endif
973 track = parse_memory(library, buf);
974 if (need_free)
975 free(buf);
976 if (!track)
977 return 0;
979 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_AddedSubtitleFileMemory, track->n_styles, track->n_events);
980 return track;
984 * \brief Read subtitles from file.
985 * \param library libass library object
986 * \param fname file name
987 * \param codepage recode buffer contents from given codepage
988 * \return newly allocated track
990 ass_track_t* ass_read_file(ass_library_t* library, char* fname, char* codepage)
992 char* buf;
993 ass_track_t* track;
994 size_t bufsize;
996 buf = read_file(fname, &bufsize);
997 if (!buf)
998 return 0;
999 #ifdef USE_ICONV
1000 if (codepage) {
1001 char* tmpbuf = sub_recode(buf, bufsize, codepage);
1002 free(buf);
1003 buf = tmpbuf;
1005 if (!buf)
1006 return 0;
1007 #endif
1008 track = parse_memory(library, buf);
1009 free(buf);
1010 if (!track)
1011 return 0;
1013 track->name = strdup(fname);
1015 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_AddedSubtitleFileFname, fname, track->n_styles, track->n_events);
1017 // dump_events(forced_tid);
1018 return track;
1022 * \brief read styles from file into already initialized track
1024 int ass_read_styles(ass_track_t* track, char* fname, char* codepage)
1026 char* buf;
1027 parser_state_t old_state;
1028 size_t sz;
1030 buf = read_file(fname, &sz);
1031 if (!buf)
1032 return 1;
1033 #ifdef USE_ICONV
1034 if (codepage) {
1035 char* tmpbuf;
1036 tmpbuf = sub_recode(buf, sz, codepage);
1037 free(buf);
1038 buf = tmpbuf;
1040 if (!buf)
1041 return 0;
1042 #endif
1044 old_state = track->parser_priv->state;
1045 track->parser_priv->state = PST_STYLES;
1046 process_text(track, buf);
1047 track->parser_priv->state = old_state;
1049 return 0;
1052 long long ass_step_sub(ass_track_t* track, long long now, int movement) {
1053 int i;
1055 if (movement == 0) return 0;
1056 if (track->n_events == 0) return 0;
1058 if (movement < 0)
1059 for (i = 0; (i < track->n_events) && ((long long)(track->events[i].Start + track->events[i].Duration) <= now); ++i) {}
1060 else
1061 for (i = track->n_events - 1; (i >= 0) && ((long long)(track->events[i].Start) > now); --i) {}
1063 // -1 and n_events are ok
1064 assert(i >= -1); assert(i <= track->n_events);
1065 i += movement;
1066 if (i < 0) i = 0;
1067 if (i >= track->n_events) i = track->n_events - 1;
1068 return ((long long)track->events[i].Start) - now;
1071 ass_track_t* ass_new_track(ass_library_t* library) {
1072 ass_track_t* track = calloc(1, sizeof(ass_track_t));
1073 track->library = library;
1074 track->parser_priv = calloc(1, sizeof(parser_priv_t));
1075 return track;