]> git.sesse.net Git - ffmpeg/blob - libavcodec/movtextenc.c
Merge commit '2a9e1c122eed66be1b26b747342b848300b226c7'
[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 av_bprint_append_any(buf, data, size)   av_bprint_append_data(buf, ((const char*)data), size)
43
44 typedef struct {
45     uint16_t style_start;
46     uint16_t style_end;
47     uint8_t style_flag;
48 } StyleBox;
49
50 typedef struct {
51     uint16_t start;
52     uint16_t end;
53 } HighlightBox;
54
55 typedef struct {
56    uint32_t color;
57 } HilightcolorBox;
58
59 typedef struct {
60     AVCodecContext *avctx;
61
62     ASSSplitContext *ass_ctx;
63     AVBPrint buffer;
64     StyleBox **style_attributes;
65     StyleBox *style_attributes_temp;
66     HighlightBox hlit;
67     HilightcolorBox hclr;
68     int count;
69     uint8_t box_flags;
70     uint16_t style_entries;
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 }
93
94 static void encode_styl(MovTextContext *s, uint32_t tsmb_type)
95 {
96     int j;
97     uint32_t tsmb_size;
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);
117         }
118         mov_text_cleanup(s);
119     }
120 }
121
122 static void encode_hlit(MovTextContext *s, uint32_t tsmb_type)
123 {
124     uint32_t tsmb_size;
125     if (s->box_flags & HLIT_BOX) {
126         tsmb_size = 12;
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);
132     }
133 }
134
135 static void encode_hclr(MovTextContext *s, uint32_t tsmb_type)
136 {
137     uint32_t tsmb_size;
138     if (s->box_flags & HCLR_BOX) {
139         tsmb_size = 12;
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);
144     }
145 }
146
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 },
151 };
152
153 const static size_t box_count = FF_ARRAY_ELEMS(box_types);
154
155 static av_cold int mov_text_encode_init(AVCodecContext *avctx)
156 {
157     /*
158      * For now, we'll use a fixed default style. When we add styling
159      * support, this will be generated from the ASS style.
160      */
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]
166         // BoxRecord {
167         0x00, 0x00,             // int16_t top
168         0x00, 0x00,             // int16_t left
169         0x00, 0x00,             // int16_t bottom
170         0x00, 0x00,             // int16_t right
171         // };
172         // StyleRecord {
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]
179         // };
180         // FontTableBox {
181         0x00, 0x00, 0x00, 0x12, // uint32_t size
182         'f', 't', 'a', 'b',     // uint8_t name[4]
183         0x00, 0x01,             // uint16_t entry-count
184         // FontRecord {
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]
188         // };
189         // };
190     };
191
192     MovTextContext *s = avctx->priv_data;
193     s->avctx = avctx;
194
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);
199
200     av_bprint_init(&s->buffer, 0, AV_BPRINT_SIZE_UNLIMITED);
201
202     memcpy(avctx->extradata, text_sample_entry, avctx->extradata_size);
203
204     s->ass_ctx = ff_ass_split(avctx->subtitle_header);
205     return s->ass_ctx ? 0 : AVERROR_INVALIDDATA;
206 }
207
208 static void mov_text_style_cb(void *priv, const char style, int close)
209 {
210     MovTextContext *s = priv;
211     if (!close) {
212         if (!(s->box_flags & STYL_BOX)) {   //first style entry
213
214             s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
215
216             if (!s->style_attributes_temp) {
217                 av_bprint_clear(&s->buffer);
218                 s->box_flags &= ~STYL_BOX;
219                 return;
220             }
221
222             s->style_attributes_temp->style_flag = 0;
223             s->style_attributes_temp->style_start = AV_RB16(&s->text_pos);
224         } else {
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) {
230                     mov_text_cleanup(s);
231                     av_bprint_clear(&s->buffer);
232                     s->box_flags &= ~STYL_BOX;
233                     return;
234                 }
235
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);
238             } else {
239                 s->style_attributes_temp->style_flag = 0;
240                 s->style_attributes_temp->style_start = AV_RB16(&s->text_pos);
241             }
242         }
243         switch (style){
244         case 'b':
245             s->style_attributes_temp->style_flag |= STYLE_FLAG_BOLD;
246             break;
247         case 'i':
248             s->style_attributes_temp->style_flag |= STYLE_FLAG_ITALIC;
249             break;
250         case 'u':
251             s->style_attributes_temp->style_flag |= STYLE_FLAG_UNDERLINE;
252             break;
253         }
254     } else if (!s->style_attributes_temp) {
255         av_log(s->avctx, AV_LOG_WARNING, "Ignoring unmatched close tag\n");
256         return;
257     } else {
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);
260
261         s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
262
263         if (!s->style_attributes_temp) {
264             mov_text_cleanup(s);
265             av_bprint_clear(&s->buffer);
266             s->box_flags &= ~STYL_BOX;
267             return;
268         }
269
270         s->style_attributes_temp->style_flag = s->style_attributes[s->count - 1]->style_flag;
271         switch (style){
272         case 'b':
273             s->style_attributes_temp->style_flag &= ~STYLE_FLAG_BOLD;
274             break;
275         case 'i':
276             s->style_attributes_temp->style_flag &= ~STYLE_FLAG_ITALIC;
277             break;
278         case 'u':
279             s->style_attributes_temp->style_flag &= ~STYLE_FLAG_UNDERLINE;
280             break;
281         }
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);
284         }
285     }
286     s->box_flags |= STYL_BOX;
287 }
288
289 static void mov_text_color_cb(void *priv, unsigned int color, unsigned int color_id)
290 {
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);
295         } else {
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
300         }
301     }
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.
305      */
306 }
307
308 static uint16_t utf8_strlen(const char *text, int len)
309 {
310     uint16_t i = 0, ret = 0;
311     while (i < len) {
312         char c = text[i];
313         if ((c & 0x80) == 0)
314             i += 1;
315         else if ((c & 0xE0) == 0xC0)
316             i += 2;
317         else if ((c & 0xF0) == 0xE0)
318             i += 3;
319         else if ((c & 0xF8) == 0xF0)
320             i += 4;
321         else
322             return 0;
323         ret++;
324     }
325     return ret;
326 }
327
328 static void mov_text_text_cb(void *priv, const char *text, int len)
329 {
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;
336 }
337
338 static void mov_text_new_line_cb(void *priv, int forced)
339 {
340     MovTextContext *s = priv;
341     av_bprint_append_data(&s->buffer, "\n", 1);
342     s->text_pos += 1;
343     s->byte_count += 1;
344 }
345
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,
351 };
352
353 static int mov_text_encode_frame(AVCodecContext *avctx, unsigned char *buf,
354                                  int bufsize, const AVSubtitle *sub)
355 {
356     MovTextContext *s = avctx->priv_data;
357     ASSDialog *dialog;
358     int i, length;
359     size_t j;
360
361     s->byte_count = 0;
362     s->text_pos = 0;
363     s->count = 0;
364     s->box_flags = 0;
365     s->style_entries = 0;
366     for (i = 0; i < sub->num_rects; i++) {
367         const char *ass = sub->rects[i]->ass;
368
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);
372         }
373
374 #if FF_API_ASS_TIMING
375         if (!strncmp(ass, "Dialogue: ", 10)) {
376             int num;
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);
380             }
381         } else {
382 #endif
383             dialog = ff_ass_split_dialog2(s->ass_ctx, ass);
384             if (!dialog)
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
389         }
390 #endif
391
392         for (j = 0; j < box_count; j++) {
393             box_types[j].encode(s, box_types[j].type);
394         }
395     }
396
397     AV_WB16(buf, s->byte_count);
398     buf += 2;
399
400     if (!av_bprint_is_complete(&s->buffer)) {
401         length = AVERROR(ENOMEM);
402         goto exit;
403     }
404
405     if (!s->buffer.len) {
406         length = 0;
407         goto exit;
408     }
409
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);
413         goto exit;
414     }
415
416     memcpy(buf, s->buffer.str, s->buffer.len);
417     length = s->buffer.len + 2;
418
419 exit:
420     av_bprint_clear(&s->buffer);
421     return length;
422 }
423
424 static int mov_text_encode_close(AVCodecContext *avctx)
425 {
426     MovTextContext *s = avctx->priv_data;
427     ff_ass_split_free(s->ass_ctx);
428     av_bprint_finalize(&s->buffer, NULL);
429     return 0;
430 }
431
432 AVCodec ff_movtext_encoder = {
433     .name           = "mov_text",
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,
441 };