1/* 2 * SSA/ASS spliting functions 3 * Copyright (c) 2010 Aurelien Jacobs <aurel@gnuage.org> 4 * 5 * This file is part of FFmpeg. 6 * 7 * FFmpeg is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * License as published by the Free Software Foundation; either 10 * version 2.1 of the License, or (at your option) any later version. 11 * 12 * FFmpeg is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with FFmpeg; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 */ 21 22#include <limits.h> 23#include <stddef.h> 24#include <stdint.h> 25#include <stdio.h> 26#include <string.h> 27 28#include "libavutil/error.h" 29#include "libavutil/macros.h" 30#include "libavutil/mem.h" 31#include "ass_split.h" 32 33typedef enum { 34 ASS_STR, 35 ASS_INT, 36 ASS_FLT, 37 ASS_COLOR, 38 ASS_TIMESTAMP, 39 ASS_ALGN, 40} ASSFieldType; 41 42typedef struct { 43 const char *name; 44 int type; 45 int offset; 46} ASSFields; 47 48typedef struct { 49 const char *section; 50 const char *format_header; 51 const char *fields_header; 52 int size; 53 int offset; 54 int offset_count; 55 ASSFields fields[24]; 56} ASSSection; 57 58static const ASSSection ass_sections[] = { 59 { .section = "Script Info", 60 .offset = offsetof(ASS, script_info), 61 .fields = {{"ScriptType", ASS_STR, offsetof(ASSScriptInfo, script_type)}, 62 {"Collisions", ASS_STR, offsetof(ASSScriptInfo, collisions) }, 63 {"PlayResX", ASS_INT, offsetof(ASSScriptInfo, play_res_x) }, 64 {"PlayResY", ASS_INT, offsetof(ASSScriptInfo, play_res_y) }, 65 {"Timer", ASS_FLT, offsetof(ASSScriptInfo, timer) }, 66 {0}, 67 } 68 }, 69 { .section = "V4+ Styles", 70 .format_header = "Format", 71 .fields_header = "Style", 72 .size = sizeof(ASSStyle), 73 .offset = offsetof(ASS, styles), 74 .offset_count = offsetof(ASS, styles_count), 75 .fields = {{"Name", ASS_STR, offsetof(ASSStyle, name) }, 76 {"Fontname", ASS_STR, offsetof(ASSStyle, font_name) }, 77 {"Fontsize", ASS_INT, offsetof(ASSStyle, font_size) }, 78 {"PrimaryColour", ASS_COLOR, offsetof(ASSStyle, primary_color) }, 79 {"SecondaryColour", ASS_COLOR, offsetof(ASSStyle, secondary_color)}, 80 {"OutlineColour", ASS_COLOR, offsetof(ASSStyle, outline_color) }, 81 {"BackColour", ASS_COLOR, offsetof(ASSStyle, back_color) }, 82 {"Bold", ASS_INT, offsetof(ASSStyle, bold) }, 83 {"Italic", ASS_INT, offsetof(ASSStyle, italic) }, 84 {"Underline", ASS_INT, offsetof(ASSStyle, underline) }, 85 {"StrikeOut", ASS_INT, offsetof(ASSStyle, strikeout) }, 86 {"ScaleX", ASS_FLT, offsetof(ASSStyle, scalex) }, 87 {"ScaleY", ASS_FLT, offsetof(ASSStyle, scaley) }, 88 {"Spacing", ASS_FLT, offsetof(ASSStyle, spacing) }, 89 {"Angle", ASS_FLT, offsetof(ASSStyle, angle) }, 90 {"BorderStyle", ASS_INT, offsetof(ASSStyle, border_style) }, 91 {"Outline", ASS_FLT, offsetof(ASSStyle, outline) }, 92 {"Shadow", ASS_FLT, offsetof(ASSStyle, shadow) }, 93 {"Alignment", ASS_INT, offsetof(ASSStyle, alignment) }, 94 {"MarginL", ASS_INT, offsetof(ASSStyle, margin_l) }, 95 {"MarginR", ASS_INT, offsetof(ASSStyle, margin_r) }, 96 {"MarginV", ASS_INT, offsetof(ASSStyle, margin_v) }, 97 {"Encoding", ASS_INT, offsetof(ASSStyle, encoding) }, 98 {0}, 99 } 100 }, 101 { .section = "V4 Styles", 102 .format_header = "Format", 103 .fields_header = "Style", 104 .size = sizeof(ASSStyle), 105 .offset = offsetof(ASS, styles), 106 .offset_count = offsetof(ASS, styles_count), 107 .fields = {{"Name", ASS_STR, offsetof(ASSStyle, name) }, 108 {"Fontname", ASS_STR, offsetof(ASSStyle, font_name) }, 109 {"Fontsize", ASS_INT, offsetof(ASSStyle, font_size) }, 110 {"PrimaryColour", ASS_COLOR, offsetof(ASSStyle, primary_color) }, 111 {"SecondaryColour", ASS_COLOR, offsetof(ASSStyle, secondary_color)}, 112 {"TertiaryColour", ASS_COLOR, offsetof(ASSStyle, outline_color) }, 113 {"BackColour", ASS_COLOR, offsetof(ASSStyle, back_color) }, 114 {"Bold", ASS_INT, offsetof(ASSStyle, bold) }, 115 {"Italic", ASS_INT, offsetof(ASSStyle, italic) }, 116 {"BorderStyle", ASS_INT, offsetof(ASSStyle, border_style) }, 117 {"Outline", ASS_FLT, offsetof(ASSStyle, outline) }, 118 {"Shadow", ASS_FLT, offsetof(ASSStyle, shadow) }, 119 {"Alignment", ASS_ALGN, offsetof(ASSStyle, alignment) }, 120 {"MarginL", ASS_INT, offsetof(ASSStyle, margin_l) }, 121 {"MarginR", ASS_INT, offsetof(ASSStyle, margin_r) }, 122 {"MarginV", ASS_INT, offsetof(ASSStyle, margin_v) }, 123 {"AlphaLevel", ASS_INT, offsetof(ASSStyle, alpha_level) }, 124 {"Encoding", ASS_INT, offsetof(ASSStyle, encoding) }, 125 {0}, 126 } 127 }, 128 { .section = "Events", 129 .format_header = "Format", 130 .fields_header = "Dialogue", 131 .size = sizeof(ASSDialog), 132 .offset = offsetof(ASS, dialogs), 133 .offset_count = offsetof(ASS, dialogs_count), 134 .fields = {{"Layer", ASS_INT, offsetof(ASSDialog, layer) }, 135 {"Start", ASS_TIMESTAMP, offsetof(ASSDialog, start) }, 136 {"End", ASS_TIMESTAMP, offsetof(ASSDialog, end) }, 137 {"Style", ASS_STR, offsetof(ASSDialog, style) }, 138 {"Name", ASS_STR, offsetof(ASSDialog, name) }, 139 {"MarginL", ASS_INT, offsetof(ASSDialog, margin_l)}, 140 {"MarginR", ASS_INT, offsetof(ASSDialog, margin_r)}, 141 {"MarginV", ASS_INT, offsetof(ASSDialog, margin_v)}, 142 {"Effect", ASS_STR, offsetof(ASSDialog, effect) }, 143 {"Text", ASS_STR, offsetof(ASSDialog, text) }, 144 {0}, 145 } 146 }, 147}; 148 149 150typedef int (*ASSConvertFunc)(void *dest, const char *buf, int len); 151 152static int convert_str(void *dest, const char *buf, int len) 153{ 154 char *str = av_malloc(len + 1); 155 if (str) { 156 memcpy(str, buf, len); 157 str[len] = 0; 158 if (*(void **)dest) 159 av_free(*(void **)dest); 160 *(char **)dest = str; 161 } 162 return !str; 163} 164static int convert_int(void *dest, const char *buf, int len) 165{ 166 return sscanf(buf, "%d", (int *)dest) == 1; 167} 168static int convert_flt(void *dest, const char *buf, int len) 169{ 170 return sscanf(buf, "%f", (float *)dest) == 1; 171} 172static int convert_color(void *dest, const char *buf, int len) 173{ 174 return sscanf(buf, "&H%8x", (int *)dest) == 1 || 175 sscanf(buf, "%d", (int *)dest) == 1; 176} 177static int convert_timestamp(void *dest, const char *buf, int len) 178{ 179 int c, h, m, s, cs; 180 if ((c = sscanf(buf, "%d:%02d:%02d.%02d", &h, &m, &s, &cs)) == 4) 181 *(int *)dest = 360000*h + 6000*m + 100*s + cs; 182 return c == 4; 183} 184static int convert_alignment(void *dest, const char *buf, int len) 185{ 186 int a; 187 if (sscanf(buf, "%d", &a) == 1) { 188 /* convert V4 Style alignment to V4+ Style */ 189 *(int *)dest = a + ((a&4) >> 1) - 5*!!(a&8); 190 return 1; 191 } 192 return 0; 193} 194 195static const ASSConvertFunc convert_func[] = { 196 [ASS_STR] = convert_str, 197 [ASS_INT] = convert_int, 198 [ASS_FLT] = convert_flt, 199 [ASS_COLOR] = convert_color, 200 [ASS_TIMESTAMP] = convert_timestamp, 201 [ASS_ALGN] = convert_alignment, 202}; 203 204 205struct ASSSplitContext { 206 ASS ass; 207 int current_section; 208 int field_number[FF_ARRAY_ELEMS(ass_sections)]; 209 int *field_order[FF_ARRAY_ELEMS(ass_sections)]; 210}; 211 212 213static uint8_t *realloc_section_array(ASSSplitContext *ctx) 214{ 215 const ASSSection *section = &ass_sections[ctx->current_section]; 216 int *count = (int *)((uint8_t *)&ctx->ass + section->offset_count); 217 void **section_ptr = (void **)((uint8_t *)&ctx->ass + section->offset); 218 uint8_t *tmp = av_realloc_array(*section_ptr, (*count+1), section->size); 219 if (!tmp) 220 return NULL; 221 *section_ptr = tmp; 222 tmp += *count * section->size; 223 memset(tmp, 0, section->size); 224 (*count)++; 225 return tmp; 226} 227 228static inline int is_eol(char buf) 229{ 230 return buf == '\r' || buf == '\n' || buf == 0; 231} 232 233static inline const char *skip_space(const char *buf) 234{ 235 while (*buf == ' ') 236 buf++; 237 return buf; 238} 239 240static int *get_default_field_orders(const ASSSection *section, int *number) 241{ 242 int i; 243 int *order = av_malloc_array(FF_ARRAY_ELEMS(section->fields), sizeof(*order)); 244 245 if (!order) 246 return NULL; 247 for (i = 0; section->fields[i].name; i++) 248 order[i] = i; 249 *number = i; 250 while (i < FF_ARRAY_ELEMS(section->fields)) 251 order[i++] = -1; 252 return order; 253} 254 255static const char *ass_split_section(ASSSplitContext *ctx, const char *buf) 256{ 257 const ASSSection *section = &ass_sections[ctx->current_section]; 258 int *number = &ctx->field_number[ctx->current_section]; 259 int *order = ctx->field_order[ctx->current_section]; 260 int i, len; 261 262 while (buf && *buf) { 263 if (buf[0] == '[') { 264 ctx->current_section = -1; 265 break; 266 } 267 if (buf[0] == ';' || (buf[0] == '!' && buf[1] == ':')) 268 goto next_line; // skip comments 269 270 len = strcspn(buf, ":\r\n"); 271 if (buf[len] == ':' && 272 (!section->fields_header || strncmp(buf, section->fields_header, len))) { 273 for (i = 0; i < FF_ARRAY_ELEMS(ass_sections); i++) { 274 if (ass_sections[i].fields_header && 275 !strncmp(buf, ass_sections[i].fields_header, len)) { 276 ctx->current_section = i; 277 section = &ass_sections[ctx->current_section]; 278 number = &ctx->field_number[ctx->current_section]; 279 order = ctx->field_order[ctx->current_section]; 280 break; 281 } 282 } 283 } 284 if (section->format_header && !order) { 285 len = strlen(section->format_header); 286 if (!strncmp(buf, section->format_header, len) && buf[len] == ':') { 287 buf += len + 1; 288 while (!is_eol(*buf)) { 289 buf = skip_space(buf); 290 len = strcspn(buf, ", \r\n"); 291 if (av_reallocp_array(&order, (*number + 1), sizeof(*order)) != 0) 292 return NULL; 293 294 order[*number] = -1; 295 for (i=0; section->fields[i].name; i++) 296 if (!strncmp(buf, section->fields[i].name, len)) { 297 order[*number] = i; 298 break; 299 } 300 (*number)++; 301 buf = skip_space(buf + len + (buf[len] == ',')); 302 } 303 ctx->field_order[ctx->current_section] = order; 304 goto next_line; 305 } 306 } 307 if (section->fields_header) { 308 len = strlen(section->fields_header); 309 if (!strncmp(buf, section->fields_header, len) && buf[len] == ':') { 310 uint8_t *ptr, *struct_ptr = realloc_section_array(ctx); 311 if (!struct_ptr) return NULL; 312 313 /* No format header line found so far, assume default */ 314 if (!order) { 315 order = get_default_field_orders(section, number); 316 if (!order) 317 return NULL; 318 ctx->field_order[ctx->current_section] = order; 319 } 320 321 buf += len + 1; 322 for (i=0; !is_eol(*buf) && i < *number; i++) { 323 int last = i == *number - 1; 324 buf = skip_space(buf); 325 len = strcspn(buf, last ? "\r\n" : ",\r\n"); 326 if (order[i] >= 0) { 327 ASSFieldType type = section->fields[order[i]].type; 328 ptr = struct_ptr + section->fields[order[i]].offset; 329 convert_func[type](ptr, buf, len); 330 } 331 buf += len; 332 if (!last && *buf) buf++; 333 buf = skip_space(buf); 334 } 335 } 336 } else { 337 len = strcspn(buf, ":\r\n"); 338 if (buf[len] == ':') { 339 for (i=0; section->fields[i].name; i++) 340 if (!strncmp(buf, section->fields[i].name, len)) { 341 ASSFieldType type = section->fields[i].type; 342 uint8_t *ptr = (uint8_t *)&ctx->ass + section->offset; 343 ptr += section->fields[i].offset; 344 buf = skip_space(buf + len + 1); 345 convert_func[type](ptr, buf, strcspn(buf, "\r\n")); 346 break; 347 } 348 } 349 } 350next_line: 351 buf += strcspn(buf, "\n"); 352 buf += !!*buf; 353 } 354 return buf; 355} 356 357static int ass_split(ASSSplitContext *ctx, const char *buf) 358{ 359 char c, section[16]; 360 int i; 361 362 if (ctx->current_section >= 0) 363 buf = ass_split_section(ctx, buf); 364 365 while (buf && *buf) { 366 if (sscanf(buf, "[%15[0-9A-Za-z+ ]]%c", section, &c) == 2) { 367 buf += strcspn(buf, "\n"); 368 buf += !!*buf; 369 for (i=0; i<FF_ARRAY_ELEMS(ass_sections); i++) 370 if (!strcmp(section, ass_sections[i].section)) { 371 ctx->current_section = i; 372 buf = ass_split_section(ctx, buf); 373 } 374 } else { 375 buf += strcspn(buf, "\n"); 376 buf += !!*buf; 377 } 378 } 379 return buf ? 0 : AVERROR_INVALIDDATA; 380} 381 382ASSSplitContext *ff_ass_split(const char *buf) 383{ 384 ASSSplitContext *ctx = av_mallocz(sizeof(*ctx)); 385 if (!ctx) 386 return NULL; 387 if (buf && !strncmp(buf, "\xef\xbb\xbf", 3)) // Skip UTF-8 BOM header 388 buf += 3; 389 ctx->current_section = -1; 390 if (ass_split(ctx, buf) < 0) { 391 ff_ass_split_free(ctx); 392 return NULL; 393 } 394 return ctx; 395} 396 397static void free_section(ASSSplitContext *ctx, const ASSSection *section) 398{ 399 uint8_t *ptr = (uint8_t *)&ctx->ass + section->offset; 400 int i, j, *count, c = 1; 401 402 if (section->format_header) { 403 ptr = *(void **)ptr; 404 count = (int *)((uint8_t *)&ctx->ass + section->offset_count); 405 } else 406 count = &c; 407 408 if (ptr) 409 for (i=0; i<*count; i++, ptr += section->size) 410 for (j=0; section->fields[j].name; j++) { 411 const ASSFields *field = §ion->fields[j]; 412 if (field->type == ASS_STR) 413 av_freep(ptr + field->offset); 414 } 415 *count = 0; 416 417 if (section->format_header) 418 av_freep((uint8_t *)&ctx->ass + section->offset); 419} 420 421void ff_ass_free_dialog(ASSDialog **dialogp) 422{ 423 ASSDialog *dialog = *dialogp; 424 if (!dialog) 425 return; 426 av_freep(&dialog->style); 427 av_freep(&dialog->name); 428 av_freep(&dialog->effect); 429 av_freep(&dialog->text); 430 av_freep(dialogp); 431} 432 433ASSDialog *ff_ass_split_dialog(ASSSplitContext *ctx, const char *buf) 434{ 435 int i; 436 static const ASSFields fields[] = { 437 {"ReadOrder", ASS_INT, offsetof(ASSDialog, readorder)}, 438 {"Layer", ASS_INT, offsetof(ASSDialog, layer) }, 439 {"Style", ASS_STR, offsetof(ASSDialog, style) }, 440 {"Name", ASS_STR, offsetof(ASSDialog, name) }, 441 {"MarginL", ASS_INT, offsetof(ASSDialog, margin_l) }, 442 {"MarginR", ASS_INT, offsetof(ASSDialog, margin_r) }, 443 {"MarginV", ASS_INT, offsetof(ASSDialog, margin_v) }, 444 {"Effect", ASS_STR, offsetof(ASSDialog, effect) }, 445 {"Text", ASS_STR, offsetof(ASSDialog, text) }, 446 }; 447 448 ASSDialog *dialog = av_mallocz(sizeof(*dialog)); 449 if (!dialog) 450 return NULL; 451 452 for (i = 0; i < FF_ARRAY_ELEMS(fields); i++) { 453 size_t len; 454 const int last = i == FF_ARRAY_ELEMS(fields) - 1; 455 const ASSFieldType type = fields[i].type; 456 uint8_t *ptr = (uint8_t *)dialog + fields[i].offset; 457 buf = skip_space(buf); 458 len = last ? strlen(buf) : strcspn(buf, ","); 459 if (len >= INT_MAX) { 460 ff_ass_free_dialog(&dialog); 461 return NULL; 462 } 463 convert_func[type](ptr, buf, len); 464 buf += len; 465 if (*buf) buf++; 466 } 467 return dialog; 468} 469 470void ff_ass_split_free(ASSSplitContext *ctx) 471{ 472 if (ctx) { 473 int i; 474 for (i=0; i<FF_ARRAY_ELEMS(ass_sections); i++) { 475 free_section(ctx, &ass_sections[i]); 476 av_freep(&(ctx->field_order[i])); 477 } 478 av_free(ctx); 479 } 480} 481 482 483int ff_ass_split_override_codes(const ASSCodesCallbacks *callbacks, void *priv, 484 const char *buf) 485{ 486 const char *text = NULL; 487 char new_line[2]; 488 int text_len = 0; 489 490 while (buf && *buf) { 491 if (text && callbacks->text && 492 (sscanf(buf, "\\%1[nN]", new_line) == 1 || 493 !strncmp(buf, "{\\", 2))) { 494 callbacks->text(priv, text, text_len); 495 text = NULL; 496 } 497 if (sscanf(buf, "\\%1[nN]", new_line) == 1) { 498 if (callbacks->new_line) 499 callbacks->new_line(priv, new_line[0] == 'N'); 500 buf += 2; 501 } else if (!strncmp(buf, "{\\", 2)) { 502 buf++; 503 while (*buf == '\\') { 504 char style[2], c[2], sep[2], c_num[2] = "0", tmp[128] = {0}; 505 unsigned int color = 0xFFFFFFFF; 506 int len, size = -1, an = -1, alpha = -1; 507 int x1, y1, x2, y2, t1 = -1, t2 = -1; 508 if (sscanf(buf, "\\%1[bisu]%1[01\\}]%n", style, c, &len) > 1) { 509 int close = c[0] == '0' ? 1 : c[0] == '1' ? 0 : -1; 510 len += close != -1; 511 if (callbacks->style) 512 callbacks->style(priv, style[0], close); 513 } else if (sscanf(buf, "\\c%1[\\}]%n", sep, &len) > 0 || 514 sscanf(buf, "\\c&H%X&%1[\\}]%n", &color, sep, &len) > 1 || 515 sscanf(buf, "\\%1[1234]c%1[\\}]%n", c_num, sep, &len) > 1 || 516 sscanf(buf, "\\%1[1234]c&H%X&%1[\\}]%n", c_num, &color, sep, &len) > 2) { 517 if (callbacks->color) 518 callbacks->color(priv, color, c_num[0] - '0'); 519 } else if (sscanf(buf, "\\alpha%1[\\}]%n", sep, &len) > 0 || 520 sscanf(buf, "\\alpha&H%2X&%1[\\}]%n", &alpha, sep, &len) > 1 || 521 sscanf(buf, "\\%1[1234]a%1[\\}]%n", c_num, sep, &len) > 1 || 522 sscanf(buf, "\\%1[1234]a&H%2X&%1[\\}]%n", c_num, &alpha, sep, &len) > 2) { 523 if (callbacks->alpha) 524 callbacks->alpha(priv, alpha, c_num[0] - '0'); 525 } else if (sscanf(buf, "\\fn%1[\\}]%n", sep, &len) > 0 || 526 sscanf(buf, "\\fn%127[^\\}]%1[\\}]%n", tmp, sep, &len) > 1) { 527 if (callbacks->font_name) 528 callbacks->font_name(priv, tmp[0] ? tmp : NULL); 529 } else if (sscanf(buf, "\\fs%1[\\}]%n", sep, &len) > 0 || 530 sscanf(buf, "\\fs%u%1[\\}]%n", &size, sep, &len) > 1) { 531 if (callbacks->font_size) 532 callbacks->font_size(priv, size); 533 } else if (sscanf(buf, "\\a%1[\\}]%n", sep, &len) > 0 || 534 sscanf(buf, "\\a%2u%1[\\}]%n", &an, sep, &len) > 1 || 535 sscanf(buf, "\\an%1[\\}]%n", sep, &len) > 0 || 536 sscanf(buf, "\\an%1u%1[\\}]%n", &an, sep, &len) > 1) { 537 if (an != -1 && buf[2] != 'n') 538 an = (an&3) + (an&4 ? 6 : an&8 ? 3 : 0); 539 if (callbacks->alignment) 540 callbacks->alignment(priv, an); 541 } else if (sscanf(buf, "\\r%1[\\}]%n", sep, &len) > 0 || 542 sscanf(buf, "\\r%127[^\\}]%1[\\}]%n", tmp, sep, &len) > 1) { 543 if (callbacks->cancel_overrides) 544 callbacks->cancel_overrides(priv, tmp); 545 } else if (sscanf(buf, "\\move(%d,%d,%d,%d)%1[\\}]%n", &x1, &y1, &x2, &y2, sep, &len) > 4 || 546 sscanf(buf, "\\move(%d,%d,%d,%d,%d,%d)%1[\\}]%n", &x1, &y1, &x2, &y2, &t1, &t2, sep, &len) > 6) { 547 if (callbacks->move) 548 callbacks->move(priv, x1, y1, x2, y2, t1, t2); 549 } else if (sscanf(buf, "\\pos(%d,%d)%1[\\}]%n", &x1, &y1, sep, &len) > 2) { 550 if (callbacks->move) 551 callbacks->move(priv, x1, y1, x1, y1, -1, -1); 552 } else if (sscanf(buf, "\\org(%d,%d)%1[\\}]%n", &x1, &y1, sep, &len) > 2) { 553 if (callbacks->origin) 554 callbacks->origin(priv, x1, y1); 555 } else { 556 len = strcspn(buf+1, "\\}") + 2; /* skip unknown code */ 557 } 558 buf += len - 1; 559 } 560 if (*buf++ != '}') 561 return AVERROR_INVALIDDATA; 562 } else { 563 if (!text) { 564 text = buf; 565 text_len = 1; 566 } else 567 text_len++; 568 buf++; 569 } 570 } 571 if (text && callbacks->text) 572 callbacks->text(priv, text, text_len); 573 if (callbacks->end) 574 callbacks->end(priv); 575 return 0; 576} 577 578ASSStyle *ff_ass_style_get(ASSSplitContext *ctx, const char *style) 579{ 580 ASS *ass = &ctx->ass; 581 int i; 582 583 if (!style || !*style) 584 style = "Default"; 585 for (i=0; i<ass->styles_count; i++) 586 if (ass->styles[i].name && !strcmp(ass->styles[i].name, style)) 587 return ass->styles + i; 588 return NULL; 589} 590