2 * SSA/ASS spliting functions
3 * Copyright (c) 2010 Aurelien Jacobs <aurel@gnuage.org>
5 * This file is part of FFmpeg.
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.
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.
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
23 #include "ass_split.h"
42 const char *format_header
;
43 const char *fields_header
;
50 static const ASSSection ass_sections
[] = {
51 { .section
= "Script Info",
52 .offset
= offsetof(ASS
, script_info
),
53 .fields
= {{"ScriptType", ASS_STR
, offsetof(ASSScriptInfo
, script_type
)},
54 {"Collisions", ASS_STR
, offsetof(ASSScriptInfo
, collisions
) },
55 {"PlayResX", ASS_INT
, offsetof(ASSScriptInfo
, play_res_x
) },
56 {"PlayResY", ASS_INT
, offsetof(ASSScriptInfo
, play_res_y
) },
57 {"Timer", ASS_FLT
, offsetof(ASSScriptInfo
, timer
) },
61 { .section
= "V4+ Styles",
62 .format_header
= "Format",
63 .fields_header
= "Style",
64 .size
= sizeof(ASSStyle
),
65 .offset
= offsetof(ASS
, styles
),
66 .offset_count
= offsetof(ASS
, styles_count
),
67 .fields
= {{"Name", ASS_STR
, offsetof(ASSStyle
, name
) },
68 {"Fontname", ASS_STR
, offsetof(ASSStyle
, font_name
) },
69 {"Fontsize", ASS_INT
, offsetof(ASSStyle
, font_size
) },
70 {"PrimaryColour", ASS_COLOR
, offsetof(ASSStyle
, primary_color
) },
71 {"SecondaryColour", ASS_COLOR
, offsetof(ASSStyle
, secondary_color
)},
72 {"OutlineColour", ASS_COLOR
, offsetof(ASSStyle
, outline_color
) },
73 {"BackColour", ASS_COLOR
, offsetof(ASSStyle
, back_color
) },
74 {"Bold", ASS_INT
, offsetof(ASSStyle
, bold
) },
75 {"Italic", ASS_INT
, offsetof(ASSStyle
, italic
) },
76 {"Underline", ASS_INT
, offsetof(ASSStyle
, underline
) },
77 {"StrikeOut", ASS_INT
, offsetof(ASSStyle
, strikeout
) },
78 {"ScaleX", ASS_FLT
, offsetof(ASSStyle
, scalex
) },
79 {"ScaleY", ASS_FLT
, offsetof(ASSStyle
, scaley
) },
80 {"Spacing", ASS_FLT
, offsetof(ASSStyle
, spacing
) },
81 {"Angle", ASS_FLT
, offsetof(ASSStyle
, angle
) },
82 {"BorderStyle", ASS_INT
, offsetof(ASSStyle
, border_style
) },
83 {"Outline", ASS_FLT
, offsetof(ASSStyle
, outline
) },
84 {"Shadow", ASS_FLT
, offsetof(ASSStyle
, shadow
) },
85 {"Alignment", ASS_INT
, offsetof(ASSStyle
, alignment
) },
86 {"MarginL", ASS_INT
, offsetof(ASSStyle
, margin_l
) },
87 {"MarginR", ASS_INT
, offsetof(ASSStyle
, margin_r
) },
88 {"MarginV", ASS_INT
, offsetof(ASSStyle
, margin_v
) },
89 {"Encoding", ASS_INT
, offsetof(ASSStyle
, encoding
) },
93 { .section
= "V4 Styles",
94 .format_header
= "Format",
95 .fields_header
= "Style",
96 .size
= sizeof(ASSStyle
),
97 .offset
= offsetof(ASS
, styles
),
98 .offset_count
= offsetof(ASS
, styles_count
),
99 .fields
= {{"Name", ASS_STR
, offsetof(ASSStyle
, name
) },
100 {"Fontname", ASS_STR
, offsetof(ASSStyle
, font_name
) },
101 {"Fontsize", ASS_INT
, offsetof(ASSStyle
, font_size
) },
102 {"PrimaryColour", ASS_COLOR
, offsetof(ASSStyle
, primary_color
) },
103 {"SecondaryColour", ASS_COLOR
, offsetof(ASSStyle
, secondary_color
)},
104 {"TertiaryColour", ASS_COLOR
, offsetof(ASSStyle
, outline_color
) },
105 {"BackColour", ASS_COLOR
, offsetof(ASSStyle
, back_color
) },
106 {"Bold", ASS_INT
, offsetof(ASSStyle
, bold
) },
107 {"Italic", ASS_INT
, offsetof(ASSStyle
, italic
) },
108 {"BorderStyle", ASS_INT
, offsetof(ASSStyle
, border_style
) },
109 {"Outline", ASS_FLT
, offsetof(ASSStyle
, outline
) },
110 {"Shadow", ASS_FLT
, offsetof(ASSStyle
, shadow
) },
111 {"Alignment", ASS_ALGN
, offsetof(ASSStyle
, alignment
) },
112 {"MarginL", ASS_INT
, offsetof(ASSStyle
, margin_l
) },
113 {"MarginR", ASS_INT
, offsetof(ASSStyle
, margin_r
) },
114 {"MarginV", ASS_INT
, offsetof(ASSStyle
, margin_v
) },
115 {"AlphaLevel", ASS_INT
, offsetof(ASSStyle
, alpha_level
) },
116 {"Encoding", ASS_INT
, offsetof(ASSStyle
, encoding
) },
120 { .section
= "Events",
121 .format_header
= "Format",
122 .fields_header
= "Dialogue",
123 .size
= sizeof(ASSDialog
),
124 .offset
= offsetof(ASS
, dialogs
),
125 .offset_count
= offsetof(ASS
, dialogs_count
),
126 .fields
= {{"Layer", ASS_INT
, offsetof(ASSDialog
, layer
) },
127 {"Start", ASS_TIMESTAMP
, offsetof(ASSDialog
, start
) },
128 {"End", ASS_TIMESTAMP
, offsetof(ASSDialog
, end
) },
129 {"Style", ASS_STR
, offsetof(ASSDialog
, style
) },
130 {"Name", ASS_STR
, offsetof(ASSDialog
, name
) },
131 {"MarginL", ASS_INT
, offsetof(ASSDialog
, margin_l
)},
132 {"MarginR", ASS_INT
, offsetof(ASSDialog
, margin_r
)},
133 {"MarginV", ASS_INT
, offsetof(ASSDialog
, margin_v
)},
134 {"Effect", ASS_STR
, offsetof(ASSDialog
, effect
) },
135 {"Text", ASS_STR
, offsetof(ASSDialog
, text
) },
142 typedef int (*ASSConvertFunc
)(void *dest
, const char *buf
, int len
);
144 static int convert_str(void *dest
, const char *buf
, int len
)
146 char *str
= av_malloc(len
+ 1);
148 memcpy(str
, buf
, len
);
151 av_free(*(void **)dest
);
152 *(char **)dest
= str
;
156 static int convert_int(void *dest
, const char *buf
, int len
)
158 return sscanf(buf
, "%d", (int *)dest
) == 1;
160 static int convert_flt(void *dest
, const char *buf
, int len
)
162 return sscanf(buf
, "%f", (float *)dest
) == 1;
164 static int convert_color(void *dest
, const char *buf
, int len
)
166 return sscanf(buf
, "&H%8x", (int *)dest
) == 1 ||
167 sscanf(buf
, "%d", (int *)dest
) == 1;
169 static int convert_timestamp(void *dest
, const char *buf
, int len
)
172 if ((c
= sscanf(buf
, "%d:%02d:%02d.%02d", &h
, &m
, &s
, &cs
)) == 4)
173 *(int *)dest
= 360000*h
+ 6000*m
+ 100*s
+ cs
;
176 static int convert_alignment(void *dest
, const char *buf
, int len
)
179 if (sscanf(buf
, "%d", &a
) == 1) {
180 /* convert V4 Style alignment to V4+ Style */
181 *(int *)dest
= a
+ ((a
&4) >> 1) - 5*!!(a
&8);
187 static const ASSConvertFunc convert_func
[] = {
188 [ASS_STR
] = convert_str
,
189 [ASS_INT
] = convert_int
,
190 [ASS_FLT
] = convert_flt
,
191 [ASS_COLOR
] = convert_color
,
192 [ASS_TIMESTAMP
] = convert_timestamp
,
193 [ASS_ALGN
] = convert_alignment
,
197 struct ASSSplitContext
{
200 int field_number
[FF_ARRAY_ELEMS(ass_sections
)];
201 int *field_order
[FF_ARRAY_ELEMS(ass_sections
)];
205 static uint8_t *realloc_section_array(ASSSplitContext
*ctx
)
207 const ASSSection
*section
= &ass_sections
[ctx
->current_section
];
208 int *count
= (int *)((uint8_t *)&ctx
->ass
+ section
->offset_count
);
209 void **section_ptr
= (void **)((uint8_t *)&ctx
->ass
+ section
->offset
);
210 uint8_t *tmp
= av_realloc(*section_ptr
, (*count
+1)*section
->size
);
214 tmp
+= *count
* section
->size
;
215 memset(tmp
, 0, section
->size
);
220 static inline int is_eol(char buf
)
222 return buf
== '\r' || buf
== '\n' || buf
== 0;
225 static inline const char *skip_space(const char *buf
)
232 static int *get_default_field_orders(const ASSSection
*section
)
235 int *order
= av_malloc(FF_ARRAY_ELEMS(section
->fields
) * sizeof(*order
));
239 for (i
= 0; section
->fields
[i
].name
; i
++)
241 while (i
< FF_ARRAY_ELEMS(section
->fields
))
246 static const char *ass_split_section(ASSSplitContext
*ctx
, const char *buf
)
248 const ASSSection
*section
= &ass_sections
[ctx
->current_section
];
249 int *number
= &ctx
->field_number
[ctx
->current_section
];
250 int *order
= ctx
->field_order
[ctx
->current_section
];
253 while (buf
&& *buf
) {
255 ctx
->current_section
= -1;
258 if (buf
[0] == ';' || (buf
[0] == '!' && buf
[1] == ':')) {
260 } else if (section
->format_header
&& !order
) {
261 len
= strlen(section
->format_header
);
262 if (strncmp(buf
, section
->format_header
, len
) || buf
[len
] != ':')
265 while (!is_eol(*buf
)) {
266 buf
= skip_space(buf
);
267 len
= strcspn(buf
, ", \r\n");
268 if (!(tmp
= av_realloc(order
, (*number
+ 1) * sizeof(*order
))))
272 for (i
=0; section
->fields
[i
].name
; i
++)
273 if (!strncmp(buf
, section
->fields
[i
].name
, len
)) {
278 buf
= skip_space(buf
+ len
+ (buf
[len
] == ','));
280 ctx
->field_order
[ctx
->current_section
] = order
;
281 } else if (section
->fields_header
) {
282 len
= strlen(section
->fields_header
);
283 if (!strncmp(buf
, section
->fields_header
, len
) && buf
[len
] == ':') {
284 uint8_t *ptr
, *struct_ptr
= realloc_section_array(ctx
);
285 if (!struct_ptr
) return NULL
;
287 /* No format header line found so far, assume default */
289 order
= get_default_field_orders(section
);
292 ctx
->field_order
[ctx
->current_section
] = order
;
296 for (i
=0; !is_eol(*buf
) && i
< *number
; i
++) {
297 int last
= i
== *number
- 1;
298 buf
= skip_space(buf
);
299 len
= strcspn(buf
, last
? "\r\n" : ",\r\n");
301 ASSFieldType type
= section
->fields
[order
[i
]].type
;
302 ptr
= struct_ptr
+ section
->fields
[order
[i
]].offset
;
303 convert_func
[type
](ptr
, buf
, len
);
306 if (!last
&& *buf
) buf
++;
307 buf
= skip_space(buf
);
311 len
= strcspn(buf
, ":\r\n");
312 if (buf
[len
] == ':') {
313 for (i
=0; section
->fields
[i
].name
; i
++)
314 if (!strncmp(buf
, section
->fields
[i
].name
, len
)) {
315 ASSFieldType type
= section
->fields
[i
].type
;
316 uint8_t *ptr
= (uint8_t *)&ctx
->ass
+ section
->offset
;
317 ptr
+= section
->fields
[i
].offset
;
318 buf
= skip_space(buf
+ len
+ 1);
319 convert_func
[type
](ptr
, buf
, strcspn(buf
, "\r\n"));
325 buf
+= strcspn(buf
, "\n");
331 static int ass_split(ASSSplitContext
*ctx
, const char *buf
)
336 if (ctx
->current_section
>= 0)
337 buf
= ass_split_section(ctx
, buf
);
339 while (buf
&& *buf
) {
340 if (sscanf(buf
, "[%15[0-9A-Za-z+ ]]%c", section
, &c
) == 2) {
341 buf
+= strcspn(buf
, "\n");
343 for (i
=0; i
<FF_ARRAY_ELEMS(ass_sections
); i
++)
344 if (!strcmp(section
, ass_sections
[i
].section
)) {
345 ctx
->current_section
= i
;
346 buf
= ass_split_section(ctx
, buf
);
349 buf
+= strcspn(buf
, "\n");
353 return buf
? 0 : AVERROR_INVALIDDATA
;
356 ASSSplitContext
*ff_ass_split(const char *buf
)
358 ASSSplitContext
*ctx
= av_mallocz(sizeof(*ctx
));
359 ctx
->current_section
= -1;
360 if (ass_split(ctx
, buf
) < 0) {
361 ff_ass_split_free(ctx
);
367 static void free_section(ASSSplitContext
*ctx
, const ASSSection
*section
)
369 uint8_t *ptr
= (uint8_t *)&ctx
->ass
+ section
->offset
;
370 int i
, j
, *count
, c
= 1;
372 if (section
->format_header
) {
374 count
= (int *)((uint8_t *)&ctx
->ass
+ section
->offset_count
);
379 for (i
=0; i
<*count
; i
++, ptr
+= section
->size
)
380 for (j
=0; section
->fields
[j
].name
; j
++) {
381 const ASSFields
*field
= §ion
->fields
[j
];
382 if (field
->type
== ASS_STR
)
383 av_freep(ptr
+ field
->offset
);
387 if (section
->format_header
)
388 av_freep((uint8_t *)&ctx
->ass
+ section
->offset
);
391 ASSDialog
*ff_ass_split_dialog(ASSSplitContext
*ctx
, const char *buf
,
392 int cache
, int *number
)
394 ASSDialog
*dialog
= NULL
;
397 for (i
=0; i
<FF_ARRAY_ELEMS(ass_sections
); i
++)
398 if (!strcmp(ass_sections
[i
].section
, "Events")) {
399 free_section(ctx
, &ass_sections
[i
]);
402 count
= ctx
->ass
.dialogs_count
;
403 if (ass_split(ctx
, buf
) == 0)
404 dialog
= ctx
->ass
.dialogs
+ count
;
406 *number
= ctx
->ass
.dialogs_count
- count
;
410 void ff_ass_split_free(ASSSplitContext
*ctx
)
414 for (i
=0; i
<FF_ARRAY_ELEMS(ass_sections
); i
++) {
415 free_section(ctx
, &ass_sections
[i
]);
416 av_freep(&(ctx
->field_order
[i
]));
423 int ff_ass_split_override_codes(const ASSCodesCallbacks
*callbacks
, void *priv
,
426 const char *text
= NULL
;
430 while (buf
&& *buf
) {
431 if (text
&& callbacks
->text
&&
432 (sscanf(buf
, "\\%1[nN]", new_line
) == 1 ||
433 !strncmp(buf
, "{\\", 2))) {
434 callbacks
->text(priv
, text
, text_len
);
437 if (sscanf(buf
, "\\%1[nN]", new_line
) == 1) {
438 if (callbacks
->new_line
)
439 callbacks
->new_line(priv
, new_line
[0] == 'N');
441 } else if (!strncmp(buf
, "{\\", 2)) {
443 while (*buf
== '\\') {
444 char style
[2], c
[2], sep
[2], c_num
[2] = "0", tmp
[128] = {0};
445 unsigned int color
= 0xFFFFFFFF;
446 int len
, size
= -1, an
= -1, alpha
= -1;
447 int x1
, y1
, x2
, y2
, t1
= -1, t2
= -1;
448 if (sscanf(buf
, "\\%1[bisu]%1[01\\}]%n", style
, c
, &len
) > 1) {
449 int close
= c
[0] == '0' ? 1 : c
[0] == '1' ? 0 : -1;
451 if (callbacks
->style
)
452 callbacks
->style(priv
, style
[0], close
);
453 } else if (sscanf(buf
, "\\c%1[\\}]%n", sep
, &len
) > 0 ||
454 sscanf(buf
, "\\c&H%X&%1[\\}]%n", &color
, sep
, &len
) > 1 ||
455 sscanf(buf
, "\\%1[1234]c%1[\\}]%n", c_num
, sep
, &len
) > 1 ||
456 sscanf(buf
, "\\%1[1234]c&H%X&%1[\\}]%n", c_num
, &color
, sep
, &len
) > 2) {
457 if (callbacks
->color
)
458 callbacks
->color(priv
, color
, c_num
[0] - '0');
459 } else if (sscanf(buf
, "\\alpha%1[\\}]%n", sep
, &len
) > 0 ||
460 sscanf(buf
, "\\alpha&H%2X&%1[\\}]%n", &alpha
, sep
, &len
) > 1 ||
461 sscanf(buf
, "\\%1[1234]a%1[\\}]%n", c_num
, sep
, &len
) > 1 ||
462 sscanf(buf
, "\\%1[1234]a&H%2X&%1[\\}]%n", c_num
, &alpha
, sep
, &len
) > 2) {
463 if (callbacks
->alpha
)
464 callbacks
->alpha(priv
, alpha
, c_num
[0] - '0');
465 } else if (sscanf(buf
, "\\fn%1[\\}]%n", sep
, &len
) > 0 ||
466 sscanf(buf
, "\\fn%127[^\\}]%1[\\}]%n", tmp
, sep
, &len
) > 1) {
467 if (callbacks
->font_name
)
468 callbacks
->font_name(priv
, tmp
[0] ? tmp
: NULL
);
469 } else if (sscanf(buf
, "\\fs%1[\\}]%n", sep
, &len
) > 0 ||
470 sscanf(buf
, "\\fs%u%1[\\}]%n", &size
, sep
, &len
) > 1) {
471 if (callbacks
->font_size
)
472 callbacks
->font_size(priv
, size
);
473 } else if (sscanf(buf
, "\\a%1[\\}]%n", sep
, &len
) > 0 ||
474 sscanf(buf
, "\\a%2u%1[\\}]%n", &an
, sep
, &len
) > 1 ||
475 sscanf(buf
, "\\an%1[\\}]%n", sep
, &len
) > 0 ||
476 sscanf(buf
, "\\an%1u%1[\\}]%n", &an
, sep
, &len
) > 1) {
477 if (an
!= -1 && buf
[2] != 'n')
478 an
= (an
&3) + (an
&4 ? 6 : an
&8 ? 3 : 0);
479 if (callbacks
->alignment
)
480 callbacks
->alignment(priv
, an
);
481 } else if (sscanf(buf
, "\\r%1[\\}]%n", sep
, &len
) > 0 ||
482 sscanf(buf
, "\\r%127[^\\}]%1[\\}]%n", tmp
, sep
, &len
) > 1) {
483 if (callbacks
->cancel_overrides
)
484 callbacks
->cancel_overrides(priv
, tmp
);
485 } else if (sscanf(buf
, "\\move(%d,%d,%d,%d)%1[\\}]%n", &x1
, &y1
, &x2
, &y2
, sep
, &len
) > 4 ||
486 sscanf(buf
, "\\move(%d,%d,%d,%d,%d,%d)%1[\\}]%n", &x1
, &y1
, &x2
, &y2
, &t1
, &t2
, sep
, &len
) > 6) {
488 callbacks
->move(priv
, x1
, y1
, x2
, y2
, t1
, t2
);
489 } else if (sscanf(buf
, "\\pos(%d,%d)%1[\\}]%n", &x1
, &y1
, sep
, &len
) > 2) {
491 callbacks
->move(priv
, x1
, y1
, x1
, y1
, -1, -1);
492 } else if (sscanf(buf
, "\\org(%d,%d)%1[\\}]%n", &x1
, &y1
, sep
, &len
) > 2) {
493 if (callbacks
->origin
)
494 callbacks
->origin(priv
, x1
, y1
);
496 len
= strcspn(buf
+1, "\\}") + 2; /* skip unknown code */
501 return AVERROR_INVALIDDATA
;
511 if (text
&& callbacks
->text
)
512 callbacks
->text(priv
, text
, text_len
);
514 callbacks
->end(priv
);
518 ASSStyle
*ff_ass_style_get(ASSSplitContext
*ctx
, const char *style
)
520 ASS
*ass
= &ctx
->ass
;
523 if (!style
|| !*style
)
525 for (i
=0; i
<ass
->styles_count
; i
++)
526 if (!strcmp(ass
->styles
[i
].name
, style
))
527 return ass
->styles
+ i
;