]> git.sesse.net Git - ffmpeg/blob - libavcodec/movtextenc.c
lavc/movtextenc: use correct color component order
[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_entries;
72     uint16_t style_fontID;
73     uint8_t style_fontsize;
74     uint32_t style_color;
75     uint16_t text_pos;
76     uint16_t byte_count;
77 } MovTextContext;
78
79 typedef struct {
80     uint32_t type;
81     void (*encode)(MovTextContext *s, uint32_t tsmb_type);
82 } Box;
83
84 static void mov_text_cleanup(MovTextContext *s)
85 {
86     int j;
87     if (s->box_flags & STYL_BOX) {
88         for (j = 0; j < s->count; j++) {
89             av_freep(&s->style_attributes[j]);
90         }
91         av_freep(&s->style_attributes);
92     }
93 }
94
95 static void encode_styl(MovTextContext *s, uint32_t tsmb_type)
96 {
97     int j;
98     uint32_t tsmb_size;
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);
118         }
119         mov_text_cleanup(s);
120     }
121 }
122
123 static void encode_hlit(MovTextContext *s, uint32_t tsmb_type)
124 {
125     uint32_t tsmb_size;
126     if (s->box_flags & HLIT_BOX) {
127         tsmb_size = 12;
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);
133     }
134 }
135
136 static void encode_hclr(MovTextContext *s, uint32_t tsmb_type)
137 {
138     uint32_t tsmb_size, color;
139     if (s->box_flags & HCLR_BOX) {
140         tsmb_size = 12;
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);
146     }
147 }
148
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 },
153 };
154
155 const static size_t box_count = FF_ARRAY_ELEMS(box_types);
156
157 static av_cold int mov_text_encode_init(AVCodecContext *avctx)
158 {
159     /*
160      * For now, we'll use a fixed default style. When we add styling
161      * support, this will be generated from the ASS style.
162      */
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]
168         // BoxRecord {
169         0x00, 0x00,             // int16_t top
170         0x00, 0x00,             // int16_t left
171         0x00, 0x00,             // int16_t bottom
172         0x00, 0x00,             // int16_t right
173         // };
174         // StyleRecord {
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]
181         // };
182         // FontTableBox {
183         0x00, 0x00, 0x00, 0x12, // uint32_t size
184         'f', 't', 'a', 'b',     // uint8_t name[4]
185         0x00, 0x01,             // uint16_t entry-count
186         // FontRecord {
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]
190         // };
191         // };
192     };
193
194     MovTextContext *s = avctx->priv_data;
195     s->avctx = avctx;
196
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);
201
202     av_bprint_init(&s->buffer, 0, AV_BPRINT_SIZE_UNLIMITED);
203
204     memcpy(avctx->extradata, text_sample_entry, avctx->extradata_size);
205
206     s->ass_ctx = ff_ass_split(avctx->subtitle_header);
207     return s->ass_ctx ? 0 : AVERROR_INVALIDDATA;
208 }
209
210 static void mov_text_style_cb(void *priv, const char style, int close)
211 {
212     MovTextContext *s = priv;
213     if (!close) {
214         if (!(s->box_flags & STYL_BOX)) {   //first style entry
215
216             s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
217
218             if (!s->style_attributes_temp) {
219                 av_bprint_clear(&s->buffer);
220                 s->box_flags &= ~STYL_BOX;
221                 return;
222             }
223
224             s->style_attributes_temp->style_flag = 0;
225             s->style_attributes_temp->style_start = AV_RB16(&s->text_pos);
226         } else {
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) {
232                     mov_text_cleanup(s);
233                     av_bprint_clear(&s->buffer);
234                     s->box_flags &= ~STYL_BOX;
235                     return;
236                 }
237
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);
240             } else {
241                 s->style_attributes_temp->style_flag = 0;
242                 s->style_attributes_temp->style_start = AV_RB16(&s->text_pos);
243             }
244         }
245         switch (style){
246         case 'b':
247             s->style_attributes_temp->style_flag |= STYLE_FLAG_BOLD;
248             break;
249         case 'i':
250             s->style_attributes_temp->style_flag |= STYLE_FLAG_ITALIC;
251             break;
252         case 'u':
253             s->style_attributes_temp->style_flag |= STYLE_FLAG_UNDERLINE;
254             break;
255         }
256     } else if (!s->style_attributes_temp) {
257         av_log(s->avctx, AV_LOG_WARNING, "Ignoring unmatched close tag\n");
258         return;
259     } else {
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);
262
263         s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
264
265         if (!s->style_attributes_temp) {
266             mov_text_cleanup(s);
267             av_bprint_clear(&s->buffer);
268             s->box_flags &= ~STYL_BOX;
269             return;
270         }
271
272         s->style_attributes_temp->style_flag = s->style_attributes[s->count - 1]->style_flag;
273         switch (style){
274         case 'b':
275             s->style_attributes_temp->style_flag &= ~STYLE_FLAG_BOLD;
276             break;
277         case 'i':
278             s->style_attributes_temp->style_flag &= ~STYLE_FLAG_ITALIC;
279             break;
280         case 'u':
281             s->style_attributes_temp->style_flag &= ~STYLE_FLAG_UNDERLINE;
282             break;
283         }
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);
286         }
287     }
288     s->box_flags |= STYL_BOX;
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 = AV_RB16(&s->text_pos);
299         } else {
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
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 uint16_t utf8_strlen(const char *text, int len)
313 {
314     uint16_t i = 0, ret = 0;
315     while (i < len) {
316         char c = text[i];
317         if ((c & 0x80) == 0)
318             i += 1;
319         else if ((c & 0xE0) == 0xC0)
320             i += 2;
321         else if ((c & 0xF0) == 0xE0)
322             i += 3;
323         else if ((c & 0xF8) == 0xF0)
324             i += 4;
325         else
326             return 0;
327         ret++;
328     }
329     return ret;
330 }
331
332 static void mov_text_text_cb(void *priv, const char *text, int len)
333 {
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;
340 }
341
342 static void mov_text_new_line_cb(void *priv, int forced)
343 {
344     MovTextContext *s = priv;
345     av_bprint_append_data(&s->buffer, "\n", 1);
346     s->text_pos += 1;
347     s->byte_count += 1;
348 }
349
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,
355 };
356
357 static int mov_text_encode_frame(AVCodecContext *avctx, unsigned char *buf,
358                                  int bufsize, const AVSubtitle *sub)
359 {
360     MovTextContext *s = avctx->priv_data;
361     ASSDialog *dialog;
362     int i, length;
363     size_t j;
364
365     s->byte_count = 0;
366     s->text_pos = 0;
367     s->count = 0;
368     s->box_flags = 0;
369     s->style_entries = 0;
370     for (i = 0; i < sub->num_rects; i++) {
371         const char *ass = sub->rects[i]->ass;
372
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);
376         }
377
378 #if FF_API_ASS_TIMING
379         if (!strncmp(ass, "Dialogue: ", 10)) {
380             int num;
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);
384             }
385         } else {
386 #endif
387             dialog = ff_ass_split_dialog2(s->ass_ctx, ass);
388             if (!dialog)
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
393         }
394 #endif
395
396         for (j = 0; j < box_count; j++) {
397             box_types[j].encode(s, box_types[j].type);
398         }
399     }
400
401     AV_WB16(buf, s->byte_count);
402     buf += 2;
403
404     if (!av_bprint_is_complete(&s->buffer)) {
405         length = AVERROR(ENOMEM);
406         goto exit;
407     }
408
409     if (!s->buffer.len) {
410         length = 0;
411         goto exit;
412     }
413
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);
417         goto exit;
418     }
419
420     memcpy(buf, s->buffer.str, s->buffer.len);
421     length = s->buffer.len + 2;
422
423 exit:
424     av_bprint_clear(&s->buffer);
425     return length;
426 }
427
428 static int mov_text_encode_close(AVCodecContext *avctx)
429 {
430     MovTextContext *s = avctx->priv_data;
431     ff_ass_split_free(s->ass_ctx);
432     av_bprint_finalize(&s->buffer, NULL);
433     return 0;
434 }
435
436 AVCodec ff_movtext_encoder = {
437     .name           = "mov_text",
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,
445 };