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 {"BackColour", ASS_COLOR
,offsetof(ASSStyle
, back_color
) },
72 {"Bold", ASS_INT
, offsetof(ASSStyle
, bold
) },
73 {"Italic", ASS_INT
, offsetof(ASSStyle
, italic
) },
74 {"Underline", ASS_INT
, offsetof(ASSStyle
, underline
) },
75 {"Alignment", ASS_INT
, offsetof(ASSStyle
, alignment
) },
79 { .section
= "V4 Styles",
80 .format_header
= "Format",
81 .fields_header
= "Style",
82 .size
= sizeof(ASSStyle
),
83 .offset
= offsetof(ASS
, styles
),
84 .offset_count
= offsetof(ASS
, styles_count
),
85 .fields
= {{"Name", ASS_STR
, offsetof(ASSStyle
, name
) },
86 {"Fontname", ASS_STR
, offsetof(ASSStyle
, font_name
) },
87 {"Fontsize", ASS_INT
, offsetof(ASSStyle
, font_size
) },
88 {"PrimaryColour",ASS_COLOR
,offsetof(ASSStyle
, primary_color
)},
89 {"BackColour", ASS_COLOR
,offsetof(ASSStyle
, back_color
) },
90 {"Bold", ASS_INT
, offsetof(ASSStyle
, bold
) },
91 {"Italic", ASS_INT
, offsetof(ASSStyle
, italic
) },
92 {"Alignment", ASS_ALGN
, offsetof(ASSStyle
, alignment
) },
96 { .section
= "Events",
97 .format_header
= "Format",
98 .fields_header
= "Dialogue",
99 .size
= sizeof(ASSDialog
),
100 .offset
= offsetof(ASS
, dialogs
),
101 .offset_count
= offsetof(ASS
, dialogs_count
),
102 .fields
= {{"Layer", ASS_INT
, offsetof(ASSDialog
, layer
) },
103 {"Start", ASS_TIMESTAMP
, offsetof(ASSDialog
, start
) },
104 {"End", ASS_TIMESTAMP
, offsetof(ASSDialog
, end
) },
105 {"Style", ASS_STR
, offsetof(ASSDialog
, style
) },
106 {"Text", ASS_STR
, offsetof(ASSDialog
, text
) },
113 typedef int (*ASSConvertFunc
)(void *dest
, const char *buf
, int len
);
115 static int convert_str(void *dest
, const char *buf
, int len
)
117 char *str
= av_malloc(len
+ 1);
119 memcpy(str
, buf
, len
);
122 av_free(*(void **)dest
);
123 *(char **)dest
= str
;
127 static int convert_int(void *dest
, const char *buf
, int len
)
129 return sscanf(buf
, "%d", (int *)dest
) == 1;
131 static int convert_flt(void *dest
, const char *buf
, int len
)
133 return sscanf(buf
, "%f", (float *)dest
) == 1;
135 static int convert_color(void *dest
, const char *buf
, int len
)
137 return sscanf(buf
, "&H%8x", (int *)dest
) == 1 ||
138 sscanf(buf
, "%d", (int *)dest
) == 1;
140 static int convert_timestamp(void *dest
, const char *buf
, int len
)
143 if ((c
= sscanf(buf
, "%d:%02d:%02d.%02d", &h
, &m
, &s
, &cs
)) == 4)
144 *(int *)dest
= 360000*h
+ 6000*m
+ 100*s
+ cs
;
147 static int convert_alignment(void *dest
, const char *buf
, int len
)
150 if (sscanf(buf
, "%d", &a
) == 1) {
151 /* convert V4 Style alignment to V4+ Style */
152 *(int *)dest
= a
+ ((a
&4) >> 1) - 5*!!(a
&8);
158 static const ASSConvertFunc convert_func
[] = {
159 [ASS_STR
] = convert_str
,
160 [ASS_INT
] = convert_int
,
161 [ASS_FLT
] = convert_flt
,
162 [ASS_COLOR
] = convert_color
,
163 [ASS_TIMESTAMP
] = convert_timestamp
,
164 [ASS_ALGN
] = convert_alignment
,
168 struct ASSSplitContext
{
171 int field_number
[FF_ARRAY_ELEMS(ass_sections
)];
172 int *field_order
[FF_ARRAY_ELEMS(ass_sections
)];
176 static uint8_t *realloc_section_array(ASSSplitContext
*ctx
)
178 const ASSSection
*section
= &ass_sections
[ctx
->current_section
];
179 int *count
= (int *)((uint8_t *)&ctx
->ass
+ section
->offset_count
);
180 void **section_ptr
= (void **)((uint8_t *)&ctx
->ass
+ section
->offset
);
181 uint8_t *tmp
= av_realloc(*section_ptr
, (*count
+1)*section
->size
);
185 tmp
+= *count
* section
->size
;
186 memset(tmp
, 0, section
->size
);
191 static inline int is_eol(char buf
)
193 return buf
== '\r' || buf
== '\n' || buf
== 0;
196 static inline const char *skip_space(const char *buf
)
203 static const char *ass_split_section(ASSSplitContext
*ctx
, const char *buf
)
205 const ASSSection
*section
= &ass_sections
[ctx
->current_section
];
206 int *number
= &ctx
->field_number
[ctx
->current_section
];
207 int *order
= ctx
->field_order
[ctx
->current_section
];
210 while (buf
&& *buf
) {
212 ctx
->current_section
= -1;
215 if (buf
[0] == ';' || (buf
[0] == '!' && buf
[1] == ':')) {
217 } else if (section
->format_header
&& !order
) {
218 len
= strlen(section
->format_header
);
219 if (strncmp(buf
, section
->format_header
, len
) || buf
[len
] != ':')
222 while (!is_eol(*buf
)) {
223 buf
= skip_space(buf
);
224 len
= strcspn(buf
, ", \r\n");
225 if (!(tmp
= av_realloc(order
, (*number
+ 1) * sizeof(*order
))))
229 for (i
=0; section
->fields
[i
].name
; i
++)
230 if (!strncmp(buf
, section
->fields
[i
].name
, len
)) {
235 buf
= skip_space(buf
+ len
+ (buf
[len
] == ','));
237 ctx
->field_order
[ctx
->current_section
] = order
;
238 } else if (section
->fields_header
) {
239 len
= strlen(section
->fields_header
);
240 if (!strncmp(buf
, section
->fields_header
, len
) && buf
[len
] == ':') {
241 uint8_t *ptr
, *struct_ptr
= realloc_section_array(ctx
);
242 if (!struct_ptr
) return NULL
;
244 for (i
=0; !is_eol(*buf
) && i
< *number
; i
++) {
245 int last
= i
== *number
- 1;
246 buf
= skip_space(buf
);
247 len
= strcspn(buf
, last
? "\r\n" : ",\r\n");
249 ASSFieldType type
= section
->fields
[order
[i
]].type
;
250 ptr
= struct_ptr
+ section
->fields
[order
[i
]].offset
;
251 convert_func
[type
](ptr
, buf
, len
);
254 if (!last
&& *buf
) buf
++;
255 buf
= skip_space(buf
);
259 len
= strcspn(buf
, ":\r\n");
260 if (buf
[len
] == ':') {
261 for (i
=0; section
->fields
[i
].name
; i
++)
262 if (!strncmp(buf
, section
->fields
[i
].name
, len
)) {
263 ASSFieldType type
= section
->fields
[i
].type
;
264 uint8_t *ptr
= (uint8_t *)&ctx
->ass
+ section
->offset
;
265 ptr
+= section
->fields
[i
].offset
;
266 buf
= skip_space(buf
+ len
+ 1);
267 convert_func
[type
](ptr
, buf
, strcspn(buf
, "\r\n"));
272 buf
+= strcspn(buf
, "\n");
278 static int ass_split(ASSSplitContext
*ctx
, const char *buf
)
283 if (ctx
->current_section
>= 0)
284 buf
= ass_split_section(ctx
, buf
);
286 while (buf
&& *buf
) {
287 if (sscanf(buf
, "[%15[0-9A-Za-z+ ]]%c", section
, &c
) == 2) {
288 buf
+= strcspn(buf
, "\n");
290 for (i
=0; i
<FF_ARRAY_ELEMS(ass_sections
); i
++)
291 if (!strcmp(section
, ass_sections
[i
].section
)) {
292 ctx
->current_section
= i
;
293 buf
= ass_split_section(ctx
, buf
);
296 buf
+= strcspn(buf
, "\n");
300 return buf
? 0 : AVERROR_INVALIDDATA
;
303 ASSSplitContext
*ff_ass_split(const char *buf
)
305 ASSSplitContext
*ctx
= av_mallocz(sizeof(*ctx
));
306 ctx
->current_section
= -1;
307 if (ass_split(ctx
, buf
) < 0) {
308 ff_ass_split_free(ctx
);
314 static void free_section(ASSSplitContext
*ctx
, const ASSSection
*section
)
316 uint8_t *ptr
= (uint8_t *)&ctx
->ass
+ section
->offset
;
317 int i
, j
, *count
, c
= 1;
319 if (section
->format_header
) {
321 count
= (int *)((uint8_t *)&ctx
->ass
+ section
->offset_count
);
326 for (i
=0; i
<*count
; i
++, ptr
+= section
->size
)
327 for (j
=0; section
->fields
[j
].name
; j
++) {
328 const ASSFields
*field
= §ion
->fields
[j
];
329 if (field
->type
== ASS_STR
)
330 av_freep(ptr
+ field
->offset
);
334 if (section
->format_header
)
335 av_freep((uint8_t *)&ctx
->ass
+ section
->offset
);
338 ASSDialog
*ff_ass_split_dialog(ASSSplitContext
*ctx
, const char *buf
,
339 int cache
, int *number
)
341 ASSDialog
*dialog
= NULL
;
344 for (i
=0; i
<FF_ARRAY_ELEMS(ass_sections
); i
++)
345 if (!strcmp(ass_sections
[i
].section
, "Events")) {
346 free_section(ctx
, &ass_sections
[i
]);
349 count
= ctx
->ass
.dialogs_count
;
350 if (ass_split(ctx
, buf
) == 0)
351 dialog
= ctx
->ass
.dialogs
+ count
;
353 *number
= ctx
->ass
.dialogs_count
- count
;
357 void ff_ass_split_free(ASSSplitContext
*ctx
)
361 for (i
=0; i
<FF_ARRAY_ELEMS(ass_sections
); i
++) {
362 free_section(ctx
, &ass_sections
[i
]);
363 av_freep(&(ctx
->field_order
[i
]));
370 int ff_ass_split_override_codes(const ASSCodesCallbacks
*callbacks
, void *priv
,
373 const char *text
= NULL
;
377 while (buf
&& *buf
) {
378 if (text
&& callbacks
->text
&&
379 (sscanf(buf
, "\\%1[nN]", new_line
) == 1 ||
380 !strncmp(buf
, "{\\", 2))) {
381 callbacks
->text(priv
, text
, text_len
);
384 if (sscanf(buf
, "\\%1[nN]", new_line
) == 1) {
385 if (callbacks
->new_line
)
386 callbacks
->new_line(priv
, new_line
[0] == 'N');
388 } else if (!strncmp(buf
, "{\\", 2)) {
390 while (*buf
== '\\') {
391 char style
[2], c
[2], sep
[2], c_num
[2] = "0", tmp
[128] = {0};
392 unsigned int color
= 0xFFFFFFFF;
393 int len
, size
= -1, an
= -1, alpha
= -1;
394 int x1
, y1
, x2
, y2
, t1
= -1, t2
= -1;
395 if (sscanf(buf
, "\\%1[bisu]%1[01\\}]%n", style
, c
, &len
) > 1) {
396 int close
= c
[0] == '0' ? 1 : c
[0] == '1' ? 0 : -1;
398 if (callbacks
->style
)
399 callbacks
->style(priv
, style
[0], close
);
400 } else if (sscanf(buf
, "\\c%1[\\}]%n", sep
, &len
) > 0 ||
401 sscanf(buf
, "\\c&H%X&%1[\\}]%n", &color
, sep
, &len
) > 1 ||
402 sscanf(buf
, "\\%1[1234]c%1[\\}]%n", c_num
, sep
, &len
) > 1 ||
403 sscanf(buf
, "\\%1[1234]c&H%X&%1[\\}]%n", c_num
, &color
, sep
, &len
) > 2) {
404 if (callbacks
->color
)
405 callbacks
->color(priv
, color
, c_num
[0] - '0');
406 } else if (sscanf(buf
, "\\alpha%1[\\}]%n", sep
, &len
) > 0 ||
407 sscanf(buf
, "\\alpha&H%2X&%1[\\}]%n", &alpha
, sep
, &len
) > 1 ||
408 sscanf(buf
, "\\%1[1234]a%1[\\}]%n", c_num
, sep
, &len
) > 1 ||
409 sscanf(buf
, "\\%1[1234]a&H%2X&%1[\\}]%n", c_num
, &alpha
, sep
, &len
) > 2) {
410 if (callbacks
->alpha
)
411 callbacks
->alpha(priv
, alpha
, c_num
[0] - '0');
412 } else if (sscanf(buf
, "\\fn%1[\\}]%n", sep
, &len
) > 0 ||
413 sscanf(buf
, "\\fn%127[^\\}]%1[\\}]%n", tmp
, sep
, &len
) > 1) {
414 if (callbacks
->font_name
)
415 callbacks
->font_name(priv
, tmp
[0] ? tmp
: NULL
);
416 } else if (sscanf(buf
, "\\fs%1[\\}]%n", sep
, &len
) > 0 ||
417 sscanf(buf
, "\\fs%u%1[\\}]%n", &size
, sep
, &len
) > 1) {
418 if (callbacks
->font_size
)
419 callbacks
->font_size(priv
, size
);
420 } else if (sscanf(buf
, "\\a%1[\\}]%n", sep
, &len
) > 0 ||
421 sscanf(buf
, "\\a%2u%1[\\}]%n", &an
, sep
, &len
) > 1 ||
422 sscanf(buf
, "\\an%1[\\}]%n", sep
, &len
) > 0 ||
423 sscanf(buf
, "\\an%1u%1[\\}]%n", &an
, sep
, &len
) > 1) {
424 if (an
!= -1 && buf
[2] != 'n')
425 an
= (an
&3) + (an
&4 ? 6 : an
&8 ? 3 : 0);
426 if (callbacks
->alignment
)
427 callbacks
->alignment(priv
, an
);
428 } else if (sscanf(buf
, "\\r%1[\\}]%n", sep
, &len
) > 0 ||
429 sscanf(buf
, "\\r%127[^\\}]%1[\\}]%n", tmp
, sep
, &len
) > 1) {
430 if (callbacks
->cancel_overrides
)
431 callbacks
->cancel_overrides(priv
, tmp
);
432 } else if (sscanf(buf
, "\\move(%d,%d,%d,%d)%1[\\}]%n", &x1
, &y1
, &x2
, &y2
, sep
, &len
) > 4 ||
433 sscanf(buf
, "\\move(%d,%d,%d,%d,%d,%d)%1[\\}]%n", &x1
, &y1
, &x2
, &y2
, &t1
, &t2
, sep
, &len
) > 6) {
435 callbacks
->move(priv
, x1
, y1
, x2
, y2
, t1
, t2
);
436 } else if (sscanf(buf
, "\\pos(%d,%d)%1[\\}]%n", &x1
, &y1
, sep
, &len
) > 2) {
438 callbacks
->move(priv
, x1
, y1
, x1
, y1
, -1, -1);
439 } else if (sscanf(buf
, "\\org(%d,%d)%1[\\}]%n", &x1
, &y1
, sep
, &len
) > 2) {
440 if (callbacks
->origin
)
441 callbacks
->origin(priv
, x1
, y1
);
443 len
= strcspn(buf
+1, "\\}") + 2; /* skip unknown code */
448 return AVERROR_INVALIDDATA
;
458 if (text
&& callbacks
->text
)
459 callbacks
->text(priv
, text
, text_len
);
461 callbacks
->end(priv
);
465 ASSStyle
*ff_ass_style_get(ASSSplitContext
*ctx
, const char *style
)
467 ASS
*ass
= &ctx
->ass
;
470 if (!style
|| !*style
)
472 for (i
=0; i
<ass
->styles_count
; i
++)
473 if (!strcmp(ass
->styles
[i
].name
, style
))
474 return ass
->styles
+ i
;