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_entries;
72 uint16_t style_fontID;
73 uint8_t style_fontsize;
81 void (*encode)(MovTextContext *s, uint32_t tsmb_type);
84 static void mov_text_cleanup(MovTextContext *s)
87 if (s->box_flags & STYL_BOX) {
88 for (j = 0; j < s->count; j++) {
89 av_freep(&s->style_attributes[j]);
91 av_freep(&s->style_attributes);
95 static void encode_styl(MovTextContext *s, uint32_t tsmb_type)
99 if (s->box_flags & STYL_BOX) {
100 tsmb_size = s->count * STYLE_RECORD_SIZE + SIZE_ADD;
101 tsmb_size = AV_RB32(&tsmb_size);
102 s->style_entries = AV_RB16(&s->count);
103 s->style_fontID = 0x00 | 0x01<<8;
104 s->style_fontsize = 0x12;
105 s->style_color = MKTAG(0xFF, 0xFF, 0xFF, 0xFF);
106 /*The above three attributes are hard coded for now
107 but will come from ASS style in the future*/
108 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
109 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
110 av_bprint_append_any(&s->buffer, &s->style_entries, 2);
111 for (j = 0; j < s->count; j++) {
112 av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_start, 2);
113 av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_end, 2);
114 av_bprint_append_any(&s->buffer, &s->style_fontID, 2);
115 av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_flag, 1);
116 av_bprint_append_any(&s->buffer, &s->style_fontsize, 1);
117 av_bprint_append_any(&s->buffer, &s->style_color, 4);
123 static void encode_hlit(MovTextContext *s, uint32_t tsmb_type)
126 if (s->box_flags & HLIT_BOX) {
128 tsmb_size = AV_RB32(&tsmb_size);
129 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
130 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
131 av_bprint_append_any(&s->buffer, &s->hlit.start, 2);
132 av_bprint_append_any(&s->buffer, &s->hlit.end, 2);
136 static void encode_hclr(MovTextContext *s, uint32_t tsmb_type)
138 uint32_t tsmb_size, color;
139 if (s->box_flags & HCLR_BOX) {
141 tsmb_size = AV_RB32(&tsmb_size);
142 color = AV_RB32(&s->hclr.color);
143 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
144 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
145 av_bprint_append_any(&s->buffer, &color, 4);
149 static const Box box_types[] = {
150 { MKTAG('s','t','y','l'), encode_styl },
151 { MKTAG('h','l','i','t'), encode_hlit },
152 { MKTAG('h','c','l','r'), encode_hclr },
155 const static size_t box_count = FF_ARRAY_ELEMS(box_types);
157 static av_cold int mov_text_encode_init(AVCodecContext *avctx)
160 * For now, we'll use a fixed default style. When we add styling
161 * support, this will be generated from the ASS style.
163 static const uint8_t text_sample_entry[] = {
164 0x00, 0x00, 0x00, 0x00, // uint32_t displayFlags
165 0x01, // int8_t horizontal-justification
166 0xFF, // int8_t vertical-justification
167 0x00, 0x00, 0x00, 0x00, // uint8_t background-color-rgba[4]
169 0x00, 0x00, // int16_t top
170 0x00, 0x00, // int16_t left
171 0x00, 0x00, // int16_t bottom
172 0x00, 0x00, // int16_t right
175 0x00, 0x00, // uint16_t startChar
176 0x00, 0x00, // uint16_t endChar
177 0x00, 0x01, // uint16_t font-ID
178 0x00, // uint8_t face-style-flags
179 0x12, // uint8_t font-size
180 0xFF, 0xFF, 0xFF, 0xFF, // uint8_t text-color-rgba[4]
183 0x00, 0x00, 0x00, 0x12, // uint32_t size
184 'f', 't', 'a', 'b', // uint8_t name[4]
185 0x00, 0x01, // uint16_t entry-count
187 0x00, 0x01, // uint16_t font-ID
188 0x05, // uint8_t font-name-length
189 'S', 'e', 'r', 'i', 'f',// uint8_t font[font-name-length]
194 MovTextContext *s = avctx->priv_data;
197 avctx->extradata_size = sizeof text_sample_entry;
198 avctx->extradata = av_mallocz(avctx->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
199 if (!avctx->extradata)
200 return AVERROR(ENOMEM);
202 av_bprint_init(&s->buffer, 0, AV_BPRINT_SIZE_UNLIMITED);
204 memcpy(avctx->extradata, text_sample_entry, avctx->extradata_size);
206 s->ass_ctx = ff_ass_split(avctx->subtitle_header);
207 return s->ass_ctx ? 0 : AVERROR_INVALIDDATA;
210 static void mov_text_style_cb(void *priv, const char style, int close)
212 MovTextContext *s = priv;
214 if (!(s->box_flags & STYL_BOX)) { //first style entry
216 s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
218 if (!s->style_attributes_temp) {
219 av_bprint_clear(&s->buffer);
220 s->box_flags &= ~STYL_BOX;
224 s->style_attributes_temp->style_flag = 0;
225 s->style_attributes_temp->style_start = AV_RB16(&s->text_pos);
227 if (s->style_attributes_temp->style_flag) { //break the style record here and start a new one
228 s->style_attributes_temp->style_end = AV_RB16(&s->text_pos);
229 av_dynarray_add(&s->style_attributes, &s->count, s->style_attributes_temp);
230 s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
231 if (!s->style_attributes_temp) {
233 av_bprint_clear(&s->buffer);
234 s->box_flags &= ~STYL_BOX;
238 s->style_attributes_temp->style_flag = s->style_attributes[s->count - 1]->style_flag;
239 s->style_attributes_temp->style_start = AV_RB16(&s->text_pos);
241 s->style_attributes_temp->style_flag = 0;
242 s->style_attributes_temp->style_start = AV_RB16(&s->text_pos);
247 s->style_attributes_temp->style_flag |= STYLE_FLAG_BOLD;
250 s->style_attributes_temp->style_flag |= STYLE_FLAG_ITALIC;
253 s->style_attributes_temp->style_flag |= STYLE_FLAG_UNDERLINE;
256 } else if (!s->style_attributes_temp) {
257 av_log(s->avctx, AV_LOG_WARNING, "Ignoring unmatched close tag\n");
260 s->style_attributes_temp->style_end = AV_RB16(&s->text_pos);
261 av_dynarray_add(&s->style_attributes, &s->count, s->style_attributes_temp);
263 s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
265 if (!s->style_attributes_temp) {
267 av_bprint_clear(&s->buffer);
268 s->box_flags &= ~STYL_BOX;
272 s->style_attributes_temp->style_flag = s->style_attributes[s->count - 1]->style_flag;
275 s->style_attributes_temp->style_flag &= ~STYLE_FLAG_BOLD;
278 s->style_attributes_temp->style_flag &= ~STYLE_FLAG_ITALIC;
281 s->style_attributes_temp->style_flag &= ~STYLE_FLAG_UNDERLINE;
284 if (s->style_attributes_temp->style_flag) { //start of new style record
285 s->style_attributes_temp->style_start = AV_RB16(&s->text_pos);
288 s->box_flags |= STYL_BOX;
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 = AV_RB16(&s->text_pos);
300 s->box_flags |= HCLR_BOX;
301 s->box_flags |= HLIT_BOX;
302 s->hlit.start = AV_RB16(&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 uint16_t utf8_strlen(const char *text, int len)
314 uint16_t i = 0, ret = 0;
319 else if ((c & 0xE0) == 0xC0)
321 else if ((c & 0xF0) == 0xE0)
323 else if ((c & 0xF8) == 0xF0)
332 static void mov_text_text_cb(void *priv, const char *text, int len)
334 uint16_t utf8_len = utf8_strlen(text, len);
335 MovTextContext *s = priv;
336 av_bprint_append_data(&s->buffer, text, len);
337 // If it's not utf-8, just use the byte length
338 s->text_pos += utf8_len ? utf8_len : len;
339 s->byte_count += len;
342 static void mov_text_new_line_cb(void *priv, int forced)
344 MovTextContext *s = priv;
345 av_bprint_append_data(&s->buffer, "\n", 1);
350 static const ASSCodesCallbacks mov_text_callbacks = {
351 .text = mov_text_text_cb,
352 .new_line = mov_text_new_line_cb,
353 .style = mov_text_style_cb,
354 .color = mov_text_color_cb,
357 static int mov_text_encode_frame(AVCodecContext *avctx, unsigned char *buf,
358 int bufsize, const AVSubtitle *sub)
360 MovTextContext *s = avctx->priv_data;
369 s->style_entries = 0;
370 for (i = 0; i < sub->num_rects; i++) {
371 const char *ass = sub->rects[i]->ass;
373 if (sub->rects[i]->type != SUBTITLE_ASS) {
374 av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n");
375 return AVERROR(ENOSYS);
378 #if FF_API_ASS_TIMING
379 if (!strncmp(ass, "Dialogue: ", 10)) {
381 dialog = ff_ass_split_dialog(s->ass_ctx, ass, 0, &num);
382 for (; dialog && num--; dialog++) {
383 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
387 dialog = ff_ass_split_dialog2(s->ass_ctx, ass);
389 return AVERROR(ENOMEM);
390 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
391 ff_ass_free_dialog(&dialog);
392 #if FF_API_ASS_TIMING
396 for (j = 0; j < box_count; j++) {
397 box_types[j].encode(s, box_types[j].type);
401 AV_WB16(buf, s->byte_count);
404 if (!av_bprint_is_complete(&s->buffer)) {
405 length = AVERROR(ENOMEM);
409 if (!s->buffer.len) {
414 if (s->buffer.len > bufsize - 3) {
415 av_log(avctx, AV_LOG_ERROR, "Buffer too small for ASS event.\n");
416 length = AVERROR(EINVAL);
420 memcpy(buf, s->buffer.str, s->buffer.len);
421 length = s->buffer.len + 2;
424 av_bprint_clear(&s->buffer);
428 static int mov_text_encode_close(AVCodecContext *avctx)
430 MovTextContext *s = avctx->priv_data;
431 ff_ass_split_free(s->ass_ctx);
432 av_bprint_finalize(&s->buffer, NULL);
436 AVCodec ff_movtext_encoder = {
438 .long_name = NULL_IF_CONFIG_SMALL("3GPP Timed Text subtitle"),
439 .type = AVMEDIA_TYPE_SUBTITLE,
440 .id = AV_CODEC_ID_MOV_TEXT,
441 .priv_data_size = sizeof(MovTextContext),
442 .init = mov_text_encode_init,
443 .encode_sub = mov_text_encode_frame,
444 .close = mov_text_encode_close,