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 ASSSplitContext *ass_ctx;
62 StyleBox **style_attributes;
63 StyleBox *style_attributes_temp;
68 uint16_t style_entries;
69 uint16_t style_fontID;
70 uint8_t style_fontsize;
77 void (*encode)(MovTextContext *s, uint32_t tsmb_type);
80 static void mov_text_cleanup(MovTextContext *s)
83 if (s->box_flags & STYL_BOX) {
84 for (j = 0; j < s->count; j++) {
85 av_freep(&s->style_attributes[j]);
87 av_freep(&s->style_attributes);
91 static void encode_styl(MovTextContext *s, uint32_t tsmb_type)
95 if (s->box_flags & STYL_BOX) {
96 tsmb_size = s->count * STYLE_RECORD_SIZE + SIZE_ADD;
97 tsmb_size = AV_RB32(&tsmb_size);
98 s->style_entries = AV_RB16(&s->count);
99 s->style_fontID = 0x00 | 0x01<<8;
100 s->style_fontsize = 0x12;
101 s->style_color = MKTAG(0xFF, 0xFF, 0xFF, 0xFF);
102 /*The above three attributes are hard coded for now
103 but will come from ASS style in the future*/
104 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
105 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
106 av_bprint_append_any(&s->buffer, &s->style_entries, 2);
107 for (j = 0; j < s->count; j++) {
108 av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_start, 2);
109 av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_end, 2);
110 av_bprint_append_any(&s->buffer, &s->style_fontID, 2);
111 av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_flag, 1);
112 av_bprint_append_any(&s->buffer, &s->style_fontsize, 1);
113 av_bprint_append_any(&s->buffer, &s->style_color, 4);
119 static void encode_hlit(MovTextContext *s, uint32_t tsmb_type)
122 if (s->box_flags & HLIT_BOX) {
124 tsmb_size = AV_RB32(&tsmb_size);
125 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
126 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
127 av_bprint_append_any(&s->buffer, &s->hlit.start, 2);
128 av_bprint_append_any(&s->buffer, &s->hlit.end, 2);
132 static void encode_hclr(MovTextContext *s, uint32_t tsmb_type)
135 if (s->box_flags & HCLR_BOX) {
137 tsmb_size = AV_RB32(&tsmb_size);
138 av_bprint_append_any(&s->buffer, &tsmb_size, 4);
139 av_bprint_append_any(&s->buffer, &tsmb_type, 4);
140 av_bprint_append_any(&s->buffer, &s->hclr.color, 4);
144 static const Box box_types[] = {
145 { MKTAG('s','t','y','l'), encode_styl },
146 { MKTAG('h','l','i','t'), encode_hlit },
147 { MKTAG('h','c','l','r'), encode_hclr },
150 const static size_t box_count = FF_ARRAY_ELEMS(box_types);
152 static av_cold int mov_text_encode_init(AVCodecContext *avctx)
155 * For now, we'll use a fixed default style. When we add styling
156 * support, this will be generated from the ASS style.
158 static const uint8_t text_sample_entry[] = {
159 0x00, 0x00, 0x00, 0x00, // uint32_t displayFlags
160 0x01, // int8_t horizontal-justification
161 0xFF, // int8_t vertical-justification
162 0x00, 0x00, 0x00, 0x00, // uint8_t background-color-rgba[4]
164 0x00, 0x00, // int16_t top
165 0x00, 0x00, // int16_t left
166 0x00, 0x00, // int16_t bottom
167 0x00, 0x00, // int16_t right
170 0x00, 0x00, // uint16_t startChar
171 0x00, 0x00, // uint16_t endChar
172 0x00, 0x01, // uint16_t font-ID
173 0x00, // uint8_t face-style-flags
174 0x12, // uint8_t font-size
175 0xFF, 0xFF, 0xFF, 0xFF, // uint8_t text-color-rgba[4]
178 0x00, 0x00, 0x00, 0x12, // uint32_t size
179 'f', 't', 'a', 'b', // uint8_t name[4]
180 0x00, 0x01, // uint16_t entry-count
182 0x00, 0x01, // uint16_t font-ID
183 0x05, // uint8_t font-name-length
184 'S', 'e', 'r', 'i', 'f',// uint8_t font[font-name-length]
189 MovTextContext *s = avctx->priv_data;
191 avctx->extradata_size = sizeof text_sample_entry;
192 avctx->extradata = av_mallocz(avctx->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
193 if (!avctx->extradata)
194 return AVERROR(ENOMEM);
196 av_bprint_init(&s->buffer, 0, AV_BPRINT_SIZE_UNLIMITED);
198 memcpy(avctx->extradata, text_sample_entry, avctx->extradata_size);
200 s->ass_ctx = ff_ass_split(avctx->subtitle_header);
201 return s->ass_ctx ? 0 : AVERROR_INVALIDDATA;
204 static void mov_text_style_cb(void *priv, const char style, int close)
206 MovTextContext *s = priv;
208 if (!(s->box_flags & STYL_BOX)) { //first style entry
210 s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
212 if (!s->style_attributes_temp) {
213 av_bprint_clear(&s->buffer);
214 s->box_flags &= ~STYL_BOX;
218 s->style_attributes_temp->style_flag = 0;
219 s->style_attributes_temp->style_start = AV_RB16(&s->text_pos);
221 if (s->style_attributes_temp->style_flag) { //break the style record here and start a new one
222 s->style_attributes_temp->style_end = AV_RB16(&s->text_pos);
223 av_dynarray_add(&s->style_attributes, &s->count, s->style_attributes_temp);
224 s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
225 if (!s->style_attributes_temp) {
227 av_bprint_clear(&s->buffer);
228 s->box_flags &= ~STYL_BOX;
232 s->style_attributes_temp->style_flag = s->style_attributes[s->count - 1]->style_flag;
233 s->style_attributes_temp->style_start = AV_RB16(&s->text_pos);
235 s->style_attributes_temp->style_flag = 0;
236 s->style_attributes_temp->style_start = AV_RB16(&s->text_pos);
241 s->style_attributes_temp->style_flag |= STYLE_FLAG_BOLD;
244 s->style_attributes_temp->style_flag |= STYLE_FLAG_ITALIC;
247 s->style_attributes_temp->style_flag |= STYLE_FLAG_UNDERLINE;
251 s->style_attributes_temp->style_end = AV_RB16(&s->text_pos);
252 av_dynarray_add(&s->style_attributes, &s->count, s->style_attributes_temp);
254 s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
256 if (!s->style_attributes_temp) {
258 av_bprint_clear(&s->buffer);
259 s->box_flags &= ~STYL_BOX;
263 s->style_attributes_temp->style_flag = s->style_attributes[s->count - 1]->style_flag;
266 s->style_attributes_temp->style_flag &= ~STYLE_FLAG_BOLD;
269 s->style_attributes_temp->style_flag &= ~STYLE_FLAG_ITALIC;
272 s->style_attributes_temp->style_flag &= ~STYLE_FLAG_UNDERLINE;
275 if (s->style_attributes_temp->style_flag) { //start of new style record
276 s->style_attributes_temp->style_start = AV_RB16(&s->text_pos);
279 s->box_flags |= STYL_BOX;
282 static void mov_text_color_cb(void *priv, unsigned int color, unsigned int color_id)
284 MovTextContext *s = priv;
285 if (color_id == 2) { //secondary color changes
286 if (s->box_flags & HLIT_BOX) { //close tag
287 s->hlit.end = AV_RB16(&s->text_pos);
289 s->box_flags |= HCLR_BOX;
290 s->box_flags |= HLIT_BOX;
291 s->hlit.start = AV_RB16(&s->text_pos);
292 s->hclr.color = color | (0xFF << 24); //set alpha value to FF
295 /* If there are more than one secondary color changes in ASS, take start of
296 first section and end of last section. Movtext allows only one
297 highlight box per sample.
301 static void mov_text_text_cb(void *priv, const char *text, int len)
303 MovTextContext *s = priv;
304 av_bprint_append_data(&s->buffer, text, len);
308 static void mov_text_new_line_cb(void *priv, int forced)
310 MovTextContext *s = priv;
311 av_bprint_append_data(&s->buffer, "\n", 1);
315 static const ASSCodesCallbacks mov_text_callbacks = {
316 .text = mov_text_text_cb,
317 .new_line = mov_text_new_line_cb,
318 .style = mov_text_style_cb,
319 .color = mov_text_color_cb,
322 static int mov_text_encode_frame(AVCodecContext *avctx, unsigned char *buf,
323 int bufsize, const AVSubtitle *sub)
325 MovTextContext *s = avctx->priv_data;
333 s->style_entries = 0;
334 for (i = 0; i < sub->num_rects; i++) {
335 const char *ass = sub->rects[i]->ass;
337 if (sub->rects[i]->type != SUBTITLE_ASS) {
338 av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n");
339 return AVERROR(ENOSYS);
342 #if FF_API_ASS_TIMING
343 if (!strncmp(ass, "Dialogue: ", 10)) {
345 dialog = ff_ass_split_dialog(s->ass_ctx, ass, 0, &num);
346 for (; dialog && num--; dialog++) {
347 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
351 dialog = ff_ass_split_dialog2(s->ass_ctx, ass);
353 return AVERROR(ENOMEM);
354 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
355 ff_ass_free_dialog(&dialog);
356 #if FF_API_ASS_TIMING
360 for (j = 0; j < box_count; j++) {
361 box_types[j].encode(s, box_types[j].type);
365 AV_WB16(buf, s->text_pos);
368 if (!av_bprint_is_complete(&s->buffer)) {
369 length = AVERROR(ENOMEM);
373 if (!s->buffer.len) {
378 if (s->buffer.len > bufsize - 3) {
379 av_log(avctx, AV_LOG_ERROR, "Buffer too small for ASS event.\n");
380 length = AVERROR(EINVAL);
384 memcpy(buf, s->buffer.str, s->buffer.len);
385 length = s->buffer.len + 2;
388 av_bprint_clear(&s->buffer);
392 static int mov_text_encode_close(AVCodecContext *avctx)
394 MovTextContext *s = avctx->priv_data;
395 ff_ass_split_free(s->ass_ctx);
396 av_bprint_finalize(&s->buffer, NULL);
400 AVCodec ff_movtext_encoder = {
402 .long_name = NULL_IF_CONFIG_SMALL("3GPP Timed Text subtitle"),
403 .type = AVMEDIA_TYPE_SUBTITLE,
404 .id = AV_CODEC_ID_MOV_TEXT,
405 .priv_data_size = sizeof(MovTextContext),
406 .init = mov_text_encode_init,
407 .encode_sub = mov_text_encode_frame,
408 .close = mov_text_encode_close,