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 av_bprint_append_any(buf, data, size) av_bprint_append_data(buf, ((const char*)data), size)
60 AVCodecContext *avctx;
62 ASSSplitContext *ass_ctx;
64 StyleBox **style_attributes;
65 StyleBox *style_attributes_temp;
70 uint16_t style_entries;
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);
94 static void encode_styl(MovTextContext *s, uint32_t tsmb_type)
98 if (s->box_flags & STYL_BOX) {
99 tsmb_size = s->count * STYLE_RECORD_SIZE + SIZE_ADD;
100 tsmb_size = AV_RB32(&tsmb_size);
101 s->style_entries = AV_RB16(&s->count);
102 s->style_fontID = 0x00 | 0x01<<8;
103 s->style_fontsize = 0x12;
104 s->style_color = MKTAG(0xFF, 0xFF, 0xFF, 0xFF);
105 /*The above three attributes are hard coded for now
106 but will come from ASS style in the future*/
107 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
108 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
109 av_bprint_append_any(&s->buffer, &s->style_entries, 2);
110 for (j = 0; j < s->count; j++) {
111 av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_start, 2);
112 av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_end, 2);
113 av_bprint_append_any(&s->buffer, &s->style_fontID, 2);
114 av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_flag, 1);
115 av_bprint_append_any(&s->buffer, &s->style_fontsize, 1);
116 av_bprint_append_any(&s->buffer, &s->style_color, 4);
122 static void encode_hlit(MovTextContext *s, uint32_t tsmb_type)
125 if (s->box_flags & HLIT_BOX) {
127 tsmb_size = AV_RB32(&tsmb_size);
128 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
129 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
130 av_bprint_append_any(&s->buffer, &s->hlit.start, 2);
131 av_bprint_append_any(&s->buffer, &s->hlit.end, 2);
135 static void encode_hclr(MovTextContext *s, uint32_t tsmb_type)
138 if (s->box_flags & HCLR_BOX) {
140 tsmb_size = AV_RB32(&tsmb_size);
141 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
142 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
143 av_bprint_append_any(&s->buffer, &s->hclr.color, 4);
147 static const Box box_types[] = {
148 { MKTAG('s','t','y','l'), encode_styl },
149 { MKTAG('h','l','i','t'), encode_hlit },
150 { MKTAG('h','c','l','r'), encode_hclr },
153 const static size_t box_count = FF_ARRAY_ELEMS(box_types);
155 static av_cold int mov_text_encode_init(AVCodecContext *avctx)
158 * For now, we'll use a fixed default style. When we add styling
159 * support, this will be generated from the ASS style.
161 static const uint8_t text_sample_entry[] = {
162 0x00, 0x00, 0x00, 0x00, // uint32_t displayFlags
163 0x01, // int8_t horizontal-justification
164 0xFF, // int8_t vertical-justification
165 0x00, 0x00, 0x00, 0x00, // uint8_t background-color-rgba[4]
167 0x00, 0x00, // int16_t top
168 0x00, 0x00, // int16_t left
169 0x00, 0x00, // int16_t bottom
170 0x00, 0x00, // int16_t right
173 0x00, 0x00, // uint16_t startChar
174 0x00, 0x00, // uint16_t endChar
175 0x00, 0x01, // uint16_t font-ID
176 0x00, // uint8_t face-style-flags
177 0x12, // uint8_t font-size
178 0xFF, 0xFF, 0xFF, 0xFF, // uint8_t text-color-rgba[4]
181 0x00, 0x00, 0x00, 0x12, // uint32_t size
182 'f', 't', 'a', 'b', // uint8_t name[4]
183 0x00, 0x01, // uint16_t entry-count
185 0x00, 0x01, // uint16_t font-ID
186 0x05, // uint8_t font-name-length
187 'S', 'e', 'r', 'i', 'f',// uint8_t font[font-name-length]
192 MovTextContext *s = avctx->priv_data;
195 avctx->extradata_size = sizeof text_sample_entry;
196 avctx->extradata = av_mallocz(avctx->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
197 if (!avctx->extradata)
198 return AVERROR(ENOMEM);
200 av_bprint_init(&s->buffer, 0, AV_BPRINT_SIZE_UNLIMITED);
202 memcpy(avctx->extradata, text_sample_entry, avctx->extradata_size);
204 s->ass_ctx = ff_ass_split(avctx->subtitle_header);
205 return s->ass_ctx ? 0 : AVERROR_INVALIDDATA;
208 static void mov_text_style_cb(void *priv, const char style, int close)
210 MovTextContext *s = priv;
212 if (!(s->box_flags & STYL_BOX)) { //first style entry
214 s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
216 if (!s->style_attributes_temp) {
217 av_bprint_clear(&s->buffer);
218 s->box_flags &= ~STYL_BOX;
222 s->style_attributes_temp->style_flag = 0;
223 s->style_attributes_temp->style_start = AV_RB16(&s->text_pos);
225 if (s->style_attributes_temp->style_flag) { //break the style record here and start a new one
226 s->style_attributes_temp->style_end = AV_RB16(&s->text_pos);
227 av_dynarray_add(&s->style_attributes, &s->count, s->style_attributes_temp);
228 s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
229 if (!s->style_attributes_temp) {
231 av_bprint_clear(&s->buffer);
232 s->box_flags &= ~STYL_BOX;
236 s->style_attributes_temp->style_flag = s->style_attributes[s->count - 1]->style_flag;
237 s->style_attributes_temp->style_start = AV_RB16(&s->text_pos);
239 s->style_attributes_temp->style_flag = 0;
240 s->style_attributes_temp->style_start = AV_RB16(&s->text_pos);
245 s->style_attributes_temp->style_flag |= STYLE_FLAG_BOLD;
248 s->style_attributes_temp->style_flag |= STYLE_FLAG_ITALIC;
251 s->style_attributes_temp->style_flag |= STYLE_FLAG_UNDERLINE;
254 } else if (!s->style_attributes_temp) {
255 av_log(s->avctx, AV_LOG_WARNING, "Ignoring unmatched close tag\n");
258 s->style_attributes_temp->style_end = AV_RB16(&s->text_pos);
259 av_dynarray_add(&s->style_attributes, &s->count, s->style_attributes_temp);
261 s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
263 if (!s->style_attributes_temp) {
265 av_bprint_clear(&s->buffer);
266 s->box_flags &= ~STYL_BOX;
270 s->style_attributes_temp->style_flag = s->style_attributes[s->count - 1]->style_flag;
273 s->style_attributes_temp->style_flag &= ~STYLE_FLAG_BOLD;
276 s->style_attributes_temp->style_flag &= ~STYLE_FLAG_ITALIC;
279 s->style_attributes_temp->style_flag &= ~STYLE_FLAG_UNDERLINE;
282 if (s->style_attributes_temp->style_flag) { //start of new style record
283 s->style_attributes_temp->style_start = AV_RB16(&s->text_pos);
286 s->box_flags |= STYL_BOX;
289 static void mov_text_color_cb(void *priv, unsigned int color, unsigned int color_id)
291 MovTextContext *s = priv;
292 if (color_id == 2) { //secondary color changes
293 if (s->box_flags & HLIT_BOX) { //close tag
294 s->hlit.end = AV_RB16(&s->text_pos);
296 s->box_flags |= HCLR_BOX;
297 s->box_flags |= HLIT_BOX;
298 s->hlit.start = AV_RB16(&s->text_pos);
299 s->hclr.color = color | (0xFF << 24); //set alpha value to FF
302 /* If there are more than one secondary color changes in ASS, take start of
303 first section and end of last section. Movtext allows only one
304 highlight box per sample.
308 static uint16_t utf8_strlen(const char *text, int len)
310 uint16_t i = 0, ret = 0;
315 else if ((c & 0xE0) == 0xC0)
317 else if ((c & 0xF0) == 0xE0)
319 else if ((c & 0xF8) == 0xF0)
328 static void mov_text_text_cb(void *priv, const char *text, int len)
330 uint16_t utf8_len = utf8_strlen(text, len);
331 MovTextContext *s = priv;
332 av_bprint_append_data(&s->buffer, text, len);
333 // If it's not utf-8, just use the byte length
334 s->text_pos += utf8_len ? utf8_len : len;
335 s->byte_count += len;
338 static void mov_text_new_line_cb(void *priv, int forced)
340 MovTextContext *s = priv;
341 av_bprint_append_data(&s->buffer, "\n", 1);
346 static const ASSCodesCallbacks mov_text_callbacks = {
347 .text = mov_text_text_cb,
348 .new_line = mov_text_new_line_cb,
349 .style = mov_text_style_cb,
350 .color = mov_text_color_cb,
353 static int mov_text_encode_frame(AVCodecContext *avctx, unsigned char *buf,
354 int bufsize, const AVSubtitle *sub)
356 MovTextContext *s = avctx->priv_data;
365 s->style_entries = 0;
366 for (i = 0; i < sub->num_rects; i++) {
367 const char *ass = sub->rects[i]->ass;
369 if (sub->rects[i]->type != SUBTITLE_ASS) {
370 av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n");
371 return AVERROR(ENOSYS);
374 #if FF_API_ASS_TIMING
375 if (!strncmp(ass, "Dialogue: ", 10)) {
377 dialog = ff_ass_split_dialog(s->ass_ctx, ass, 0, &num);
378 for (; dialog && num--; dialog++) {
379 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
383 dialog = ff_ass_split_dialog2(s->ass_ctx, ass);
385 return AVERROR(ENOMEM);
386 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
387 ff_ass_free_dialog(&dialog);
388 #if FF_API_ASS_TIMING
392 for (j = 0; j < box_count; j++) {
393 box_types[j].encode(s, box_types[j].type);
397 AV_WB16(buf, s->byte_count);
400 if (!av_bprint_is_complete(&s->buffer)) {
401 length = AVERROR(ENOMEM);
405 if (!s->buffer.len) {
410 if (s->buffer.len > bufsize - 3) {
411 av_log(avctx, AV_LOG_ERROR, "Buffer too small for ASS event.\n");
412 length = AVERROR(EINVAL);
416 memcpy(buf, s->buffer.str, s->buffer.len);
417 length = s->buffer.len + 2;
420 av_bprint_clear(&s->buffer);
424 static int mov_text_encode_close(AVCodecContext *avctx)
426 MovTextContext *s = avctx->priv_data;
427 ff_ass_split_free(s->ass_ctx);
428 av_bprint_finalize(&s->buffer, NULL);
432 AVCodec ff_movtext_encoder = {
434 .long_name = NULL_IF_CONFIG_SMALL("3GPP Timed Text subtitle"),
435 .type = AVMEDIA_TYPE_SUBTITLE,
436 .id = AV_CODEC_ID_MOV_TEXT,
437 .priv_data_size = sizeof(MovTextContext),
438 .init = mov_text_encode_init,
439 .encode_sub = mov_text_encode_frame,
440 .close = mov_text_encode_close,