]> git.sesse.net Git - ffmpeg/blob - libavcodec/movtextenc.c
lavc/movtextenc: fix unclosed style records
[ffmpeg] / libavcodec / movtextenc.c
1 /*
2  * 3GPP TS 26.245 Timed Text encoder
3  * Copyright (c) 2012  Philip Langdale <philipl@overt.org>
4  *
5  * This file is part of FFmpeg.
6  *
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.
11  *
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.
16  *
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
20  */
21
22 #include <stdarg.h>
23 #include "avcodec.h"
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"
30 #include "ass.h"
31
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
36 #define SIZE_ADD                10
37
38 #define STYL_BOX   (1<<0)
39 #define HLIT_BOX   (1<<1)
40 #define HCLR_BOX   (1<<2)
41
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)
44
45 typedef struct {
46     uint16_t style_start;
47     uint16_t style_end;
48     uint8_t style_flag;
49 } StyleBox;
50
51 typedef struct {
52     uint16_t start;
53     uint16_t end;
54 } HighlightBox;
55
56 typedef struct {
57    uint32_t color;
58 } HilightcolorBox;
59
60 typedef struct {
61     AVCodecContext *avctx;
62
63     ASSSplitContext *ass_ctx;
64     AVBPrint buffer;
65     StyleBox **style_attributes;
66     StyleBox *style_attributes_temp;
67     HighlightBox hlit;
68     HilightcolorBox hclr;
69     int count;
70     uint8_t box_flags;
71     uint16_t style_fontID;
72     uint8_t style_fontsize;
73     uint32_t style_color;
74     uint16_t text_pos;
75     uint16_t byte_count;
76 } MovTextContext;
77
78 typedef struct {
79     uint32_t type;
80     void (*encode)(MovTextContext *s, uint32_t tsmb_type);
81 } Box;
82
83 static void mov_text_cleanup(MovTextContext *s)
84 {
85     int j;
86     if (s->box_flags & STYL_BOX) {
87         for (j = 0; j < s->count; j++) {
88             av_freep(&s->style_attributes[j]);
89         }
90         av_freep(&s->style_attributes);
91     }
92     if (s->style_attributes_temp) {
93         s->style_attributes_temp->style_flag = 0;
94         s->style_attributes_temp->style_start = 0;
95     }
96 }
97
98 static void encode_styl(MovTextContext *s, uint32_t tsmb_type)
99 {
100     int j;
101     uint32_t tsmb_size;
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;
117
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);
126         }
127     }
128     mov_text_cleanup(s);
129 }
130
131 static void encode_hlit(MovTextContext *s, uint32_t tsmb_type)
132 {
133     uint32_t tsmb_size;
134     uint16_t start, end;
135     if (s->box_flags & HLIT_BOX) {
136         tsmb_size = 12;
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);
144     }
145 }
146
147 static void encode_hclr(MovTextContext *s, uint32_t tsmb_type)
148 {
149     uint32_t tsmb_size, color;
150     if (s->box_flags & HCLR_BOX) {
151         tsmb_size = 12;
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);
157     }
158 }
159
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 },
164 };
165
166 const static size_t box_count = FF_ARRAY_ELEMS(box_types);
167
168 static av_cold int mov_text_encode_init(AVCodecContext *avctx)
169 {
170     /*
171      * For now, we'll use a fixed default style. When we add styling
172      * support, this will be generated from the ASS style.
173      */
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]
179         // BoxRecord {
180         0x00, 0x00,             // int16_t top
181         0x00, 0x00,             // int16_t left
182         0x00, 0x00,             // int16_t bottom
183         0x00, 0x00,             // int16_t right
184         // };
185         // StyleRecord {
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]
192         // };
193         // FontTableBox {
194         0x00, 0x00, 0x00, 0x12, // uint32_t size
195         'f', 't', 'a', 'b',     // uint8_t name[4]
196         0x00, 0x01,             // uint16_t entry-count
197         // FontRecord {
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]
201         // };
202         // };
203     };
204
205     MovTextContext *s = avctx->priv_data;
206     s->avctx = avctx;
207
208     s->style_attributes_temp = av_mallocz(sizeof(*s->style_attributes_temp));
209     if (!s->style_attributes_temp) {
210         return AVERROR(ENOMEM);
211     }
212
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);
217
218     av_bprint_init(&s->buffer, 0, AV_BPRINT_SIZE_UNLIMITED);
219
220     memcpy(avctx->extradata, text_sample_entry, avctx->extradata_size);
221
222     s->ass_ctx = ff_ass_split(avctx->subtitle_header);
223     return s->ass_ctx ? 0 : AVERROR_INVALIDDATA;
224 }
225
226 // Start a new style box if needed
227 static int mov_text_style_start(MovTextContext *s)
228 {
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
232         return 1;
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) {
240             mov_text_cleanup(s);
241             av_bprint_clear(&s->buffer);
242             s->box_flags &= ~STYL_BOX;
243             return 0;
244         }
245
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;
251     }
252     return 1;
253 }
254
255 static uint8_t mov_text_style_to_flag(const char style)
256 {
257     uint8_t style_flag = 0;
258
259     switch (style){
260     case 'b':
261         style_flag = STYLE_FLAG_BOLD;
262         break;
263     case 'i':
264         style_flag = STYLE_FLAG_ITALIC;
265         break;
266     case 'u':
267         style_flag = STYLE_FLAG_UNDERLINE;
268         break;
269     }
270     return style_flag;
271 }
272
273 static void mov_text_style_cb(void *priv, const char style, int close)
274 {
275     MovTextContext *s = priv;
276     uint8_t style_flag = mov_text_style_to_flag(style);
277
278     if (!s->style_attributes_temp ||
279         !!(s->style_attributes_temp->style_flag & style_flag) != close) {
280         // setting flag that is already set
281         return;
282     }
283     if (mov_text_style_start(s)) {
284         if (!close)
285             s->style_attributes_temp->style_flag |= style_flag;
286         else
287             s->style_attributes_temp->style_flag &= ~style_flag;
288     }
289 }
290
291 static void mov_text_color_cb(void *priv, unsigned int color, unsigned int color_id)
292 {
293     MovTextContext *s = priv;
294
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;
299         } else {
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
304         }
305     }
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.
309      */
310 }
311
312 static void mov_text_end_cb(void *priv)
313 {
314     // End of text, close any open style record
315     mov_text_style_start((MovTextContext*)priv);
316 }
317
318 static uint16_t utf8_strlen(const char *text, int len)
319 {
320     uint16_t i = 0, ret = 0;
321     while (i < len) {
322         char c = text[i];
323         if ((c & 0x80) == 0)
324             i += 1;
325         else if ((c & 0xE0) == 0xC0)
326             i += 2;
327         else if ((c & 0xF0) == 0xE0)
328             i += 3;
329         else if ((c & 0xF8) == 0xF0)
330             i += 4;
331         else
332             return 0;
333         ret++;
334     }
335     return ret;
336 }
337
338 static void mov_text_text_cb(void *priv, const char *text, int len)
339 {
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;
346 }
347
348 static void mov_text_new_line_cb(void *priv, int forced)
349 {
350     MovTextContext *s = priv;
351     av_bprint_append_data(&s->buffer, "\n", 1);
352     s->text_pos += 1;
353     s->byte_count += 1;
354 }
355
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,
362 };
363
364 static int mov_text_encode_frame(AVCodecContext *avctx, unsigned char *buf,
365                                  int bufsize, const AVSubtitle *sub)
366 {
367     MovTextContext *s = avctx->priv_data;
368     ASSDialog *dialog;
369     int i, length;
370     size_t j;
371
372     s->byte_count = 0;
373     s->text_pos = 0;
374     s->count = 0;
375     s->box_flags = 0;
376     for (i = 0; i < sub->num_rects; i++) {
377         const char *ass = sub->rects[i]->ass;
378
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);
382         }
383
384 #if FF_API_ASS_TIMING
385         if (!strncmp(ass, "Dialogue: ", 10)) {
386             int num;
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);
390             }
391         } else {
392 #endif
393             dialog = ff_ass_split_dialog2(s->ass_ctx, ass);
394             if (!dialog)
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
399         }
400 #endif
401
402         for (j = 0; j < box_count; j++) {
403             box_types[j].encode(s, box_types[j].type);
404         }
405     }
406
407     AV_WB16(buf, s->byte_count);
408     buf += 2;
409
410     if (!av_bprint_is_complete(&s->buffer)) {
411         length = AVERROR(ENOMEM);
412         goto exit;
413     }
414
415     if (!s->buffer.len) {
416         length = 0;
417         goto exit;
418     }
419
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);
423         goto exit;
424     }
425
426     memcpy(buf, s->buffer.str, s->buffer.len);
427     length = s->buffer.len + 2;
428
429 exit:
430     av_bprint_clear(&s->buffer);
431     return length;
432 }
433
434 static int mov_text_encode_close(AVCodecContext *avctx)
435 {
436     MovTextContext *s = avctx->priv_data;
437     ff_ass_split_free(s->ass_ctx);
438     av_bprint_finalize(&s->buffer, NULL);
439     return 0;
440 }
441
442 AVCodec ff_movtext_encoder = {
443     .name           = "mov_text",
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,
451 };