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;
79 void (*encode)(MovTextContext *s, uint32_t tsmb_type);
82 static void mov_text_cleanup(MovTextContext *s)
85 if (s->box_flags & STYL_BOX) {
86 for (j = 0; j < s->count; j++) {
87 av_freep(&s->style_attributes[j]);
89 av_freep(&s->style_attributes);
93 static void encode_styl(MovTextContext *s, uint32_t tsmb_type)
97 if (s->box_flags & STYL_BOX) {
98 tsmb_size = s->count * STYLE_RECORD_SIZE + SIZE_ADD;
99 tsmb_size = AV_RB32(&tsmb_size);
100 s->style_entries = AV_RB16(&s->count);
101 s->style_fontID = 0x00 | 0x01<<8;
102 s->style_fontsize = 0x12;
103 s->style_color = MKTAG(0xFF, 0xFF, 0xFF, 0xFF);
104 /*The above three attributes are hard coded for now
105 but will come from ASS style in the future*/
106 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
107 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
108 av_bprint_append_any(&s->buffer, &s->style_entries, 2);
109 for (j = 0; j < s->count; j++) {
110 av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_start, 2);
111 av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_end, 2);
112 av_bprint_append_any(&s->buffer, &s->style_fontID, 2);
113 av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_flag, 1);
114 av_bprint_append_any(&s->buffer, &s->style_fontsize, 1);
115 av_bprint_append_any(&s->buffer, &s->style_color, 4);
121 static void encode_hlit(MovTextContext *s, uint32_t tsmb_type)
124 if (s->box_flags & HLIT_BOX) {
126 tsmb_size = AV_RB32(&tsmb_size);
127 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
128 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
129 av_bprint_append_any(&s->buffer, &s->hlit.start, 2);
130 av_bprint_append_any(&s->buffer, &s->hlit.end, 2);
134 static void encode_hclr(MovTextContext *s, uint32_t tsmb_type)
137 if (s->box_flags & HCLR_BOX) {
139 tsmb_size = AV_RB32(&tsmb_size);
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, &s->hclr.color, 4);
146 static const Box box_types[] = {
147 { MKTAG('s','t','y','l'), encode_styl },
148 { MKTAG('h','l','i','t'), encode_hlit },
149 { MKTAG('h','c','l','r'), encode_hclr },
152 const static size_t box_count = FF_ARRAY_ELEMS(box_types);
154 static av_cold int mov_text_encode_init(AVCodecContext *avctx)
157 * For now, we'll use a fixed default style. When we add styling
158 * support, this will be generated from the ASS style.
160 static const uint8_t text_sample_entry[] = {
161 0x00, 0x00, 0x00, 0x00, // uint32_t displayFlags
162 0x01, // int8_t horizontal-justification
163 0xFF, // int8_t vertical-justification
164 0x00, 0x00, 0x00, 0x00, // uint8_t background-color-rgba[4]
166 0x00, 0x00, // int16_t top
167 0x00, 0x00, // int16_t left
168 0x00, 0x00, // int16_t bottom
169 0x00, 0x00, // int16_t right
172 0x00, 0x00, // uint16_t startChar
173 0x00, 0x00, // uint16_t endChar
174 0x00, 0x01, // uint16_t font-ID
175 0x00, // uint8_t face-style-flags
176 0x12, // uint8_t font-size
177 0xFF, 0xFF, 0xFF, 0xFF, // uint8_t text-color-rgba[4]
180 0x00, 0x00, 0x00, 0x12, // uint32_t size
181 'f', 't', 'a', 'b', // uint8_t name[4]
182 0x00, 0x01, // uint16_t entry-count
184 0x00, 0x01, // uint16_t font-ID
185 0x05, // uint8_t font-name-length
186 'S', 'e', 'r', 'i', 'f',// uint8_t font[font-name-length]
191 MovTextContext *s = avctx->priv_data;
194 avctx->extradata_size = sizeof text_sample_entry;
195 avctx->extradata = av_mallocz(avctx->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
196 if (!avctx->extradata)
197 return AVERROR(ENOMEM);
199 av_bprint_init(&s->buffer, 0, AV_BPRINT_SIZE_UNLIMITED);
201 memcpy(avctx->extradata, text_sample_entry, avctx->extradata_size);
203 s->ass_ctx = ff_ass_split(avctx->subtitle_header);
204 return s->ass_ctx ? 0 : AVERROR_INVALIDDATA;
207 static void mov_text_style_cb(void *priv, const char style, int close)
209 MovTextContext *s = priv;
211 if (!(s->box_flags & STYL_BOX)) { //first style entry
213 s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
215 if (!s->style_attributes_temp) {
216 av_bprint_clear(&s->buffer);
217 s->box_flags &= ~STYL_BOX;
221 s->style_attributes_temp->style_flag = 0;
222 s->style_attributes_temp->style_start = AV_RB16(&s->text_pos);
224 if (s->style_attributes_temp->style_flag) { //break the style record here and start a new one
225 s->style_attributes_temp->style_end = AV_RB16(&s->text_pos);
226 av_dynarray_add(&s->style_attributes, &s->count, s->style_attributes_temp);
227 s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
228 if (!s->style_attributes_temp) {
230 av_bprint_clear(&s->buffer);
231 s->box_flags &= ~STYL_BOX;
235 s->style_attributes_temp->style_flag = s->style_attributes[s->count - 1]->style_flag;
236 s->style_attributes_temp->style_start = AV_RB16(&s->text_pos);
238 s->style_attributes_temp->style_flag = 0;
239 s->style_attributes_temp->style_start = AV_RB16(&s->text_pos);
244 s->style_attributes_temp->style_flag |= STYLE_FLAG_BOLD;
247 s->style_attributes_temp->style_flag |= STYLE_FLAG_ITALIC;
250 s->style_attributes_temp->style_flag |= STYLE_FLAG_UNDERLINE;
253 } else if (!s->style_attributes_temp) {
254 av_log(s->avctx, AV_LOG_WARNING, "Ignoring unmatched close tag\n");
257 s->style_attributes_temp->style_end = AV_RB16(&s->text_pos);
258 av_dynarray_add(&s->style_attributes, &s->count, s->style_attributes_temp);
260 s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
262 if (!s->style_attributes_temp) {
264 av_bprint_clear(&s->buffer);
265 s->box_flags &= ~STYL_BOX;
269 s->style_attributes_temp->style_flag = s->style_attributes[s->count - 1]->style_flag;
272 s->style_attributes_temp->style_flag &= ~STYLE_FLAG_BOLD;
275 s->style_attributes_temp->style_flag &= ~STYLE_FLAG_ITALIC;
278 s->style_attributes_temp->style_flag &= ~STYLE_FLAG_UNDERLINE;
281 if (s->style_attributes_temp->style_flag) { //start of new style record
282 s->style_attributes_temp->style_start = AV_RB16(&s->text_pos);
285 s->box_flags |= STYL_BOX;
288 static void mov_text_color_cb(void *priv, unsigned int color, unsigned int color_id)
290 MovTextContext *s = priv;
291 if (color_id == 2) { //secondary color changes
292 if (s->box_flags & HLIT_BOX) { //close tag
293 s->hlit.end = AV_RB16(&s->text_pos);
295 s->box_flags |= HCLR_BOX;
296 s->box_flags |= HLIT_BOX;
297 s->hlit.start = AV_RB16(&s->text_pos);
298 s->hclr.color = color | (0xFF << 24); //set alpha value to FF
301 /* If there are more than one secondary color changes in ASS, take start of
302 first section and end of last section. Movtext allows only one
303 highlight box per sample.
307 static void mov_text_text_cb(void *priv, const char *text, int len)
309 MovTextContext *s = priv;
310 av_bprint_append_data(&s->buffer, text, len);
314 static void mov_text_new_line_cb(void *priv, int forced)
316 MovTextContext *s = priv;
317 av_bprint_append_data(&s->buffer, "\n", 1);
321 static const ASSCodesCallbacks mov_text_callbacks = {
322 .text = mov_text_text_cb,
323 .new_line = mov_text_new_line_cb,
324 .style = mov_text_style_cb,
325 .color = mov_text_color_cb,
328 static int mov_text_encode_frame(AVCodecContext *avctx, unsigned char *buf,
329 int bufsize, const AVSubtitle *sub)
331 MovTextContext *s = avctx->priv_data;
339 s->style_entries = 0;
340 for (i = 0; i < sub->num_rects; i++) {
341 const char *ass = sub->rects[i]->ass;
343 if (sub->rects[i]->type != SUBTITLE_ASS) {
344 av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n");
345 return AVERROR(ENOSYS);
348 #if FF_API_ASS_TIMING
349 if (!strncmp(ass, "Dialogue: ", 10)) {
351 dialog = ff_ass_split_dialog(s->ass_ctx, ass, 0, &num);
352 for (; dialog && num--; dialog++) {
353 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
357 dialog = ff_ass_split_dialog2(s->ass_ctx, ass);
359 return AVERROR(ENOMEM);
360 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
361 ff_ass_free_dialog(&dialog);
362 #if FF_API_ASS_TIMING
366 for (j = 0; j < box_count; j++) {
367 box_types[j].encode(s, box_types[j].type);
371 AV_WB16(buf, s->text_pos);
374 if (!av_bprint_is_complete(&s->buffer)) {
375 length = AVERROR(ENOMEM);
379 if (!s->buffer.len) {
384 if (s->buffer.len > bufsize - 3) {
385 av_log(avctx, AV_LOG_ERROR, "Buffer too small for ASS event.\n");
386 length = AVERROR(EINVAL);
390 memcpy(buf, s->buffer.str, s->buffer.len);
391 length = s->buffer.len + 2;
394 av_bprint_clear(&s->buffer);
398 static int mov_text_encode_close(AVCodecContext *avctx)
400 MovTextContext *s = avctx->priv_data;
401 ff_ass_split_free(s->ass_ctx);
402 av_bprint_finalize(&s->buffer, NULL);
406 AVCodec ff_movtext_encoder = {
408 .long_name = NULL_IF_CONFIG_SMALL("3GPP Timed Text subtitle"),
409 .type = AVMEDIA_TYPE_SUBTITLE,
410 .id = AV_CODEC_ID_MOV_TEXT,
411 .priv_data_size = sizeof(MovTextContext),
412 .init = mov_text_encode_init,
413 .encode_sub = mov_text_encode_frame,
414 .close = mov_text_encode_close,