10l: comparison of char* ptrs with string literals
[mplayer.git] / libass / ass.c
blobd2c9523c7eadad32c1c05e99a62946fee9f95003
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 INTVAL(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 INTVAL(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 if (!style->Name)
462 style->Name = strdup("Default");
463 if (!style->FontName)
464 style->FontName = strdup("Arial");
465 free(format);
466 return 0;
470 static int process_styles_line(ass_track_t* track, char *str)
472 if (!strncmp(str,"Format:", 7)) {
473 char* p = str + 7;
474 skip_spaces(&p);
475 track->style_format = strdup(p);
476 mp_msg(MSGT_ASS, MSGL_DBG2, "Style format: %s\n", track->style_format);
477 } else if (!strncmp(str,"Style:", 6)) {
478 char* p = str + 6;
479 skip_spaces(&p);
480 process_style(track, p);
482 return 0;
485 static int process_info_line(ass_track_t* track, char *str)
487 if (!strncmp(str, "PlayResX:", 9)) {
488 track->PlayResX = atoi(str + 9);
489 } else if (!strncmp(str,"PlayResY:", 9)) {
490 track->PlayResY = atoi(str + 9);
491 } else if (!strncmp(str,"Timer:", 6)) {
492 track->Timer = atof(str + 6);
493 } else if (!strncmp(str,"WrapStyle:", 10)) {
494 track->WrapStyle = atoi(str + 10);
496 return 0;
499 static int process_events_line(ass_track_t* track, char *str)
501 if (!strncmp(str, "Format:", 7)) {
502 char* p = str + 7;
503 skip_spaces(&p);
504 track->event_format = strdup(p);
505 mp_msg(MSGT_ASS, MSGL_DBG2, "Event format: %s\n", track->event_format);
506 } else if (!strncmp(str, "Dialogue:", 9)) {
507 // This should never be reached for embedded subtitles.
508 // They have slightly different format and are parsed in ass_process_chunk,
509 // called directly from demuxer
510 int eid;
511 ass_event_t* event;
513 str += 9;
514 skip_spaces(&str);
516 eid = ass_alloc_event(track);
517 event = track->events + eid;
519 process_event_tail(track, event, str, 0);
520 } else {
521 mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s \n", str);
523 return 0;
526 // Copied from mkvtoolnix
527 static unsigned char* decode_chars(unsigned char c1, unsigned char c2,
528 unsigned char c3, unsigned char c4, unsigned char* dst, int cnt)
530 uint32_t value;
531 unsigned char bytes[3];
532 int i;
534 value = ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 - 33);
535 bytes[2] = value & 0xff;
536 bytes[1] = (value & 0xff00) >> 8;
537 bytes[0] = (value & 0xff0000) >> 16;
539 for (i = 0; i < cnt; ++i)
540 *dst++ = bytes[i];
541 return dst;
544 static int decode_font(ass_track_t* track)
546 unsigned char* p;
547 unsigned char* q;
548 int i;
549 int size; // original size
550 int dsize; // decoded size
551 unsigned char* buf = 0;
553 mp_msg(MSGT_ASS, MSGL_V, "font: %d bytes encoded data \n", track->parser_priv->fontdata_used);
554 size = track->parser_priv->fontdata_used;
555 if (size % 4 == 1) {
556 mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_BadEncodedDataSize);
557 goto error_decode_font;
559 buf = malloc(size / 4 * 3 + 2);
560 q = buf;
561 for (i = 0, p = (unsigned char*)track->parser_priv->fontdata; i < size / 4; i++, p+=4) {
562 q = decode_chars(p[0], p[1], p[2], p[3], q, 3);
564 if (size % 4 == 2) {
565 q = decode_chars(p[0], p[1], 0, 0, q, 1);
566 } else if (size % 4 == 3) {
567 q = decode_chars(p[0], p[1], p[2], 0, q, 2);
569 dsize = q - buf;
570 assert(dsize <= size / 4 * 3 + 2);
572 if (track->library->extract_fonts)
573 ass_add_font(track->library, track->parser_priv->fontname, (char*)buf, dsize);
575 error_decode_font:
576 if (buf) free(buf);
577 free(track->parser_priv->fontname);
578 free(track->parser_priv->fontdata);
579 track->parser_priv->fontname = 0;
580 track->parser_priv->fontdata = 0;
581 track->parser_priv->fontdata_size = 0;
582 track->parser_priv->fontdata_used = 0;
583 return 0;
586 static int process_fonts_line(ass_track_t* track, char *str)
588 int len;
590 if (!strncmp(str, "fontname:", 9)) {
591 char* p = str + 9;
592 skip_spaces(&p);
593 if (track->parser_priv->fontname) {
594 decode_font(track);
596 track->parser_priv->fontname = strdup(p);
597 mp_msg(MSGT_ASS, MSGL_V, "fontname: %s\n", track->parser_priv->fontname);
598 return 0;
601 if (!track->parser_priv->fontname) {
602 mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s \n", str);
603 return 0;
606 len = strlen(str);
607 if (len > 80) {
608 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FontLineTooLong, len, str);
609 return 0;
611 if (track->parser_priv->fontdata_used + len > track->parser_priv->fontdata_size) {
612 track->parser_priv->fontdata_size += 100 * 1024;
613 track->parser_priv->fontdata = realloc(track->parser_priv->fontdata, track->parser_priv->fontdata_size);
615 memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used, str, len);
616 track->parser_priv->fontdata_used += len;
618 return 0;
622 * \brief Parse a header line
623 * \param track track
624 * \param str string to parse, zero-terminated
626 static int process_line(ass_track_t* track, char *str)
628 if (strstr(str, "[Script Info]")) { // FIXME: strstr to skip possible BOM at the beginning of the script
629 track->parser_priv->state = PST_INFO;
630 } else if (!strncmp(str, "[V4 Styles]", 11)) {
631 track->parser_priv->state = PST_STYLES;
632 track->track_type = TRACK_TYPE_SSA;
633 } else if (!strncmp(str, "[V4+ Styles]", 12)) {
634 track->parser_priv->state = PST_STYLES;
635 track->track_type = TRACK_TYPE_ASS;
636 } else if (!strncmp(str, "[Events]", 8)) {
637 track->parser_priv->state = PST_EVENTS;
638 } else if (!strncmp(str, "[Fonts]", 7)) {
639 track->parser_priv->state = PST_FONTS;
640 } else {
641 switch (track->parser_priv->state) {
642 case PST_INFO:
643 process_info_line(track, str);
644 break;
645 case PST_STYLES:
646 process_styles_line(track, str);
647 break;
648 case PST_EVENTS:
649 process_events_line(track, str);
650 break;
651 case PST_FONTS:
652 process_fonts_line(track, str);
653 break;
654 default:
655 break;
659 // there is no explicit end-of-font marker in ssa/ass
660 if ((track->parser_priv->state != PST_FONTS) && (track->parser_priv->fontname))
661 decode_font(track);
663 return 0;
666 static int process_text(ass_track_t* track, char* str)
668 char* p = str;
669 while(1) {
670 char* q;
671 for (;((*p=='\r')||(*p=='\n'));++p) {}
672 for (q=p; ((*q!='\0')&&(*q!='\r')&&(*q!='\n')); ++q) {};
673 if (q==p)
674 break;
675 if (*q != '\0')
676 *(q++) = '\0';
677 process_line(track, p);
678 if (*q == '\0')
679 break;
680 p = q;
682 return 0;
686 * \brief Process CodecPrivate section of subtitle stream
687 * \param track track
688 * \param data string to parse
689 * \param size length of data
690 CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections
692 void ass_process_codec_private(ass_track_t* track, char *data, int size)
694 char* str = malloc(size + 1);
695 int sid;
697 memcpy(str, data, size);
698 str[size] = '\0';
700 process_text(track, str);
701 free(str);
703 // add "Default" style to the end
704 // will be used if track does not contain a default style (or even does not contain styles at all)
705 sid = ass_alloc_style(track);
706 track->styles[sid].Name = strdup("Default");
707 track->styles[sid].FontName = strdup("Arial");
709 if (!track->event_format) {
710 // probably an mkv produced by ancient mkvtoolnix
711 // such files don't have [Events] and Format: headers
712 track->parser_priv->state = PST_EVENTS;
713 if (track->track_type == TRACK_TYPE_SSA)
714 track->event_format = strdup("Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text");
715 else
716 track->event_format = strdup("Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text");
719 process_force_style(track);
722 static int check_duplicate_event(ass_track_t* track, int ReadOrder)
724 int i;
725 for (i = 0; i<track->n_events - 1; ++i) // ignoring last event, it is the one we are comparing with
726 if (track->events[i].ReadOrder == ReadOrder)
727 return 1;
728 return 0;
732 * \brief Process a chunk of subtitle stream data. In matroska, this containes exactly 1 event (or a commentary)
733 * \param track track
734 * \param data string to parse
735 * \param size length of data
736 * \param timecode starting time of the event (milliseconds)
737 * \param duration duration of the event (milliseconds)
739 void ass_process_chunk(ass_track_t* track, char *data, int size, long long timecode, long long duration)
741 char* str;
742 int eid;
743 char* p;
744 char* token;
745 ass_event_t* event;
747 if (!track->event_format) {
748 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EventFormatHeaderMissing);
749 return;
752 str = malloc(size + 1);
753 memcpy(str, data, size);
754 str[size] = '\0';
755 mp_msg(MSGT_ASS, MSGL_V, "event at %" PRId64 ", +%" PRId64 ": %s \n", (int64_t)timecode, (int64_t)duration, str);
757 eid = ass_alloc_event(track);
758 event = track->events + eid;
760 p = str;
762 do {
763 NEXT(p, token);
764 event->ReadOrder = atoi(token);
765 if (check_duplicate_event(track, event->ReadOrder))
766 break;
768 NEXT(p, token);
769 event->Layer = atoi(token);
771 process_event_tail(track, event, p, 3);
773 event->Start = timecode;
774 event->Duration = duration;
776 free(str);
777 return;
778 // dump_events(tid);
779 } while (0);
780 // some error
781 ass_free_event(track, eid);
782 track->n_events--;
783 free(str);
786 #ifdef USE_ICONV
787 /** \brief recode buffer to utf-8
788 * constraint: codepage != 0
789 * \param data pointer to text buffer
790 * \param size buffer size
791 * \return a pointer to recoded buffer, caller is responsible for freeing it
793 static char* sub_recode(char* data, size_t size, char* codepage)
795 static iconv_t icdsc = (iconv_t)(-1);
796 char* tocp = "UTF-8";
797 char* outbuf;
798 assert(codepage);
801 char* cp_tmp = codepage ? strdup(codepage) : 0;
802 #ifdef HAVE_ENCA
803 char enca_lang[3], enca_fallback[100];
804 if (sscanf(codepage, "enca:%2s:%99s", enca_lang, enca_fallback) == 2
805 || sscanf(codepage, "ENCA:%2s:%99s", enca_lang, enca_fallback) == 2) {
806 cp_tmp = guess_buffer_cp((unsigned char*)data, size, enca_lang, enca_fallback);
808 #endif
809 if ((icdsc = iconv_open (tocp, cp_tmp)) != (iconv_t)(-1)){
810 mp_msg(MSGT_ASS,MSGL_V,"LIBSUB: opened iconv descriptor.\n");
811 } else
812 mp_msg(MSGT_ASS,MSGL_ERR,MSGTR_LIBASS_ErrorOpeningIconvDescriptor);
813 #ifdef HAVE_ENCA
814 if (cp_tmp) free(cp_tmp);
815 #endif
819 size_t osize = size;
820 size_t ileft = size;
821 size_t oleft = size - 1;
822 char* ip;
823 char* op;
824 size_t rc;
826 outbuf = malloc(size);
827 ip = data;
828 op = outbuf;
830 while (ileft) {
831 rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
832 if (rc == (size_t)(-1)) {
833 if (errno == E2BIG) {
834 int offset = op - outbuf;
835 outbuf = (char*)realloc(outbuf, osize + size);
836 op = outbuf + offset;
837 osize += size;
838 oleft += size;
839 } else {
840 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_ErrorRecodingFile);
841 return NULL;
845 outbuf[osize - oleft - 1] = 0;
848 if (icdsc != (iconv_t)(-1)) {
849 (void)iconv_close(icdsc);
850 icdsc = (iconv_t)(-1);
851 mp_msg(MSGT_ASS,MSGL_V,"LIBSUB: closed iconv descriptor.\n");
854 return outbuf;
856 #endif // ICONV
859 * \brief read file contents into newly allocated buffer
860 * \param fname file name
861 * \param bufsize out: file size
862 * \return pointer to file contents. Caller is responsible for its deallocation.
864 static char* read_file(char* fname, size_t *bufsize)
866 int res;
867 long sz;
868 long bytes_read;
869 char* buf;
871 FILE* fp = fopen(fname, "rb");
872 if (!fp) {
873 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FopenFailed, fname);
874 return 0;
876 res = fseek(fp, 0, SEEK_END);
877 if (res == -1) {
878 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FseekFailed, fname);
879 fclose(fp);
880 return 0;
883 sz = ftell(fp);
884 rewind(fp);
886 if (sz > 10*1024*1024) {
887 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_RefusingToLoadSubtitlesLargerThan10M, fname);
888 fclose(fp);
889 return 0;
892 mp_msg(MSGT_ASS, MSGL_V, "file size: %ld\n", sz);
894 buf = malloc(sz + 1);
895 assert(buf);
896 bytes_read = 0;
897 do {
898 res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
899 if (res <= 0) {
900 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_ReadFailed, errno, strerror(errno));
901 fclose(fp);
902 free(buf);
903 return 0;
905 bytes_read += res;
906 } while (sz - bytes_read > 0);
907 buf[sz] = '\0';
908 fclose(fp);
910 if (bufsize)
911 *bufsize = sz;
912 return buf;
916 * \param buf pointer to subtitle text in utf-8
918 static ass_track_t* parse_memory(ass_library_t* library, char* buf)
920 ass_track_t* track;
921 int i;
923 track = ass_new_track(library);
925 // process header
926 process_text(track, buf);
928 // external SSA/ASS subs does not have ReadOrder field
929 for (i = 0; i < track->n_events; ++i)
930 track->events[i].ReadOrder = i;
932 // there is no explicit end-of-font marker in ssa/ass
933 if (track->parser_priv->fontname)
934 decode_font(track);
936 if (track->track_type == TRACK_TYPE_UNKNOWN) {
937 ass_free_track(track);
938 return 0;
941 process_force_style(track);
943 return track;
947 * \brief Read subtitles from memory.
948 * \param library libass library object
949 * \param buf pointer to subtitles text
950 * \param bufsize size of buffer
951 * \param codepage recode buffer contents from given codepage
952 * \return newly allocated track
954 ass_track_t* ass_read_memory(ass_library_t* library, char* buf, size_t bufsize, char* codepage)
956 ass_track_t* track;
957 int need_free = 0;
959 if (!buf)
960 return 0;
962 #ifdef USE_ICONV
963 if (codepage)
964 buf = sub_recode(buf, bufsize, codepage);
965 if (!buf)
966 return 0;
967 else
968 need_free = 1;
969 #endif
970 track = parse_memory(library, buf);
971 if (need_free)
972 free(buf);
973 if (!track)
974 return 0;
976 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_AddedSubtitleFileMemory, track->n_styles, track->n_events);
977 return track;
981 * \brief Read subtitles from file.
982 * \param library libass library object
983 * \param fname file name
984 * \param codepage recode buffer contents from given codepage
985 * \return newly allocated track
987 ass_track_t* ass_read_file(ass_library_t* library, char* fname, char* codepage)
989 char* buf;
990 ass_track_t* track;
991 size_t bufsize;
993 buf = read_file(fname, &bufsize);
994 if (!buf)
995 return 0;
996 #ifdef USE_ICONV
997 if (codepage) {
998 char* tmpbuf = sub_recode(buf, bufsize, codepage);
999 free(buf);
1000 buf = tmpbuf;
1002 if (!buf)
1003 return 0;
1004 #endif
1005 track = parse_memory(library, buf);
1006 free(buf);
1007 if (!track)
1008 return 0;
1010 track->name = strdup(fname);
1012 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_AddedSubtitleFileFname, fname, track->n_styles, track->n_events);
1014 // dump_events(forced_tid);
1015 return track;
1019 * \brief read styles from file into already initialized track
1021 int ass_read_styles(ass_track_t* track, char* fname, char* codepage)
1023 char* buf;
1024 parser_state_t old_state;
1025 size_t sz;
1027 buf = read_file(fname, &sz);
1028 if (!buf)
1029 return 1;
1030 #ifdef USE_ICONV
1031 if (codepage) {
1032 char* tmpbuf;
1033 tmpbuf = sub_recode(buf, sz, codepage);
1034 free(buf);
1035 buf = tmpbuf;
1037 if (!buf)
1038 return 0;
1039 #endif
1041 old_state = track->parser_priv->state;
1042 track->parser_priv->state = PST_STYLES;
1043 process_text(track, buf);
1044 track->parser_priv->state = old_state;
1046 return 0;
1049 long long ass_step_sub(ass_track_t* track, long long now, int movement) {
1050 int i;
1052 if (movement == 0) return 0;
1053 if (track->n_events == 0) return 0;
1055 if (movement < 0)
1056 for (i = 0; (i < track->n_events) && ((long long)(track->events[i].Start + track->events[i].Duration) <= now); ++i) {}
1057 else
1058 for (i = track->n_events - 1; (i >= 0) && ((long long)(track->events[i].Start) > now); --i) {}
1060 // -1 and n_events are ok
1061 assert(i >= -1); assert(i <= track->n_events);
1062 i += movement;
1063 if (i < 0) i = 0;
1064 if (i >= track->n_events) i = track->n_events - 1;
1065 return ((long long)track->events[i].Start) - now;
1068 ass_track_t* ass_new_track(ass_library_t* library) {
1069 ass_track_t* track = calloc(1, sizeof(ass_track_t));
1070 track->library = library;
1071 track->parser_priv = calloc(1, sizeof(parser_priv_t));
1072 return track;