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 BGR_TO_RGB(c) (((c) & 0xff) << 16 | ((c) & 0xff00) | (((c) >> 16) & 0xff))
43 #define av_bprint_append_any(buf, data, size) av_bprint_append_data(buf, ((const char*)data), size)
61 AVCodecContext *avctx;
63 ASSSplitContext *ass_ctx;
65 StyleBox **style_attributes;
66 StyleBox *style_attributes_temp;
71 uint16_t style_fontID;
72 uint8_t style_fontsize;
80 void (*encode)(MovTextContext *s, uint32_t tsmb_type);
83 static void mov_text_cleanup(MovTextContext *s)
86 if (s->box_flags & STYL_BOX) {
87 for (j = 0; j < s->count; j++) {
88 av_freep(&s->style_attributes[j]);
90 av_freep(&s->style_attributes);
92 if (s->style_attributes_temp) {
93 s->style_attributes_temp->style_flag = 0;
94 s->style_attributes_temp->style_start = 0;
98 static void encode_styl(MovTextContext *s, uint32_t tsmb_type)
102 uint16_t style_entries;
103 if ((s->box_flags & STYL_BOX) && s->count) {
104 tsmb_size = s->count * STYLE_RECORD_SIZE + SIZE_ADD;
105 tsmb_size = AV_RB32(&tsmb_size);
106 style_entries = AV_RB16(&s->count);
107 s->style_fontID = 0x00 | 0x01<<8;
108 s->style_fontsize = 0x12;
109 s->style_color = MKTAG(0xFF, 0xFF, 0xFF, 0xFF);
110 /*The above three attributes are hard coded for now
111 but will come from ASS style in the future*/
112 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
113 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
114 av_bprint_append_any(&s->buffer, &style_entries, 2);
115 for (j = 0; j < s->count; j++) {
116 uint16_t style_start, style_end;
118 style_start = AV_RB16(&s->style_attributes[j]->style_start);
119 style_end = AV_RB16(&s->style_attributes[j]->style_end);
120 av_bprint_append_any(&s->buffer, &style_start, 2);
121 av_bprint_append_any(&s->buffer, &style_end, 2);
122 av_bprint_append_any(&s->buffer, &s->style_fontID, 2);
123 av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_flag, 1);
124 av_bprint_append_any(&s->buffer, &s->style_fontsize, 1);
125 av_bprint_append_any(&s->buffer, &s->style_color, 4);
131 static void encode_hlit(MovTextContext *s, uint32_t tsmb_type)
135 if (s->box_flags & HLIT_BOX) {
137 tsmb_size = AV_RB32(&tsmb_size);
138 start = AV_RB16(&s->hlit.start);
139 end = AV_RB16(&s->hlit.end);
140 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
141 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
142 av_bprint_append_any(&s->buffer, &start, 2);
143 av_bprint_append_any(&s->buffer, &end, 2);
147 static void encode_hclr(MovTextContext *s, uint32_t tsmb_type)
149 uint32_t tsmb_size, color;
150 if (s->box_flags & HCLR_BOX) {
152 tsmb_size = AV_RB32(&tsmb_size);
153 color = AV_RB32(&s->hclr.color);
154 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
155 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
156 av_bprint_append_any(&s->buffer, &color, 4);
160 static const Box box_types[] = {
161 { MKTAG('s','t','y','l'), encode_styl },
162 { MKTAG('h','l','i','t'), encode_hlit },
163 { MKTAG('h','c','l','r'), encode_hclr },
166 const static size_t box_count = FF_ARRAY_ELEMS(box_types);
168 static av_cold int mov_text_encode_init(AVCodecContext *avctx)
171 * For now, we'll use a fixed default style. When we add styling
172 * support, this will be generated from the ASS style.
174 static const uint8_t text_sample_entry[] = {
175 0x00, 0x00, 0x00, 0x00, // uint32_t displayFlags
176 0x01, // int8_t horizontal-justification
177 0xFF, // int8_t vertical-justification
178 0x00, 0x00, 0x00, 0x00, // uint8_t background-color-rgba[4]
180 0x00, 0x00, // int16_t top
181 0x00, 0x00, // int16_t left
182 0x00, 0x00, // int16_t bottom
183 0x00, 0x00, // int16_t right
186 0x00, 0x00, // uint16_t startChar
187 0x00, 0x00, // uint16_t endChar
188 0x00, 0x01, // uint16_t font-ID
189 0x00, // uint8_t face-style-flags
190 0x12, // uint8_t font-size
191 0xFF, 0xFF, 0xFF, 0xFF, // uint8_t text-color-rgba[4]
194 0x00, 0x00, 0x00, 0x12, // uint32_t size
195 'f', 't', 'a', 'b', // uint8_t name[4]
196 0x00, 0x01, // uint16_t entry-count
198 0x00, 0x01, // uint16_t font-ID
199 0x05, // uint8_t font-name-length
200 'S', 'e', 'r', 'i', 'f',// uint8_t font[font-name-length]
205 MovTextContext *s = avctx->priv_data;
208 s->style_attributes_temp = av_mallocz(sizeof(*s->style_attributes_temp));
209 if (!s->style_attributes_temp) {
210 return AVERROR(ENOMEM);
213 avctx->extradata_size = sizeof text_sample_entry;
214 avctx->extradata = av_mallocz(avctx->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
215 if (!avctx->extradata)
216 return AVERROR(ENOMEM);
218 av_bprint_init(&s->buffer, 0, AV_BPRINT_SIZE_UNLIMITED);
220 memcpy(avctx->extradata, text_sample_entry, avctx->extradata_size);
222 s->ass_ctx = ff_ass_split(avctx->subtitle_header);
223 return s->ass_ctx ? 0 : AVERROR_INVALIDDATA;
226 // Start a new style box if needed
227 static int mov_text_style_start(MovTextContext *s)
229 // there's an existing style entry
230 if (s->style_attributes_temp->style_start == s->text_pos)
231 // Still at same text pos, use same entry
233 if (s->style_attributes_temp->style_flag) {
234 // last style != defaults, end the style entry and start a new one
235 s->box_flags |= STYL_BOX;
236 s->style_attributes_temp->style_end = s->text_pos;
237 av_dynarray_add(&s->style_attributes, &s->count, s->style_attributes_temp);
238 s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
239 if (!s->style_attributes_temp) {
241 av_bprint_clear(&s->buffer);
242 s->box_flags &= ~STYL_BOX;
246 s->style_attributes_temp->style_flag = s->style_attributes[s->count - 1]->style_flag;
247 s->style_attributes_temp->style_start = s->text_pos;
248 } else { // style entry matches defaults, drop entry
249 s->style_attributes_temp->style_flag = 0;
250 s->style_attributes_temp->style_start = s->text_pos;
255 static uint8_t mov_text_style_to_flag(const char style)
257 uint8_t style_flag = 0;
261 style_flag = STYLE_FLAG_BOLD;
264 style_flag = STYLE_FLAG_ITALIC;
267 style_flag = STYLE_FLAG_UNDERLINE;
273 static void mov_text_style_cb(void *priv, const char style, int close)
275 MovTextContext *s = priv;
276 uint8_t style_flag = mov_text_style_to_flag(style);
278 if (!s->style_attributes_temp ||
279 !!(s->style_attributes_temp->style_flag & style_flag) != close) {
280 // setting flag that is already set
283 if (mov_text_style_start(s)) {
285 s->style_attributes_temp->style_flag |= style_flag;
287 s->style_attributes_temp->style_flag &= ~style_flag;
291 static void mov_text_color_cb(void *priv, unsigned int color, unsigned int color_id)
293 MovTextContext *s = priv;
295 color = BGR_TO_RGB(color) << 8;
296 if (color_id == 2) { //secondary color changes
297 if (s->box_flags & HLIT_BOX) { //close tag
298 s->hlit.end = s->text_pos;
300 s->box_flags |= HCLR_BOX;
301 s->box_flags |= HLIT_BOX;
302 s->hlit.start = s->text_pos;
303 s->hclr.color = color | 0xFF; //set alpha value to FF
306 /* If there are more than one secondary color changes in ASS, take start of
307 first section and end of last section. Movtext allows only one
308 highlight box per sample.
312 static void mov_text_end_cb(void *priv)
314 // End of text, close any open style record
315 mov_text_style_start((MovTextContext*)priv);
318 static uint16_t utf8_strlen(const char *text, int len)
320 uint16_t i = 0, ret = 0;
325 else if ((c & 0xE0) == 0xC0)
327 else if ((c & 0xF0) == 0xE0)
329 else if ((c & 0xF8) == 0xF0)
338 static void mov_text_text_cb(void *priv, const char *text, int len)
340 uint16_t utf8_len = utf8_strlen(text, len);
341 MovTextContext *s = priv;
342 av_bprint_append_data(&s->buffer, text, len);
343 // If it's not utf-8, just use the byte length
344 s->text_pos += utf8_len ? utf8_len : len;
345 s->byte_count += len;
348 static void mov_text_new_line_cb(void *priv, int forced)
350 MovTextContext *s = priv;
351 av_bprint_append_data(&s->buffer, "\n", 1);
356 static const ASSCodesCallbacks mov_text_callbacks = {
357 .text = mov_text_text_cb,
358 .new_line = mov_text_new_line_cb,
359 .style = mov_text_style_cb,
360 .color = mov_text_color_cb,
361 .end = mov_text_end_cb,
364 static int mov_text_encode_frame(AVCodecContext *avctx, unsigned char *buf,
365 int bufsize, const AVSubtitle *sub)
367 MovTextContext *s = avctx->priv_data;
376 for (i = 0; i < sub->num_rects; i++) {
377 const char *ass = sub->rects[i]->ass;
379 if (sub->rects[i]->type != SUBTITLE_ASS) {
380 av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n");
381 return AVERROR(ENOSYS);
384 #if FF_API_ASS_TIMING
385 if (!strncmp(ass, "Dialogue: ", 10)) {
387 dialog = ff_ass_split_dialog(s->ass_ctx, ass, 0, &num);
388 for (; dialog && num--; dialog++) {
389 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
393 dialog = ff_ass_split_dialog2(s->ass_ctx, ass);
395 return AVERROR(ENOMEM);
396 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
397 ff_ass_free_dialog(&dialog);
398 #if FF_API_ASS_TIMING
402 for (j = 0; j < box_count; j++) {
403 box_types[j].encode(s, box_types[j].type);
407 AV_WB16(buf, s->byte_count);
410 if (!av_bprint_is_complete(&s->buffer)) {
411 length = AVERROR(ENOMEM);
415 if (!s->buffer.len) {
420 if (s->buffer.len > bufsize - 3) {
421 av_log(avctx, AV_LOG_ERROR, "Buffer too small for ASS event.\n");
422 length = AVERROR(EINVAL);
426 memcpy(buf, s->buffer.str, s->buffer.len);
427 length = s->buffer.len + 2;
430 av_bprint_clear(&s->buffer);
434 static int mov_text_encode_close(AVCodecContext *avctx)
436 MovTextContext *s = avctx->priv_data;
437 ff_ass_split_free(s->ass_ctx);
438 av_bprint_finalize(&s->buffer, NULL);
442 AVCodec ff_movtext_encoder = {
444 .long_name = NULL_IF_CONFIG_SMALL("3GPP Timed Text subtitle"),
445 .type = AVMEDIA_TYPE_SUBTITLE,
446 .id = AV_CODEC_ID_MOV_TEXT,
447 .priv_data_size = sizeof(MovTextContext),
448 .init = mov_text_encode_init,
449 .encode_sub = mov_text_encode_frame,
450 .close = mov_text_encode_close,