consistency cosmetics
[mplayer/greg.git] / libass / ass.c
blob5cc04fdba71f14cafdc2181a0566dea1ab39d6e0
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 if (track->n_styles == 0) {
275 // add "Default" style to the end
276 // will be used if track does not contain a default style (or even does not contain styles at all)
277 int sid = ass_alloc_style(track);
278 track->styles[sid].Name = strdup("Default");
279 track->styles[sid].FontName = strdup("Arial");
282 for (i = 0; i < n_ignored; ++i) {
283 NEXT(q, tname);
286 while (1) {
287 NEXT(q, tname);
288 if (strcasecmp(tname, "Text") == 0) {
289 char* last;
290 event->Text = strdup(p);
291 if (*event->Text != 0) {
292 last = event->Text + strlen(event->Text) - 1;
293 if (last >= event->Text && *last == '\r')
294 *last = 0;
296 mp_msg(MSGT_ASS, MSGL_DBG2, "Text = %s\n", event->Text);
297 event->Duration -= event->Start;
298 free(format);
299 return 0; // "Text" is always the last
301 NEXT(p, token);
303 ALIAS(End,Duration) // temporarily store end timecode in event->Duration
304 if (0) { // cool ;)
305 INTVAL(Layer)
306 STYLEVAL(Style)
307 STRVAL(Name)
308 STRVAL(Effect)
309 INTVAL(MarginL)
310 INTVAL(MarginR)
311 INTVAL(MarginV)
312 TIMEVAL(Start)
313 TIMEVAL(Duration)
316 free(format);
317 return 1;
321 * \brief Parse command line style overrides (--ass-force-style option)
322 * \param track track to apply overrides to
323 * The format for overrides is [StyleName.]Field=Value
325 void process_force_style(ass_track_t* track) {
326 char **fs, *eq, *dt, *style, *tname, *token;
327 ass_style_t* target;
328 int sid;
329 char** list = track->library->style_overrides;
331 if (!list) return;
333 for (fs = list; *fs; ++fs) {
334 eq = strrchr(*fs, '=');
335 if (!eq)
336 continue;
337 *eq = '\0';
338 token = eq + 1;
340 dt = strrchr(*fs, '.');
341 if (dt) {
342 *dt = '\0';
343 style = *fs;
344 tname = dt + 1;
345 } else {
346 style = NULL;
347 tname = *fs;
349 for (sid = 0; sid < track->n_styles; ++sid) {
350 if (style == NULL || strcasecmp(track->styles[sid].Name, style) == 0) {
351 target = track->styles + sid;
352 if (0) {
353 STRVAL(FontName)
354 COLORVAL(PrimaryColour)
355 COLORVAL(SecondaryColour)
356 COLORVAL(OutlineColour)
357 COLORVAL(BackColour)
358 FPVAL(FontSize)
359 INTVAL(Bold)
360 INTVAL(Italic)
361 INTVAL(Underline)
362 INTVAL(StrikeOut)
363 FPVAL(Spacing)
364 INTVAL(Angle)
365 INTVAL(BorderStyle)
366 INTVAL(Alignment)
367 INTVAL(MarginL)
368 INTVAL(MarginR)
369 INTVAL(MarginV)
370 INTVAL(Encoding)
371 FPVAL(ScaleX)
372 FPVAL(ScaleY)
373 FPVAL(Outline)
374 FPVAL(Shadow)
378 *eq = '=';
379 if (dt) *dt = '.';
384 * \brief Parse the Style line
385 * \param track track
386 * \param str string to parse, zero-terminated
387 * Allocates a new style struct.
389 static int process_style(ass_track_t* track, char *str)
392 char* token;
393 char* tname;
394 char* p = str;
395 char* format;
396 char* q; // format scanning pointer
397 int sid;
398 ass_style_t* style;
399 ass_style_t* target;
401 if (!track->style_format) {
402 // no style format header
403 // probably an ancient script version
404 if (track->track_type == TRACK_TYPE_SSA)
405 track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
406 "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline,"
407 "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
408 else
409 track->style_format = strdup("Name, Fontname, Fontsize, PrimaryColour, SecondaryColour,"
410 "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut,"
411 "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow,"
412 "Alignment, MarginL, MarginR, MarginV, Encoding");
415 q = format = strdup(track->style_format);
417 mp_msg(MSGT_ASS, MSGL_V, "[%p] Style: %s\n", track, str);
419 sid = ass_alloc_style(track);
421 style = track->styles + sid;
422 target = style;
423 // fill style with some default values
424 style->ScaleX = 100.;
425 style->ScaleY = 100.;
427 while (1) {
428 NEXT(q, tname);
429 NEXT(p, token);
431 // ALIAS(TertiaryColour,OutlineColour) // ignore TertiaryColour; it appears only in SSA, and is overridden by BackColour
433 if (0) { // cool ;)
434 STRVAL(Name)
435 if ((strcmp(target->Name, "Default")==0) || (strcmp(target->Name, "*Default")==0))
436 track->default_style = sid;
437 STRVAL(FontName)
438 COLORVAL(PrimaryColour)
439 COLORVAL(SecondaryColour)
440 COLORVAL(OutlineColour) // TertiaryColor
441 COLORVAL(BackColour)
442 // SSA uses BackColour for both outline and shadow
443 // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
444 if (track->track_type == TRACK_TYPE_SSA)
445 target->OutlineColour = target->BackColour;
446 FPVAL(FontSize)
447 INTVAL(Bold)
448 INTVAL(Italic)
449 INTVAL(Underline)
450 INTVAL(StrikeOut)
451 FPVAL(Spacing)
452 INTVAL(Angle)
453 INTVAL(BorderStyle)
454 INTVAL(Alignment)
455 if (track->track_type == TRACK_TYPE_ASS)
456 target->Alignment = numpad2align(target->Alignment);
457 INTVAL(MarginL)
458 INTVAL(MarginR)
459 INTVAL(MarginV)
460 INTVAL(Encoding)
461 FPVAL(ScaleX)
462 FPVAL(ScaleY)
463 FPVAL(Outline)
464 FPVAL(Shadow)
467 style->ScaleX /= 100.;
468 style->ScaleY /= 100.;
469 style->Bold = !!style->Bold;
470 style->Italic = !!style->Italic;
471 style->Underline = !!style->Underline;
472 if (!style->Name)
473 style->Name = strdup("Default");
474 if (!style->FontName)
475 style->FontName = strdup("Arial");
476 free(format);
477 return 0;
481 static int process_styles_line(ass_track_t* track, char *str)
483 if (!strncmp(str,"Format:", 7)) {
484 char* p = str + 7;
485 skip_spaces(&p);
486 track->style_format = strdup(p);
487 mp_msg(MSGT_ASS, MSGL_DBG2, "Style format: %s\n", track->style_format);
488 } else if (!strncmp(str,"Style:", 6)) {
489 char* p = str + 6;
490 skip_spaces(&p);
491 process_style(track, p);
493 return 0;
496 static int process_info_line(ass_track_t* track, char *str)
498 if (!strncmp(str, "PlayResX:", 9)) {
499 track->PlayResX = atoi(str + 9);
500 } else if (!strncmp(str,"PlayResY:", 9)) {
501 track->PlayResY = atoi(str + 9);
502 } else if (!strncmp(str,"Timer:", 6)) {
503 track->Timer = atof(str + 6);
504 } else if (!strncmp(str,"WrapStyle:", 10)) {
505 track->WrapStyle = atoi(str + 10);
507 return 0;
510 static int process_events_line(ass_track_t* track, char *str)
512 if (!strncmp(str, "Format:", 7)) {
513 char* p = str + 7;
514 skip_spaces(&p);
515 track->event_format = strdup(p);
516 mp_msg(MSGT_ASS, MSGL_DBG2, "Event format: %s\n", track->event_format);
517 } else if (!strncmp(str, "Dialogue:", 9)) {
518 // This should never be reached for embedded subtitles.
519 // They have slightly different format and are parsed in ass_process_chunk,
520 // called directly from demuxer
521 int eid;
522 ass_event_t* event;
524 str += 9;
525 skip_spaces(&str);
527 eid = ass_alloc_event(track);
528 event = track->events + eid;
530 process_event_tail(track, event, str, 0);
531 } else {
532 mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s \n", str);
534 return 0;
537 // Copied from mkvtoolnix
538 static unsigned char* decode_chars(unsigned char c1, unsigned char c2,
539 unsigned char c3, unsigned char c4, unsigned char* dst, int cnt)
541 uint32_t value;
542 unsigned char bytes[3];
543 int i;
545 value = ((c1 - 33) << 18) + ((c2 - 33) << 12) + ((c3 - 33) << 6) + (c4 - 33);
546 bytes[2] = value & 0xff;
547 bytes[1] = (value & 0xff00) >> 8;
548 bytes[0] = (value & 0xff0000) >> 16;
550 for (i = 0; i < cnt; ++i)
551 *dst++ = bytes[i];
552 return dst;
555 static int decode_font(ass_track_t* track)
557 unsigned char* p;
558 unsigned char* q;
559 int i;
560 int size; // original size
561 int dsize; // decoded size
562 unsigned char* buf = 0;
564 mp_msg(MSGT_ASS, MSGL_V, "font: %d bytes encoded data \n", track->parser_priv->fontdata_used);
565 size = track->parser_priv->fontdata_used;
566 if (size % 4 == 1) {
567 mp_msg(MSGT_ASS, MSGL_ERR, MSGTR_LIBASS_BadEncodedDataSize);
568 goto error_decode_font;
570 buf = malloc(size / 4 * 3 + 2);
571 q = buf;
572 for (i = 0, p = (unsigned char*)track->parser_priv->fontdata; i < size / 4; i++, p+=4) {
573 q = decode_chars(p[0], p[1], p[2], p[3], q, 3);
575 if (size % 4 == 2) {
576 q = decode_chars(p[0], p[1], 0, 0, q, 1);
577 } else if (size % 4 == 3) {
578 q = decode_chars(p[0], p[1], p[2], 0, q, 2);
580 dsize = q - buf;
581 assert(dsize <= size / 4 * 3 + 2);
583 if (track->library->extract_fonts) {
584 ass_add_font(track->library, track->parser_priv->fontname, (char*)buf, dsize);
585 buf = 0;
588 error_decode_font:
589 if (buf) free(buf);
590 free(track->parser_priv->fontname);
591 free(track->parser_priv->fontdata);
592 track->parser_priv->fontname = 0;
593 track->parser_priv->fontdata = 0;
594 track->parser_priv->fontdata_size = 0;
595 track->parser_priv->fontdata_used = 0;
596 return 0;
599 static int process_fonts_line(ass_track_t* track, char *str)
601 int len;
603 if (!strncmp(str, "fontname:", 9)) {
604 char* p = str + 9;
605 skip_spaces(&p);
606 if (track->parser_priv->fontname) {
607 decode_font(track);
609 track->parser_priv->fontname = strdup(p);
610 mp_msg(MSGT_ASS, MSGL_V, "fontname: %s\n", track->parser_priv->fontname);
611 return 0;
614 if (!track->parser_priv->fontname) {
615 mp_msg(MSGT_ASS, MSGL_V, "Not understood: %s \n", str);
616 return 0;
619 len = strlen(str);
620 if (len > 80) {
621 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_FontLineTooLong, len, str);
622 return 0;
624 if (track->parser_priv->fontdata_used + len > track->parser_priv->fontdata_size) {
625 track->parser_priv->fontdata_size += 100 * 1024;
626 track->parser_priv->fontdata = realloc(track->parser_priv->fontdata, track->parser_priv->fontdata_size);
628 memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used, str, len);
629 track->parser_priv->fontdata_used += len;
631 return 0;
635 * \brief Parse a header line
636 * \param track track
637 * \param str string to parse, zero-terminated
639 static int process_line(ass_track_t* track, char *str)
641 if (strstr(str, "[Script Info]")) { // FIXME: strstr to skip possible BOM at the beginning of the script
642 track->parser_priv->state = PST_INFO;
643 } else if (!strncmp(str, "[V4 Styles]", 11)) {
644 track->parser_priv->state = PST_STYLES;
645 track->track_type = TRACK_TYPE_SSA;
646 } else if (!strncmp(str, "[V4+ Styles]", 12)) {
647 track->parser_priv->state = PST_STYLES;
648 track->track_type = TRACK_TYPE_ASS;
649 } else if (!strncmp(str, "[Events]", 8)) {
650 track->parser_priv->state = PST_EVENTS;
651 } else if (!strncmp(str, "[Fonts]", 7)) {
652 track->parser_priv->state = PST_FONTS;
653 } else {
654 switch (track->parser_priv->state) {
655 case PST_INFO:
656 process_info_line(track, str);
657 break;
658 case PST_STYLES:
659 process_styles_line(track, str);
660 break;
661 case PST_EVENTS:
662 process_events_line(track, str);
663 break;
664 case PST_FONTS:
665 process_fonts_line(track, str);
666 break;
667 default:
668 break;
672 // there is no explicit end-of-font marker in ssa/ass
673 if ((track->parser_priv->state != PST_FONTS) && (track->parser_priv->fontname))
674 decode_font(track);
676 return 0;
679 static int process_text(ass_track_t* track, char* str)
681 char* p = str;
682 while(1) {
683 char* q;
684 for (;((*p=='\r')||(*p=='\n'));++p) {}
685 for (q=p; ((*q!='\0')&&(*q!='\r')&&(*q!='\n')); ++q) {};
686 if (q==p)
687 break;
688 if (*q != '\0')
689 *(q++) = '\0';
690 process_line(track, p);
691 if (*q == '\0')
692 break;
693 p = q;
695 return 0;
699 * \brief Process CodecPrivate section of subtitle stream
700 * \param track track
701 * \param data string to parse
702 * \param size length of data
703 CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections
705 void ass_process_codec_private(ass_track_t* track, char *data, int size)
707 char* str = malloc(size + 1);
709 memcpy(str, data, size);
710 str[size] = '\0';
712 process_text(track, str);
713 free(str);
715 if (!track->event_format) {
716 // probably an mkv produced by ancient mkvtoolnix
717 // such files don't have [Events] and Format: headers
718 track->parser_priv->state = PST_EVENTS;
719 if (track->track_type == TRACK_TYPE_SSA)
720 track->event_format = strdup("Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text");
721 else
722 track->event_format = strdup("Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text");
725 process_force_style(track);
728 static int check_duplicate_event(ass_track_t* track, int ReadOrder)
730 int i;
731 for (i = 0; i<track->n_events - 1; ++i) // ignoring last event, it is the one we are comparing with
732 if (track->events[i].ReadOrder == ReadOrder)
733 return 1;
734 return 0;
738 * \brief Process a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary).
739 * \param track track
740 * \param data string to parse
741 * \param size length of data
742 * \param timecode starting time of the event (milliseconds)
743 * \param duration duration of the event (milliseconds)
745 void ass_process_chunk(ass_track_t* track, char *data, int size, long long timecode, long long duration)
747 char* str;
748 int eid;
749 char* p;
750 char* token;
751 ass_event_t* event;
753 if (!track->event_format) {
754 mp_msg(MSGT_ASS, MSGL_WARN, MSGTR_LIBASS_EventFormatHeaderMissing);
755 return;
758 str = malloc(size + 1);
759 memcpy(str, data, size);
760 str[size] = '\0';
761 mp_msg(MSGT_ASS, MSGL_V, "event at %" PRId64 ", +%" PRId64 ": %s \n", (int64_t)timecode, (int64_t)duration, str);
763 eid = ass_alloc_event(track);
764 event = track->events + eid;
766 p = str;
768 do {
769 NEXT(p, token);
770 event->ReadOrder = atoi(token);
771 if (check_duplicate_event(track, event->ReadOrder))
772 break;
774 NEXT(p, token);
775 event->Layer = atoi(token);
777 process_event_tail(track, event, p, 3);
779 event->Start = timecode;
780 event->Duration = duration;
782 free(str);
783 return;
784 // dump_events(tid);
785 } while (0);
786 // some error
787 ass_free_event(track, eid);
788 track->n_events--;
789 free(str);
792 #ifdef USE_ICONV
793 /** \brief recode buffer to utf-8
794 * constraint: codepage != 0
795 * \param data pointer to text buffer
796 * \param size buffer size
797 * \return a pointer to recoded buffer, caller is responsible for freeing it
799 static char* sub_recode(char* data, size_t size, char* codepage)
801 static iconv_t icdsc = (iconv_t)(-1);
802 char* tocp = "UTF-8";
803 char* outbuf;
804 assert(codepage);
807 const char* cp_tmp = codepage;
808 #ifdef HAVE_ENCA
809 char enca_lang[3], enca_fallback[100];
810 if (sscanf(codepage, "enca:%2s:%99s", enca_lang, enca_fallback) == 2
811 || sscanf(codepage, "ENCA:%2s:%99s", enca_lang, enca_fallback) == 2) {
812 cp_tmp = guess_buffer_cp((unsigned char*)data, size, enca_lang, enca_fallback);
814 #endif
815 if ((icdsc = iconv_open (tocp, cp_tmp)) != (iconv_t)(-1)){
816 mp_msg(MSGT_ASS,MSGL_V,"LIBSUB: opened iconv descriptor.\n");
817 } else
818 mp_msg(MSGT_ASS,MSGL_ERR,MSGTR_LIBASS_ErrorOpeningIconvDescriptor);
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;
983 char* read_file_recode(char* fname, char* codepage, int* size)
985 char* buf;
986 size_t bufsize;
988 buf = read_file(fname, &bufsize);
989 if (!buf)
990 return 0;
991 #ifdef USE_ICONV
992 if (codepage) {
993 char* tmpbuf = sub_recode(buf, bufsize, codepage);
994 free(buf);
995 buf = tmpbuf;
997 if (!buf)
998 return 0;
999 #endif
1000 *size = bufsize;
1001 return buf;
1005 * \brief Read subtitles from file.
1006 * \param library libass library object
1007 * \param fname file name
1008 * \param codepage recode buffer contents from given codepage
1009 * \return newly allocated track
1011 ass_track_t* ass_read_file(ass_library_t* library, char* fname, char* codepage)
1013 char* buf;
1014 ass_track_t* track;
1015 size_t bufsize;
1017 buf = read_file_recode(fname, codepage, &bufsize);
1018 if (!buf)
1019 return 0;
1020 track = parse_memory(library, buf);
1021 free(buf);
1022 if (!track)
1023 return 0;
1025 track->name = strdup(fname);
1027 mp_msg(MSGT_ASS, MSGL_INFO, MSGTR_LIBASS_AddedSubtitleFileFname, fname, track->n_styles, track->n_events);
1029 // dump_events(forced_tid);
1030 return track;
1034 * \brief read styles from file into already initialized track
1036 int ass_read_styles(ass_track_t* track, char* fname, char* codepage)
1038 char* buf;
1039 parser_state_t old_state;
1040 size_t sz;
1042 buf = read_file(fname, &sz);
1043 if (!buf)
1044 return 1;
1045 #ifdef USE_ICONV
1046 if (codepage) {
1047 char* tmpbuf;
1048 tmpbuf = sub_recode(buf, sz, codepage);
1049 free(buf);
1050 buf = tmpbuf;
1052 if (!buf)
1053 return 0;
1054 #endif
1056 old_state = track->parser_priv->state;
1057 track->parser_priv->state = PST_STYLES;
1058 process_text(track, buf);
1059 track->parser_priv->state = old_state;
1061 return 0;
1064 long long ass_step_sub(ass_track_t* track, long long now, int movement) {
1065 int i;
1067 if (movement == 0) return 0;
1068 if (track->n_events == 0) return 0;
1070 if (movement < 0)
1071 for (i = 0; (i < track->n_events) && ((long long)(track->events[i].Start + track->events[i].Duration) <= now); ++i) {}
1072 else
1073 for (i = track->n_events - 1; (i >= 0) && ((long long)(track->events[i].Start) > now); --i) {}
1075 // -1 and n_events are ok
1076 assert(i >= -1); assert(i <= track->n_events);
1077 i += movement;
1078 if (i < 0) i = 0;
1079 if (i >= track->n_events) i = track->n_events - 1;
1080 return ((long long)track->events[i].Start) - now;
1083 ass_track_t* ass_new_track(ass_library_t* library) {
1084 ass_track_t* track = calloc(1, sizeof(ass_track_t));
1085 track->library = library;
1086 track->parser_priv = calloc(1, sizeof(parser_priv_t));
1087 return track;