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->style_flag = 0;
101 s->style_attributes_temp->style_start = 0;
105 static void encode_styl(MovTextContext *s, uint32_t tsmb_type)
109 uint16_t style_entries;
110 if ((s->box_flags & STYL_BOX) && s->count) {
111 tsmb_size = s->count * STYLE_RECORD_SIZE + SIZE_ADD;
112 tsmb_size = AV_RB32(&tsmb_size);
113 style_entries = AV_RB16(&s->count);
114 /*The above three attributes are hard coded for now
115 but will come from ASS style in the future*/
116 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
117 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
118 av_bprint_append_any(&s->buffer, &style_entries, 2);
119 for (j = 0; j < s->count; j++) {
120 uint16_t style_start, style_end, style_fontID;
121 uint32_t style_color;
123 style_start = AV_RB16(&s->style_attributes[j]->style_start);
124 style_end = AV_RB16(&s->style_attributes[j]->style_end);
125 style_color = AV_RB32(&s->style_attributes[j]->style_color);
126 style_fontID = AV_RB16(&s->d.style_fontID);
128 av_bprint_append_any(&s->buffer, &style_start, 2);
129 av_bprint_append_any(&s->buffer, &style_end, 2);
130 av_bprint_append_any(&s->buffer, &style_fontID, 2);
131 av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_flag, 1);
132 av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_fontsize, 1);
133 av_bprint_append_any(&s->buffer, &style_color, 4);
139 static void encode_hlit(MovTextContext *s, uint32_t tsmb_type)
143 if (s->box_flags & HLIT_BOX) {
145 tsmb_size = AV_RB32(&tsmb_size);
146 start = AV_RB16(&s->hlit.start);
147 end = AV_RB16(&s->hlit.end);
148 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
149 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
150 av_bprint_append_any(&s->buffer, &start, 2);
151 av_bprint_append_any(&s->buffer, &end, 2);
155 static void encode_hclr(MovTextContext *s, uint32_t tsmb_type)
157 uint32_t tsmb_size, color;
158 if (s->box_flags & HCLR_BOX) {
160 tsmb_size = AV_RB32(&tsmb_size);
161 color = AV_RB32(&s->hclr.color);
162 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
163 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
164 av_bprint_append_any(&s->buffer, &color, 4);
168 static const Box box_types[] = {
169 { MKTAG('s','t','y','l'), encode_styl },
170 { MKTAG('h','l','i','t'), encode_hlit },
171 { MKTAG('h','c','l','r'), encode_hclr },
174 const static size_t box_count = FF_ARRAY_ELEMS(box_types);
176 static av_cold int mov_text_encode_init(AVCodecContext *avctx)
179 * For now, we'll use a fixed default style. When we add styling
180 * support, this will be generated from the ASS style.
182 static const uint8_t text_sample_entry[] = {
183 0x00, 0x00, 0x00, 0x00, // uint32_t displayFlags
184 0x01, // int8_t horizontal-justification
185 0xFF, // int8_t vertical-justification
186 0x00, 0x00, 0x00, 0x00, // uint8_t background-color-rgba[4]
188 0x00, 0x00, // int16_t top
189 0x00, 0x00, // int16_t left
190 0x00, 0x00, // int16_t bottom
191 0x00, 0x00, // int16_t right
194 0x00, 0x00, // uint16_t startChar
195 0x00, 0x00, // uint16_t endChar
196 0x00, 0x01, // uint16_t font-ID
197 0x00, // uint8_t face-style-flags
198 0x12, // uint8_t font-size
199 0xFF, 0xFF, 0xFF, 0xFF, // uint8_t text-color-rgba[4]
202 0x00, 0x00, 0x00, 0x12, // uint32_t size
203 'f', 't', 'a', 'b', // uint8_t name[4]
204 0x00, 0x01, // uint16_t entry-count
206 0x00, 0x01, // uint16_t font-ID
207 0x05, // uint8_t font-name-length
208 'S', 'e', 'r', 'i', 'f',// uint8_t font[font-name-length]
213 MovTextContext *s = avctx->priv_data;
216 s->style_attributes_temp = av_mallocz(sizeof(*s->style_attributes_temp));
217 if (!s->style_attributes_temp) {
218 return AVERROR(ENOMEM);
221 avctx->extradata_size = sizeof text_sample_entry;
222 avctx->extradata = av_mallocz(avctx->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
223 if (!avctx->extradata)
224 return AVERROR(ENOMEM);
226 av_bprint_init(&s->buffer, 0, AV_BPRINT_SIZE_UNLIMITED);
228 memcpy(avctx->extradata, text_sample_entry, avctx->extradata_size);
230 s->ass_ctx = ff_ass_split(avctx->subtitle_header);
232 // TODO: Initialize from ASS style record
233 s->d.style_fontID = DEFAULT_STYLE_FONT_ID;
234 s->d.style_fontsize = DEFAULT_STYLE_FONTSIZE;
235 s->d.style_color = DEFAULT_STYLE_COLOR;
236 s->d.style_flag = DEFAULT_STYLE_FLAG;
238 return s->ass_ctx ? 0 : AVERROR_INVALIDDATA;
241 // Start a new style box if needed
242 static int mov_text_style_start(MovTextContext *s)
244 // there's an existing style entry
245 if (s->style_attributes_temp->style_start == s->text_pos)
246 // Still at same text pos, use same entry
248 if (s->style_attributes_temp->style_flag != s->d.style_flag ||
249 s->style_attributes_temp->style_color != s->d.style_color ||
250 s->style_attributes_temp->style_fontsize != s->d.style_fontsize) {
251 // last style != defaults, end the style entry and start a new one
252 s->box_flags |= STYL_BOX;
253 s->style_attributes_temp->style_end = s->text_pos;
254 av_dynarray_add(&s->style_attributes, &s->count, s->style_attributes_temp);
255 s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
256 if (!s->style_attributes_temp) {
258 av_bprint_clear(&s->buffer);
259 s->box_flags &= ~STYL_BOX;
263 s->style_attributes_temp->style_flag = s->style_attributes[s->count - 1]->style_flag;
264 s->style_attributes_temp->style_color = s->style_attributes[s->count - 1]->style_color;
265 s->style_attributes_temp->style_fontsize = s->style_attributes[s->count - 1]->style_fontsize;
266 s->style_attributes_temp->style_start = s->text_pos;
267 } else { // style entry matches defaults, drop entry
268 s->style_attributes_temp->style_flag = s->d.style_flag;
269 s->style_attributes_temp->style_color = s->d.style_color;
270 s->style_attributes_temp->style_fontsize = s->d.style_fontsize;
271 s->style_attributes_temp->style_start = s->text_pos;
276 static uint8_t mov_text_style_to_flag(const char style)
278 uint8_t style_flag = 0;
282 style_flag = STYLE_FLAG_BOLD;
285 style_flag = STYLE_FLAG_ITALIC;
288 style_flag = STYLE_FLAG_UNDERLINE;
294 static void mov_text_style_set(MovTextContext *s, uint8_t style_flags)
296 if (!s->style_attributes_temp ||
297 !((s->style_attributes_temp->style_flag & style_flags) ^ style_flags)) {
298 // setting flags that that are already set
301 if (mov_text_style_start(s))
302 s->style_attributes_temp->style_flag |= style_flags;
305 static void mov_text_style_cb(void *priv, const char style, int close)
307 MovTextContext *s = priv;
308 uint8_t style_flag = mov_text_style_to_flag(style);
310 if (!s->style_attributes_temp ||
311 !!(s->style_attributes_temp->style_flag & style_flag) != close) {
312 // setting flag that is already set
315 if (mov_text_style_start(s)) {
317 s->style_attributes_temp->style_flag |= style_flag;
319 s->style_attributes_temp->style_flag &= ~style_flag;
323 static void mov_text_color_set(MovTextContext *s, uint32_t color)
325 if (!s->style_attributes_temp ||
326 (s->style_attributes_temp->style_color & 0xffffff00) == color) {
327 // color hasn't changed
330 if (mov_text_style_start(s))
331 s->style_attributes_temp->style_color = (color & 0xffffff00) |
332 (s->style_attributes_temp->style_color & 0xff);
335 static void mov_text_color_cb(void *priv, unsigned int color, unsigned int color_id)
337 MovTextContext *s = priv;
339 color = BGR_TO_RGB(color) << 8;
340 if (color_id == 1) { //primary color changes
341 mov_text_color_set(s, color);
342 } else if (color_id == 2) { //secondary color changes
343 if (s->box_flags & HLIT_BOX) { //close tag
344 s->hlit.end = s->text_pos;
346 s->box_flags |= HCLR_BOX;
347 s->box_flags |= HLIT_BOX;
348 s->hlit.start = s->text_pos;
349 s->hclr.color = color | 0xFF; //set alpha value to FF
352 /* If there are more than one secondary color changes in ASS, take start of
353 first section and end of last section. Movtext allows only one
354 highlight box per sample.
358 static void mov_text_alpha_set(MovTextContext *s, uint8_t alpha)
360 if (!s->style_attributes_temp ||
361 (s->style_attributes_temp->style_color & 0xff) == alpha) {
362 // color hasn't changed
365 if (mov_text_style_start(s))
366 s->style_attributes_temp->style_color =
367 (s->style_attributes_temp->style_color & 0xffffff00) | alpha;
370 static void mov_text_alpha_cb(void *priv, int alpha, int alpha_id)
372 MovTextContext *s = priv;
374 if (alpha_id == 1) // primary alpha changes
375 mov_text_alpha_set(s, 255 - alpha);
378 static void mov_text_font_size_set(MovTextContext *s, int size)
380 if (!s->style_attributes_temp ||
381 s->style_attributes_temp->style_fontsize == size) {
382 // color hasn't changed
385 if (mov_text_style_start(s))
386 s->style_attributes_temp->style_fontsize = size;
389 static void mov_text_font_size_cb(void *priv, int size)
391 mov_text_font_size_set((MovTextContext*)priv, size);
394 static void mov_text_end_cb(void *priv)
396 // End of text, close any open style record
397 mov_text_style_start((MovTextContext*)priv);
400 static void mov_text_ass_style_set(MovTextContext *s, ASSStyle *style)
402 uint8_t style_flags, alpha;
406 style_flags = (!!style->bold * STYLE_FLAG_BOLD) |
407 (!!style->italic * STYLE_FLAG_ITALIC) |
408 (!!style->underline * STYLE_FLAG_UNDERLINE);
409 mov_text_style_set(s, style_flags);
410 color = BGR_TO_RGB(style->primary_color & 0xffffff) << 8;
411 mov_text_color_set(s, color);
412 alpha = 255 - ((uint32_t)style->primary_color >> 24);
413 mov_text_alpha_set(s, alpha);
414 mov_text_font_size_set(s, style->font_size);
416 // End current style record, go back to defaults
417 mov_text_style_start(s);
421 static void mov_text_dialog(MovTextContext *s, ASSDialog *dialog)
423 ASSStyle * style = ff_ass_style_get(s->ass_ctx, dialog->style);
425 s->ass_dialog_style = style;
426 mov_text_ass_style_set(s, style);
429 static void mov_text_cancel_overrides_cb(void *priv, const char * style_name)
431 MovTextContext *s = priv;
434 if (!style_name || !*style_name)
435 style = s->ass_dialog_style;
437 style= ff_ass_style_get(s->ass_ctx, style_name);
439 mov_text_ass_style_set(s, style);
442 static uint16_t utf8_strlen(const char *text, int len)
444 uint16_t i = 0, ret = 0;
449 else if ((c & 0xE0) == 0xC0)
451 else if ((c & 0xF0) == 0xE0)
453 else if ((c & 0xF8) == 0xF0)
462 static void mov_text_text_cb(void *priv, const char *text, int len)
464 uint16_t utf8_len = utf8_strlen(text, len);
465 MovTextContext *s = priv;
466 av_bprint_append_data(&s->buffer, text, len);
467 // If it's not utf-8, just use the byte length
468 s->text_pos += utf8_len ? utf8_len : len;
469 s->byte_count += len;
472 static void mov_text_new_line_cb(void *priv, int forced)
474 MovTextContext *s = priv;
475 av_bprint_append_data(&s->buffer, "\n", 1);
480 static const ASSCodesCallbacks mov_text_callbacks = {
481 .text = mov_text_text_cb,
482 .new_line = mov_text_new_line_cb,
483 .style = mov_text_style_cb,
484 .color = mov_text_color_cb,
485 .alpha = mov_text_alpha_cb,
486 .font_size = mov_text_font_size_cb,
487 .cancel_overrides = mov_text_cancel_overrides_cb,
488 .end = mov_text_end_cb,
491 static int mov_text_encode_frame(AVCodecContext *avctx, unsigned char *buf,
492 int bufsize, const AVSubtitle *sub)
494 MovTextContext *s = avctx->priv_data;
503 for (i = 0; i < sub->num_rects; i++) {
504 const char *ass = sub->rects[i]->ass;
506 if (sub->rects[i]->type != SUBTITLE_ASS) {
507 av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n");
508 return AVERROR(ENOSYS);
511 #if FF_API_ASS_TIMING
512 if (!strncmp(ass, "Dialogue: ", 10)) {
514 dialog = ff_ass_split_dialog(s->ass_ctx, ass, 0, &num);
515 for (; dialog && num--; dialog++) {
516 mov_text_dialog(s, dialog);
517 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
521 dialog = ff_ass_split_dialog2(s->ass_ctx, ass);
523 return AVERROR(ENOMEM);
524 mov_text_dialog(s, dialog);
525 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
526 ff_ass_free_dialog(&dialog);
527 #if FF_API_ASS_TIMING
531 for (j = 0; j < box_count; j++) {
532 box_types[j].encode(s, box_types[j].type);
536 AV_WB16(buf, s->byte_count);
539 if (!av_bprint_is_complete(&s->buffer)) {
540 length = AVERROR(ENOMEM);
544 if (!s->buffer.len) {
549 if (s->buffer.len > bufsize - 3) {
550 av_log(avctx, AV_LOG_ERROR, "Buffer too small for ASS event.\n");
551 length = AVERROR(EINVAL);
555 memcpy(buf, s->buffer.str, s->buffer.len);
556 length = s->buffer.len + 2;
559 av_bprint_clear(&s->buffer);
563 static int mov_text_encode_close(AVCodecContext *avctx)
565 MovTextContext *s = avctx->priv_data;
566 ff_ass_split_free(s->ass_ctx);
567 av_bprint_finalize(&s->buffer, NULL);
571 AVCodec ff_movtext_encoder = {
573 .long_name = NULL_IF_CONFIG_SMALL("3GPP Timed Text subtitle"),
574 .type = AVMEDIA_TYPE_SUBTITLE,
575 .id = AV_CODEC_ID_MOV_TEXT,
576 .priv_data_size = sizeof(MovTextContext),
577 .init = mov_text_encode_init,
578 .encode_sub = mov_text_encode_frame,
579 .close = mov_text_encode_close,