2 * 3GPP TS 26.245 Timed Text encoder
3 * Copyright (c) 2012 Philip Langdale <philipl@overt.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
24 #include "libavutil/avassert.h"
25 #include "libavutil/avstring.h"
26 #include "libavutil/intreadwrite.h"
27 #include "libavutil/mem.h"
28 #include "libavutil/common.h"
29 #include "ass_split.h"
32 #define STYLE_FLAG_BOLD (1<<0)
33 #define STYLE_FLAG_ITALIC (1<<1)
34 #define STYLE_FLAG_UNDERLINE (1<<2)
35 #define STYLE_RECORD_SIZE 12
38 #define STYL_BOX (1<<0)
39 #define HLIT_BOX (1<<1)
40 #define HCLR_BOX (1<<2)
42 #define DEFAULT_STYLE_FONT_ID 0x01
43 #define DEFAULT_STYLE_FONTSIZE 0x12
44 #define DEFAULT_STYLE_COLOR 0xffffffff
45 #define DEFAULT_STYLE_FLAG 0x00
47 #define BGR_TO_RGB(c) (((c) & 0xff) << 16 | ((c) & 0xff00) | (((c) >> 16) & 0xff))
48 #define av_bprint_append_any(buf, data, size) av_bprint_append_data(buf, ((const char*)data), size)
54 uint16_t style_fontID;
55 uint8_t style_fontsize;
69 AVCodecContext *avctx;
71 ASSSplitContext *ass_ctx;
72 ASSStyle *ass_dialog_style;
74 StyleBox **style_attributes;
75 StyleBox *style_attributes_temp;
89 void (*encode)(MovTextContext *s, uint32_t tsmb_type);
92 static void mov_text_cleanup(MovTextContext *s)
95 if (s->box_flags & STYL_BOX) {
96 for (j = 0; j < s->count; j++) {
97 av_freep(&s->style_attributes[j]);
99 av_freep(&s->style_attributes);
101 if (s->style_attributes_temp) {
102 *s->style_attributes_temp = s->d;
106 static void encode_styl(MovTextContext *s, uint32_t tsmb_type)
110 uint16_t style_entries;
111 if ((s->box_flags & STYL_BOX) && s->count) {
112 tsmb_size = s->count * STYLE_RECORD_SIZE + SIZE_ADD;
113 tsmb_size = AV_RB32(&tsmb_size);
114 style_entries = AV_RB16(&s->count);
115 /*The above three attributes are hard coded for now
116 but will come from ASS style in the future*/
117 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
118 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
119 av_bprint_append_any(&s->buffer, &style_entries, 2);
120 for (j = 0; j < s->count; j++) {
121 uint16_t style_start, style_end, style_fontID;
122 uint32_t style_color;
124 style_start = AV_RB16(&s->style_attributes[j]->style_start);
125 style_end = AV_RB16(&s->style_attributes[j]->style_end);
126 style_color = AV_RB32(&s->style_attributes[j]->style_color);
127 style_fontID = AV_RB16(&s->style_attributes[j]->style_fontID);
129 av_bprint_append_any(&s->buffer, &style_start, 2);
130 av_bprint_append_any(&s->buffer, &style_end, 2);
131 av_bprint_append_any(&s->buffer, &style_fontID, 2);
132 av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_flag, 1);
133 av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_fontsize, 1);
134 av_bprint_append_any(&s->buffer, &style_color, 4);
140 static void encode_hlit(MovTextContext *s, uint32_t tsmb_type)
144 if (s->box_flags & HLIT_BOX) {
146 tsmb_size = AV_RB32(&tsmb_size);
147 start = AV_RB16(&s->hlit.start);
148 end = AV_RB16(&s->hlit.end);
149 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
150 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
151 av_bprint_append_any(&s->buffer, &start, 2);
152 av_bprint_append_any(&s->buffer, &end, 2);
156 static void encode_hclr(MovTextContext *s, uint32_t tsmb_type)
158 uint32_t tsmb_size, color;
159 if (s->box_flags & HCLR_BOX) {
161 tsmb_size = AV_RB32(&tsmb_size);
162 color = AV_RB32(&s->hclr.color);
163 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
164 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
165 av_bprint_append_any(&s->buffer, &color, 4);
169 static const Box box_types[] = {
170 { MKTAG('s','t','y','l'), encode_styl },
171 { MKTAG('h','l','i','t'), encode_hlit },
172 { MKTAG('h','c','l','r'), encode_hclr },
175 const static size_t box_count = FF_ARRAY_ELEMS(box_types);
177 static int mov_text_encode_close(AVCodecContext *avctx)
179 MovTextContext *s = avctx->priv_data;
182 ff_ass_split_free(s->ass_ctx);
183 if (s->style_attributes) {
184 for (i = 0; i < s->count; i++) {
185 av_freep(&s->style_attributes[i]);
187 av_freep(&s->style_attributes);
190 av_freep(&s->style_attributes_temp);
191 av_bprint_finalize(&s->buffer, NULL);
195 static int encode_sample_description(AVCodecContext *avctx)
200 uint32_t tsmb_size, tsmb_type, back_color, style_color;
201 uint16_t style_start, style_end, fontID, count;
202 int font_names_total_len = 0;
203 MovTextContext *s = avctx->priv_data;
205 static const uint8_t display_and_justification[] = {
206 0x00, 0x00, 0x00, 0x00, // uint32_t displayFlags
207 0x01, // int8_t horizontal-justification
208 0xFF, // int8_t vertical-justification
210 // 0x00, 0x00, 0x00, 0x00, // uint8_t background-color-rgba[4]
211 static const uint8_t box_record[] = {
213 0x00, 0x00, // int16_t top
214 0x00, 0x00, // int16_t left
215 0x00, 0x00, // int16_t bottom
216 0x00, 0x00, // int16_t right
220 // 0x00, 0x00, // uint16_t startChar
221 // 0x00, 0x00, // uint16_t endChar
222 // 0x00, 0x01, // uint16_t font-ID
223 // 0x00, // uint8_t face-style-flags
224 // 0x12, // uint8_t font-size
225 // 0xFF, 0xFF, 0xFF, 0xFF, // uint8_t text-color-rgba[4]
228 // 0x00, 0x00, 0x00, 0x12, // uint32_t size
229 // 'f', 't', 'a', 'b', // uint8_t name[4]
230 // 0x00, 0x01, // uint16_t entry-count
232 // 0x00, 0x01, // uint16_t font-ID
233 // 0x05, // uint8_t font-name-length
234 // 'S', 'e', 'r', 'i', 'f',// uint8_t font[font-name-length]
238 // Populate sample description from ASS header
239 ass = (ASS*)s->ass_ctx;
240 style = ff_ass_style_get(s->ass_ctx, "Default");
241 if (!style && ass->styles_count) {
242 style = &ass->styles[0];
244 s->d.style_fontID = DEFAULT_STYLE_FONT_ID;
245 s->d.style_fontsize = DEFAULT_STYLE_FONTSIZE;
246 s->d.style_color = DEFAULT_STYLE_COLOR;
247 s->d.style_flag = DEFAULT_STYLE_FLAG;
249 s->d.style_fontsize = style->font_size;
250 s->d.style_color = BGR_TO_RGB(style->primary_color & 0xffffff) << 8 |
251 255 - ((uint32_t)style->primary_color >> 24);
252 s->d.style_flag = (!!style->bold * STYLE_FLAG_BOLD) |
253 (!!style->italic * STYLE_FLAG_ITALIC) |
254 (!!style->underline * STYLE_FLAG_UNDERLINE);
255 back_color = (BGR_TO_RGB(style->back_color & 0xffffff) << 8) |
256 (255 - ((uint32_t)style->back_color >> 24));
259 av_bprint_append_any(&s->buffer, display_and_justification,
260 sizeof(display_and_justification));
261 back_color = AV_RB32(&back_color);
262 av_bprint_append_any(&s->buffer, &back_color, 4);
264 av_bprint_append_any(&s->buffer, box_record, sizeof(box_record));
267 style_start = AV_RB16(&s->d.style_start);
268 style_end = AV_RB16(&s->d.style_end);
269 fontID = AV_RB16(&s->d.style_fontID);
270 style_color = AV_RB32(&s->d.style_color);
271 av_bprint_append_any(&s->buffer, &style_start, 2);
272 av_bprint_append_any(&s->buffer, &style_end, 2);
273 av_bprint_append_any(&s->buffer, &fontID, 2);
274 av_bprint_append_any(&s->buffer, &s->d.style_flag, 1);
275 av_bprint_append_any(&s->buffer, &s->d.style_fontsize, 1);
276 av_bprint_append_any(&s->buffer, &style_color, 4);
280 // We can't build a complete font table since that would require
281 // scanning all dialogs first. But we can at least fill in what
282 // is avaiable in the ASS header
283 if (style && ass->styles_count) {
284 // Find unique font names
285 av_dynarray_add(&s->fonts, &s->font_count, style->font_name);
286 font_names_total_len += strlen(style->font_name);
287 for (i = 0; i < ass->styles_count; i++) {
289 for (j = 0; j < s->font_count; j++) {
290 if (!strcmp(s->fonts[j], ass->styles[i].font_name)) {
296 av_dynarray_add(&s->fonts, &s->font_count,
297 ass->styles[i].font_name);
298 font_names_total_len += strlen(ass->styles[i].font_name);
302 av_dynarray_add(&s->fonts, &s->font_count, (char*)"Serif");
305 tsmb_size = SIZE_ADD + 3 * s->font_count + font_names_total_len;
306 tsmb_size = AV_RB32(&tsmb_size);
307 tsmb_type = MKTAG('f','t','a','b');
308 count = AV_RB16(&s->font_count);
309 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
310 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
311 av_bprint_append_any(&s->buffer, &count, 2);
313 for (i = 0; i < s->font_count; i++) {
316 fontID = AV_RB16(&fontID);
317 av_bprint_append_any(&s->buffer, &fontID, 2);
318 len = strlen(s->fonts[i]);
319 av_bprint_append_any(&s->buffer, &len, 1);
320 av_bprint_append_any(&s->buffer, s->fonts[i], len);
325 if (!av_bprint_is_complete(&s->buffer)) {
326 return AVERROR(ENOMEM);
329 avctx->extradata_size = s->buffer.len;
330 avctx->extradata = av_mallocz(avctx->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
331 if (!avctx->extradata) {
332 return AVERROR(ENOMEM);
335 memcpy(avctx->extradata, s->buffer.str, avctx->extradata_size);
336 av_bprint_clear(&s->buffer);
341 static av_cold int mov_text_encode_init(AVCodecContext *avctx)
344 MovTextContext *s = avctx->priv_data;
347 av_bprint_init(&s->buffer, 0, AV_BPRINT_SIZE_UNLIMITED);
349 s->style_attributes_temp = av_mallocz(sizeof(*s->style_attributes_temp));
350 if (!s->style_attributes_temp) {
351 ret = AVERROR(ENOMEM);
355 s->ass_ctx = ff_ass_split(avctx->subtitle_header);
357 ret = AVERROR_INVALIDDATA;
360 ret = encode_sample_description(avctx);
367 mov_text_encode_close(avctx);
371 // Start a new style box if needed
372 static int mov_text_style_start(MovTextContext *s)
374 // there's an existing style entry
375 if (s->style_attributes_temp->style_start == s->text_pos)
376 // Still at same text pos, use same entry
378 if (s->style_attributes_temp->style_flag != s->d.style_flag ||
379 s->style_attributes_temp->style_color != s->d.style_color ||
380 s->style_attributes_temp->style_fontID != s->d.style_fontID ||
381 s->style_attributes_temp->style_fontsize != s->d.style_fontsize) {
382 // last style != defaults, end the style entry and start a new one
383 s->box_flags |= STYL_BOX;
384 s->style_attributes_temp->style_end = s->text_pos;
385 av_dynarray_add(&s->style_attributes, &s->count, s->style_attributes_temp);
386 s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
387 if (!s->style_attributes_temp) {
389 av_bprint_clear(&s->buffer);
390 s->box_flags &= ~STYL_BOX;
394 *s->style_attributes_temp = s->d;
395 s->style_attributes_temp->style_start = s->text_pos;
396 } else { // style entry matches defaults, drop entry
397 *s->style_attributes_temp = s->d;
398 s->style_attributes_temp->style_start = s->text_pos;
403 static uint8_t mov_text_style_to_flag(const char style)
405 uint8_t style_flag = 0;
409 style_flag = STYLE_FLAG_BOLD;
412 style_flag = STYLE_FLAG_ITALIC;
415 style_flag = STYLE_FLAG_UNDERLINE;
421 static void mov_text_style_set(MovTextContext *s, uint8_t style_flags)
423 if (!s->style_attributes_temp ||
424 !((s->style_attributes_temp->style_flag & style_flags) ^ style_flags)) {
425 // setting flags that that are already set
428 if (mov_text_style_start(s))
429 s->style_attributes_temp->style_flag |= style_flags;
432 static void mov_text_style_cb(void *priv, const char style, int close)
434 MovTextContext *s = priv;
435 uint8_t style_flag = mov_text_style_to_flag(style);
437 if (!s->style_attributes_temp ||
438 !!(s->style_attributes_temp->style_flag & style_flag) != close) {
439 // setting flag that is already set
442 if (mov_text_style_start(s)) {
444 s->style_attributes_temp->style_flag |= style_flag;
446 s->style_attributes_temp->style_flag &= ~style_flag;
450 static void mov_text_color_set(MovTextContext *s, uint32_t color)
452 if (!s->style_attributes_temp ||
453 (s->style_attributes_temp->style_color & 0xffffff00) == color) {
454 // color hasn't changed
457 if (mov_text_style_start(s))
458 s->style_attributes_temp->style_color = (color & 0xffffff00) |
459 (s->style_attributes_temp->style_color & 0xff);
462 static void mov_text_color_cb(void *priv, unsigned int color, unsigned int color_id)
464 MovTextContext *s = priv;
466 color = BGR_TO_RGB(color) << 8;
467 if (color_id == 1) { //primary color changes
468 mov_text_color_set(s, color);
469 } else if (color_id == 2) { //secondary color changes
470 if (s->box_flags & HLIT_BOX) { //close tag
471 s->hlit.end = s->text_pos;
473 s->box_flags |= HCLR_BOX;
474 s->box_flags |= HLIT_BOX;
475 s->hlit.start = s->text_pos;
476 s->hclr.color = color | 0xFF; //set alpha value to FF
479 /* If there are more than one secondary color changes in ASS, take start of
480 first section and end of last section. Movtext allows only one
481 highlight box per sample.
485 static void mov_text_alpha_set(MovTextContext *s, uint8_t alpha)
487 if (!s->style_attributes_temp ||
488 (s->style_attributes_temp->style_color & 0xff) == alpha) {
489 // color hasn't changed
492 if (mov_text_style_start(s))
493 s->style_attributes_temp->style_color =
494 (s->style_attributes_temp->style_color & 0xffffff00) | alpha;
497 static void mov_text_alpha_cb(void *priv, int alpha, int alpha_id)
499 MovTextContext *s = priv;
501 if (alpha_id == 1) // primary alpha changes
502 mov_text_alpha_set(s, 255 - alpha);
505 static uint16_t find_font_id(MovTextContext * s, const char * name)
508 for (i = 0; i < s->font_count; i++) {
509 if (!strcmp(name, s->fonts[i]))
515 static void mov_text_font_name_set(MovTextContext *s, const char *name)
517 int fontID = find_font_id(s, name);
518 if (!s->style_attributes_temp ||
519 s->style_attributes_temp->style_fontID == fontID) {
520 // color hasn't changed
523 if (mov_text_style_start(s))
524 s->style_attributes_temp->style_fontID = fontID;
527 static void mov_text_font_name_cb(void *priv, const char *name)
529 mov_text_font_name_set((MovTextContext*)priv, name);
532 static void mov_text_font_size_set(MovTextContext *s, int size)
534 if (!s->style_attributes_temp ||
535 s->style_attributes_temp->style_fontsize == size) {
536 // color hasn't changed
539 if (mov_text_style_start(s))
540 s->style_attributes_temp->style_fontsize = size;
543 static void mov_text_font_size_cb(void *priv, int size)
545 mov_text_font_size_set((MovTextContext*)priv, size);
548 static void mov_text_end_cb(void *priv)
550 // End of text, close any open style record
551 mov_text_style_start((MovTextContext*)priv);
554 static void mov_text_ass_style_set(MovTextContext *s, ASSStyle *style)
556 uint8_t style_flags, alpha;
560 style_flags = (!!style->bold * STYLE_FLAG_BOLD) |
561 (!!style->italic * STYLE_FLAG_ITALIC) |
562 (!!style->underline * STYLE_FLAG_UNDERLINE);
563 mov_text_style_set(s, style_flags);
564 color = BGR_TO_RGB(style->primary_color & 0xffffff) << 8;
565 mov_text_color_set(s, color);
566 alpha = 255 - ((uint32_t)style->primary_color >> 24);
567 mov_text_alpha_set(s, alpha);
568 mov_text_font_size_set(s, style->font_size);
569 mov_text_font_name_set(s, style->font_name);
571 // End current style record, go back to defaults
572 mov_text_style_start(s);
576 static void mov_text_dialog(MovTextContext *s, ASSDialog *dialog)
578 ASSStyle * style = ff_ass_style_get(s->ass_ctx, dialog->style);
580 s->ass_dialog_style = style;
581 mov_text_ass_style_set(s, style);
584 static void mov_text_cancel_overrides_cb(void *priv, const char * style_name)
586 MovTextContext *s = priv;
589 if (!style_name || !*style_name)
590 style = s->ass_dialog_style;
592 style= ff_ass_style_get(s->ass_ctx, style_name);
594 mov_text_ass_style_set(s, style);
597 static uint16_t utf8_strlen(const char *text, int len)
599 uint16_t i = 0, ret = 0;
604 else if ((c & 0xE0) == 0xC0)
606 else if ((c & 0xF0) == 0xE0)
608 else if ((c & 0xF8) == 0xF0)
617 static void mov_text_text_cb(void *priv, const char *text, int len)
619 uint16_t utf8_len = utf8_strlen(text, len);
620 MovTextContext *s = priv;
621 av_bprint_append_data(&s->buffer, text, len);
622 // If it's not utf-8, just use the byte length
623 s->text_pos += utf8_len ? utf8_len : len;
624 s->byte_count += len;
627 static void mov_text_new_line_cb(void *priv, int forced)
629 MovTextContext *s = priv;
630 av_bprint_append_data(&s->buffer, "\n", 1);
635 static const ASSCodesCallbacks mov_text_callbacks = {
636 .text = mov_text_text_cb,
637 .new_line = mov_text_new_line_cb,
638 .style = mov_text_style_cb,
639 .color = mov_text_color_cb,
640 .alpha = mov_text_alpha_cb,
641 .font_name = mov_text_font_name_cb,
642 .font_size = mov_text_font_size_cb,
643 .cancel_overrides = mov_text_cancel_overrides_cb,
644 .end = mov_text_end_cb,
647 static int mov_text_encode_frame(AVCodecContext *avctx, unsigned char *buf,
648 int bufsize, const AVSubtitle *sub)
650 MovTextContext *s = avctx->priv_data;
659 for (i = 0; i < sub->num_rects; i++) {
660 const char *ass = sub->rects[i]->ass;
662 if (sub->rects[i]->type != SUBTITLE_ASS) {
663 av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n");
664 return AVERROR(ENOSYS);
667 #if FF_API_ASS_TIMING
668 if (!strncmp(ass, "Dialogue: ", 10)) {
670 dialog = ff_ass_split_dialog(s->ass_ctx, ass, 0, &num);
671 for (; dialog && num--; dialog++) {
672 mov_text_dialog(s, dialog);
673 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
677 dialog = ff_ass_split_dialog2(s->ass_ctx, ass);
679 return AVERROR(ENOMEM);
680 mov_text_dialog(s, dialog);
681 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
682 ff_ass_free_dialog(&dialog);
683 #if FF_API_ASS_TIMING
687 for (j = 0; j < box_count; j++) {
688 box_types[j].encode(s, box_types[j].type);
692 AV_WB16(buf, s->byte_count);
695 if (!av_bprint_is_complete(&s->buffer)) {
696 length = AVERROR(ENOMEM);
700 if (!s->buffer.len) {
705 if (s->buffer.len > bufsize - 3) {
706 av_log(avctx, AV_LOG_ERROR, "Buffer too small for ASS event.\n");
707 length = AVERROR(EINVAL);
711 memcpy(buf, s->buffer.str, s->buffer.len);
712 length = s->buffer.len + 2;
715 av_bprint_clear(&s->buffer);
719 AVCodec ff_movtext_encoder = {
721 .long_name = NULL_IF_CONFIG_SMALL("3GPP Timed Text subtitle"),
722 .type = AVMEDIA_TYPE_SUBTITLE,
723 .id = AV_CODEC_ID_MOV_TEXT,
724 .priv_data_size = sizeof(MovTextContext),
725 .init = mov_text_encode_init,
726 .encode_sub = mov_text_encode_frame,
727 .close = mov_text_encode_close,