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;
87 void (*encode)(MovTextContext *s, uint32_t tsmb_type);
90 static void mov_text_cleanup(MovTextContext *s)
93 if (s->box_flags & STYL_BOX) {
94 for (j = 0; j < s->count; j++) {
95 av_freep(&s->style_attributes[j]);
97 av_freep(&s->style_attributes);
99 if (s->style_attributes_temp) {
100 *s->style_attributes_temp = s->d;
104 static void encode_styl(MovTextContext *s, uint32_t tsmb_type)
108 uint16_t style_entries;
109 if ((s->box_flags & STYL_BOX) && s->count) {
110 tsmb_size = s->count * STYLE_RECORD_SIZE + SIZE_ADD;
111 tsmb_size = AV_RB32(&tsmb_size);
112 style_entries = AV_RB16(&s->count);
113 /*The above three attributes are hard coded for now
114 but will come from ASS style in the future*/
115 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
116 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
117 av_bprint_append_any(&s->buffer, &style_entries, 2);
118 for (j = 0; j < s->count; j++) {
119 uint16_t style_start, style_end, style_fontID;
120 uint32_t style_color;
122 style_start = AV_RB16(&s->style_attributes[j]->style_start);
123 style_end = AV_RB16(&s->style_attributes[j]->style_end);
124 style_color = AV_RB32(&s->style_attributes[j]->style_color);
125 style_fontID = AV_RB16(&s->style_attributes[j]->style_fontID);
127 av_bprint_append_any(&s->buffer, &style_start, 2);
128 av_bprint_append_any(&s->buffer, &style_end, 2);
129 av_bprint_append_any(&s->buffer, &style_fontID, 2);
130 av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_flag, 1);
131 av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_fontsize, 1);
132 av_bprint_append_any(&s->buffer, &style_color, 4);
138 static void encode_hlit(MovTextContext *s, uint32_t tsmb_type)
142 if (s->box_flags & HLIT_BOX) {
144 tsmb_size = AV_RB32(&tsmb_size);
145 start = AV_RB16(&s->hlit.start);
146 end = AV_RB16(&s->hlit.end);
147 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
148 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
149 av_bprint_append_any(&s->buffer, &start, 2);
150 av_bprint_append_any(&s->buffer, &end, 2);
154 static void encode_hclr(MovTextContext *s, uint32_t tsmb_type)
156 uint32_t tsmb_size, color;
157 if (s->box_flags & HCLR_BOX) {
159 tsmb_size = AV_RB32(&tsmb_size);
160 color = AV_RB32(&s->hclr.color);
161 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
162 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
163 av_bprint_append_any(&s->buffer, &color, 4);
167 static const Box box_types[] = {
168 { MKTAG('s','t','y','l'), encode_styl },
169 { MKTAG('h','l','i','t'), encode_hlit },
170 { MKTAG('h','c','l','r'), encode_hclr },
173 const static size_t box_count = FF_ARRAY_ELEMS(box_types);
175 static av_cold int mov_text_encode_init(AVCodecContext *avctx)
178 * For now, we'll use a fixed default style. When we add styling
179 * support, this will be generated from the ASS style.
181 static const uint8_t text_sample_entry[] = {
182 0x00, 0x00, 0x00, 0x00, // uint32_t displayFlags
183 0x01, // int8_t horizontal-justification
184 0xFF, // int8_t vertical-justification
185 0x00, 0x00, 0x00, 0x00, // uint8_t background-color-rgba[4]
187 0x00, 0x00, // int16_t top
188 0x00, 0x00, // int16_t left
189 0x00, 0x00, // int16_t bottom
190 0x00, 0x00, // int16_t right
193 0x00, 0x00, // uint16_t startChar
194 0x00, 0x00, // uint16_t endChar
195 0x00, 0x01, // uint16_t font-ID
196 0x00, // uint8_t face-style-flags
197 0x12, // uint8_t font-size
198 0xFF, 0xFF, 0xFF, 0xFF, // uint8_t text-color-rgba[4]
201 0x00, 0x00, 0x00, 0x12, // uint32_t size
202 'f', 't', 'a', 'b', // uint8_t name[4]
203 0x00, 0x01, // uint16_t entry-count
205 0x00, 0x01, // uint16_t font-ID
206 0x05, // uint8_t font-name-length
207 'S', 'e', 'r', 'i', 'f',// uint8_t font[font-name-length]
212 MovTextContext *s = avctx->priv_data;
215 s->style_attributes_temp = av_mallocz(sizeof(*s->style_attributes_temp));
216 if (!s->style_attributes_temp) {
217 return AVERROR(ENOMEM);
220 avctx->extradata_size = sizeof text_sample_entry;
221 avctx->extradata = av_mallocz(avctx->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
222 if (!avctx->extradata)
223 return AVERROR(ENOMEM);
225 av_bprint_init(&s->buffer, 0, AV_BPRINT_SIZE_UNLIMITED);
227 memcpy(avctx->extradata, text_sample_entry, avctx->extradata_size);
229 s->ass_ctx = ff_ass_split(avctx->subtitle_header);
231 // TODO: Initialize from ASS style record
232 s->d.style_fontID = DEFAULT_STYLE_FONT_ID;
233 s->d.style_fontsize = DEFAULT_STYLE_FONTSIZE;
234 s->d.style_color = DEFAULT_STYLE_COLOR;
235 s->d.style_flag = DEFAULT_STYLE_FLAG;
237 return s->ass_ctx ? 0 : AVERROR_INVALIDDATA;
240 // Start a new style box if needed
241 static int mov_text_style_start(MovTextContext *s)
243 // there's an existing style entry
244 if (s->style_attributes_temp->style_start == s->text_pos)
245 // Still at same text pos, use same entry
247 if (s->style_attributes_temp->style_flag != s->d.style_flag ||
248 s->style_attributes_temp->style_color != s->d.style_color ||
249 s->style_attributes_temp->style_fontsize != s->d.style_fontsize) {
250 // last style != defaults, end the style entry and start a new one
251 s->box_flags |= STYL_BOX;
252 s->style_attributes_temp->style_end = s->text_pos;
253 av_dynarray_add(&s->style_attributes, &s->count, s->style_attributes_temp);
254 s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
255 if (!s->style_attributes_temp) {
257 av_bprint_clear(&s->buffer);
258 s->box_flags &= ~STYL_BOX;
262 *s->style_attributes_temp = s->d;
263 s->style_attributes_temp->style_start = s->text_pos;
264 } else { // style entry matches defaults, drop entry
265 *s->style_attributes_temp = s->d;
266 s->style_attributes_temp->style_start = s->text_pos;
271 static uint8_t mov_text_style_to_flag(const char style)
273 uint8_t style_flag = 0;
277 style_flag = STYLE_FLAG_BOLD;
280 style_flag = STYLE_FLAG_ITALIC;
283 style_flag = STYLE_FLAG_UNDERLINE;
289 static void mov_text_style_set(MovTextContext *s, uint8_t style_flags)
291 if (!s->style_attributes_temp ||
292 !((s->style_attributes_temp->style_flag & style_flags) ^ style_flags)) {
293 // setting flags that that are already set
296 if (mov_text_style_start(s))
297 s->style_attributes_temp->style_flag |= style_flags;
300 static void mov_text_style_cb(void *priv, const char style, int close)
302 MovTextContext *s = priv;
303 uint8_t style_flag = mov_text_style_to_flag(style);
305 if (!s->style_attributes_temp ||
306 !!(s->style_attributes_temp->style_flag & style_flag) != close) {
307 // setting flag that is already set
310 if (mov_text_style_start(s)) {
312 s->style_attributes_temp->style_flag |= style_flag;
314 s->style_attributes_temp->style_flag &= ~style_flag;
318 static void mov_text_color_set(MovTextContext *s, uint32_t color)
320 if (!s->style_attributes_temp ||
321 (s->style_attributes_temp->style_color & 0xffffff00) == color) {
322 // color hasn't changed
325 if (mov_text_style_start(s))
326 s->style_attributes_temp->style_color = (color & 0xffffff00) |
327 (s->style_attributes_temp->style_color & 0xff);
330 static void mov_text_color_cb(void *priv, unsigned int color, unsigned int color_id)
332 MovTextContext *s = priv;
334 color = BGR_TO_RGB(color) << 8;
335 if (color_id == 1) { //primary color changes
336 mov_text_color_set(s, color);
337 } else if (color_id == 2) { //secondary color changes
338 if (s->box_flags & HLIT_BOX) { //close tag
339 s->hlit.end = s->text_pos;
341 s->box_flags |= HCLR_BOX;
342 s->box_flags |= HLIT_BOX;
343 s->hlit.start = s->text_pos;
344 s->hclr.color = color | 0xFF; //set alpha value to FF
347 /* If there are more than one secondary color changes in ASS, take start of
348 first section and end of last section. Movtext allows only one
349 highlight box per sample.
353 static void mov_text_alpha_set(MovTextContext *s, uint8_t alpha)
355 if (!s->style_attributes_temp ||
356 (s->style_attributes_temp->style_color & 0xff) == alpha) {
357 // color hasn't changed
360 if (mov_text_style_start(s))
361 s->style_attributes_temp->style_color =
362 (s->style_attributes_temp->style_color & 0xffffff00) | alpha;
365 static void mov_text_alpha_cb(void *priv, int alpha, int alpha_id)
367 MovTextContext *s = priv;
369 if (alpha_id == 1) // primary alpha changes
370 mov_text_alpha_set(s, 255 - alpha);
373 static void mov_text_font_size_set(MovTextContext *s, int size)
375 if (!s->style_attributes_temp ||
376 s->style_attributes_temp->style_fontsize == size) {
377 // color hasn't changed
380 if (mov_text_style_start(s))
381 s->style_attributes_temp->style_fontsize = size;
384 static void mov_text_font_size_cb(void *priv, int size)
386 mov_text_font_size_set((MovTextContext*)priv, size);
389 static void mov_text_end_cb(void *priv)
391 // End of text, close any open style record
392 mov_text_style_start((MovTextContext*)priv);
395 static void mov_text_ass_style_set(MovTextContext *s, ASSStyle *style)
397 uint8_t style_flags, alpha;
401 style_flags = (!!style->bold * STYLE_FLAG_BOLD) |
402 (!!style->italic * STYLE_FLAG_ITALIC) |
403 (!!style->underline * STYLE_FLAG_UNDERLINE);
404 mov_text_style_set(s, style_flags);
405 color = BGR_TO_RGB(style->primary_color & 0xffffff) << 8;
406 mov_text_color_set(s, color);
407 alpha = 255 - ((uint32_t)style->primary_color >> 24);
408 mov_text_alpha_set(s, alpha);
409 mov_text_font_size_set(s, style->font_size);
411 // End current style record, go back to defaults
412 mov_text_style_start(s);
416 static void mov_text_dialog(MovTextContext *s, ASSDialog *dialog)
418 ASSStyle * style = ff_ass_style_get(s->ass_ctx, dialog->style);
420 s->ass_dialog_style = style;
421 mov_text_ass_style_set(s, style);
424 static void mov_text_cancel_overrides_cb(void *priv, const char * style_name)
426 MovTextContext *s = priv;
429 if (!style_name || !*style_name)
430 style = s->ass_dialog_style;
432 style= ff_ass_style_get(s->ass_ctx, style_name);
434 mov_text_ass_style_set(s, style);
437 static uint16_t utf8_strlen(const char *text, int len)
439 uint16_t i = 0, ret = 0;
444 else if ((c & 0xE0) == 0xC0)
446 else if ((c & 0xF0) == 0xE0)
448 else if ((c & 0xF8) == 0xF0)
457 static void mov_text_text_cb(void *priv, const char *text, int len)
459 uint16_t utf8_len = utf8_strlen(text, len);
460 MovTextContext *s = priv;
461 av_bprint_append_data(&s->buffer, text, len);
462 // If it's not utf-8, just use the byte length
463 s->text_pos += utf8_len ? utf8_len : len;
464 s->byte_count += len;
467 static void mov_text_new_line_cb(void *priv, int forced)
469 MovTextContext *s = priv;
470 av_bprint_append_data(&s->buffer, "\n", 1);
475 static const ASSCodesCallbacks mov_text_callbacks = {
476 .text = mov_text_text_cb,
477 .new_line = mov_text_new_line_cb,
478 .style = mov_text_style_cb,
479 .color = mov_text_color_cb,
480 .alpha = mov_text_alpha_cb,
481 .font_size = mov_text_font_size_cb,
482 .cancel_overrides = mov_text_cancel_overrides_cb,
483 .end = mov_text_end_cb,
486 static int mov_text_encode_frame(AVCodecContext *avctx, unsigned char *buf,
487 int bufsize, const AVSubtitle *sub)
489 MovTextContext *s = avctx->priv_data;
498 for (i = 0; i < sub->num_rects; i++) {
499 const char *ass = sub->rects[i]->ass;
501 if (sub->rects[i]->type != SUBTITLE_ASS) {
502 av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n");
503 return AVERROR(ENOSYS);
506 #if FF_API_ASS_TIMING
507 if (!strncmp(ass, "Dialogue: ", 10)) {
509 dialog = ff_ass_split_dialog(s->ass_ctx, ass, 0, &num);
510 for (; dialog && num--; dialog++) {
511 mov_text_dialog(s, dialog);
512 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
516 dialog = ff_ass_split_dialog2(s->ass_ctx, ass);
518 return AVERROR(ENOMEM);
519 mov_text_dialog(s, dialog);
520 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
521 ff_ass_free_dialog(&dialog);
522 #if FF_API_ASS_TIMING
526 for (j = 0; j < box_count; j++) {
527 box_types[j].encode(s, box_types[j].type);
531 AV_WB16(buf, s->byte_count);
534 if (!av_bprint_is_complete(&s->buffer)) {
535 length = AVERROR(ENOMEM);
539 if (!s->buffer.len) {
544 if (s->buffer.len > bufsize - 3) {
545 av_log(avctx, AV_LOG_ERROR, "Buffer too small for ASS event.\n");
546 length = AVERROR(EINVAL);
550 memcpy(buf, s->buffer.str, s->buffer.len);
551 length = s->buffer.len + 2;
554 av_bprint_clear(&s->buffer);
558 static int mov_text_encode_close(AVCodecContext *avctx)
560 MovTextContext *s = avctx->priv_data;
561 ff_ass_split_free(s->ass_ctx);
562 av_bprint_finalize(&s->buffer, NULL);
566 AVCodec ff_movtext_encoder = {
568 .long_name = NULL_IF_CONFIG_SMALL("3GPP Timed Text subtitle"),
569 .type = AVMEDIA_TYPE_SUBTITLE,
570 .id = AV_CODEC_ID_MOV_TEXT,
571 .priv_data_size = sizeof(MovTextContext),
572 .init = mov_text_encode_init,
573 .encode_sub = mov_text_encode_frame,
574 .close = mov_text_encode_close,