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;
73 StyleBox **style_attributes;
74 StyleBox *style_attributes_temp;
86 void (*encode)(MovTextContext *s, uint32_t tsmb_type);
89 static void mov_text_cleanup(MovTextContext *s)
92 if (s->box_flags & STYL_BOX) {
93 for (j = 0; j < s->count; j++) {
94 av_freep(&s->style_attributes[j]);
96 av_freep(&s->style_attributes);
98 if (s->style_attributes_temp) {
99 s->style_attributes_temp->style_flag = 0;
100 s->style_attributes_temp->style_start = 0;
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->d.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->d.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 // last style != defaults, end the style entry and start a new one
250 s->box_flags |= STYL_BOX;
251 s->style_attributes_temp->style_end = s->text_pos;
252 av_dynarray_add(&s->style_attributes, &s->count, s->style_attributes_temp);
253 s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
254 if (!s->style_attributes_temp) {
256 av_bprint_clear(&s->buffer);
257 s->box_flags &= ~STYL_BOX;
261 s->style_attributes_temp->style_flag = s->style_attributes[s->count - 1]->style_flag;
262 s->style_attributes_temp->style_color = s->style_attributes[s->count - 1]->style_color;
263 s->style_attributes_temp->style_start = s->text_pos;
264 } else { // style entry matches defaults, drop entry
265 s->style_attributes_temp->style_flag = s->d.style_flag;
266 s->style_attributes_temp->style_color = s->d.style_color;
267 s->style_attributes_temp->style_start = s->text_pos;
272 static uint8_t mov_text_style_to_flag(const char style)
274 uint8_t style_flag = 0;
278 style_flag = STYLE_FLAG_BOLD;
281 style_flag = STYLE_FLAG_ITALIC;
284 style_flag = STYLE_FLAG_UNDERLINE;
290 static void mov_text_style_set(MovTextContext *s, uint8_t style_flags)
292 if (!s->style_attributes_temp ||
293 !((s->style_attributes_temp->style_flag & style_flags) ^ style_flags)) {
294 // setting flags that that are already set
297 if (mov_text_style_start(s))
298 s->style_attributes_temp->style_flag |= style_flags;
301 static void mov_text_style_cb(void *priv, const char style, int close)
303 MovTextContext *s = priv;
304 uint8_t style_flag = mov_text_style_to_flag(style);
306 if (!s->style_attributes_temp ||
307 !!(s->style_attributes_temp->style_flag & style_flag) != close) {
308 // setting flag that is already set
311 if (mov_text_style_start(s)) {
313 s->style_attributes_temp->style_flag |= style_flag;
315 s->style_attributes_temp->style_flag &= ~style_flag;
319 static void mov_text_color_set(MovTextContext *s, uint32_t color)
321 if (!s->style_attributes_temp ||
322 (s->style_attributes_temp->style_color & 0xffffff00) == color) {
323 // color hasn't changed
326 if (mov_text_style_start(s))
327 s->style_attributes_temp->style_color = (color & 0xffffff00) |
328 (s->style_attributes_temp->style_color & 0xff);
331 static void mov_text_color_cb(void *priv, unsigned int color, unsigned int color_id)
333 MovTextContext *s = priv;
335 color = BGR_TO_RGB(color) << 8;
336 if (color_id == 1) { //primary color changes
337 mov_text_color_set(s, color);
338 } else if (color_id == 2) { //secondary color changes
339 if (s->box_flags & HLIT_BOX) { //close tag
340 s->hlit.end = s->text_pos;
342 s->box_flags |= HCLR_BOX;
343 s->box_flags |= HLIT_BOX;
344 s->hlit.start = s->text_pos;
345 s->hclr.color = color | 0xFF; //set alpha value to FF
348 /* If there are more than one secondary color changes in ASS, take start of
349 first section and end of last section. Movtext allows only one
350 highlight box per sample.
354 static void mov_text_end_cb(void *priv)
356 // End of text, close any open style record
357 mov_text_style_start((MovTextContext*)priv);
360 static void mov_text_dialog(MovTextContext *s, ASSDialog *dialog)
362 ASSStyle * style = ff_ass_style_get(s->ass_ctx, dialog->style);
367 style_flags = (!!style->bold * STYLE_FLAG_BOLD) |
368 (!!style->italic * STYLE_FLAG_ITALIC) |
369 (!!style->underline * STYLE_FLAG_UNDERLINE);
370 mov_text_style_set(s, style_flags);
371 color = BGR_TO_RGB(style->primary_color & 0xffffff) << 8;
372 mov_text_color_set(s, color);
376 static uint16_t utf8_strlen(const char *text, int len)
378 uint16_t i = 0, ret = 0;
383 else if ((c & 0xE0) == 0xC0)
385 else if ((c & 0xF0) == 0xE0)
387 else if ((c & 0xF8) == 0xF0)
396 static void mov_text_text_cb(void *priv, const char *text, int len)
398 uint16_t utf8_len = utf8_strlen(text, len);
399 MovTextContext *s = priv;
400 av_bprint_append_data(&s->buffer, text, len);
401 // If it's not utf-8, just use the byte length
402 s->text_pos += utf8_len ? utf8_len : len;
403 s->byte_count += len;
406 static void mov_text_new_line_cb(void *priv, int forced)
408 MovTextContext *s = priv;
409 av_bprint_append_data(&s->buffer, "\n", 1);
414 static const ASSCodesCallbacks mov_text_callbacks = {
415 .text = mov_text_text_cb,
416 .new_line = mov_text_new_line_cb,
417 .style = mov_text_style_cb,
418 .color = mov_text_color_cb,
419 .end = mov_text_end_cb,
422 static int mov_text_encode_frame(AVCodecContext *avctx, unsigned char *buf,
423 int bufsize, const AVSubtitle *sub)
425 MovTextContext *s = avctx->priv_data;
434 for (i = 0; i < sub->num_rects; i++) {
435 const char *ass = sub->rects[i]->ass;
437 if (sub->rects[i]->type != SUBTITLE_ASS) {
438 av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n");
439 return AVERROR(ENOSYS);
442 #if FF_API_ASS_TIMING
443 if (!strncmp(ass, "Dialogue: ", 10)) {
445 dialog = ff_ass_split_dialog(s->ass_ctx, ass, 0, &num);
446 for (; dialog && num--; dialog++) {
447 mov_text_dialog(s, dialog);
448 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
452 dialog = ff_ass_split_dialog2(s->ass_ctx, ass);
454 return AVERROR(ENOMEM);
455 mov_text_dialog(s, dialog);
456 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
457 ff_ass_free_dialog(&dialog);
458 #if FF_API_ASS_TIMING
462 for (j = 0; j < box_count; j++) {
463 box_types[j].encode(s, box_types[j].type);
467 AV_WB16(buf, s->byte_count);
470 if (!av_bprint_is_complete(&s->buffer)) {
471 length = AVERROR(ENOMEM);
475 if (!s->buffer.len) {
480 if (s->buffer.len > bufsize - 3) {
481 av_log(avctx, AV_LOG_ERROR, "Buffer too small for ASS event.\n");
482 length = AVERROR(EINVAL);
486 memcpy(buf, s->buffer.str, s->buffer.len);
487 length = s->buffer.len + 2;
490 av_bprint_clear(&s->buffer);
494 static int mov_text_encode_close(AVCodecContext *avctx)
496 MovTextContext *s = avctx->priv_data;
497 ff_ass_split_free(s->ass_ctx);
498 av_bprint_finalize(&s->buffer, NULL);
502 AVCodec ff_movtext_encoder = {
504 .long_name = NULL_IF_CONFIG_SMALL("3GPP Timed Text subtitle"),
505 .type = AVMEDIA_TYPE_SUBTITLE,
506 .id = AV_CODEC_ID_MOV_TEXT,
507 .priv_data_size = sizeof(MovTextContext),
508 .init = mov_text_encode_init,
509 .encode_sub = mov_text_encode_frame,
510 .close = mov_text_encode_close,