]> git.sesse.net Git - ffmpeg/blob - libavcodec/movtextenc.c
lavc/movtextenc: add alpha tag handling
[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 DEFAULT_STYLE_FONT_ID  0x01
43 #define DEFAULT_STYLE_FONTSIZE 0x12
44 #define DEFAULT_STYLE_COLOR    0xffffffff
45 #define DEFAULT_STYLE_FLAG     0x00
46
47 #define BGR_TO_RGB(c) (((c) & 0xff) << 16 | ((c) & 0xff00) | (((c) >> 16) & 0xff))
48 #define av_bprint_append_any(buf, data, size)   av_bprint_append_data(buf, ((const char*)data), size)
49
50 typedef struct {
51     uint16_t style_start;
52     uint16_t style_end;
53     uint8_t style_flag;
54     uint16_t style_fontID;
55     uint8_t style_fontsize;
56     uint32_t style_color;
57 } StyleBox;
58
59 typedef struct {
60     uint16_t start;
61     uint16_t end;
62 } HighlightBox;
63
64 typedef struct {
65    uint32_t color;
66 } HilightcolorBox;
67
68 typedef struct {
69     AVCodecContext *avctx;
70
71     ASSSplitContext *ass_ctx;
72     AVBPrint buffer;
73     StyleBox **style_attributes;
74     StyleBox *style_attributes_temp;
75     HighlightBox hlit;
76     HilightcolorBox hclr;
77     int count;
78     uint8_t box_flags;
79     StyleBox d;
80     uint16_t text_pos;
81     uint16_t byte_count;
82 } MovTextContext;
83
84 typedef struct {
85     uint32_t type;
86     void (*encode)(MovTextContext *s, uint32_t tsmb_type);
87 } Box;
88
89 static void mov_text_cleanup(MovTextContext *s)
90 {
91     int j;
92     if (s->box_flags & STYL_BOX) {
93         for (j = 0; j < s->count; j++) {
94             av_freep(&s->style_attributes[j]);
95         }
96         av_freep(&s->style_attributes);
97     }
98     if (s->style_attributes_temp) {
99         s->style_attributes_temp->style_flag = 0;
100         s->style_attributes_temp->style_start = 0;
101     }
102 }
103
104 static void encode_styl(MovTextContext *s, uint32_t tsmb_type)
105 {
106     int j;
107     uint32_t tsmb_size;
108     uint16_t style_entries;
109     if ((s->box_flags & STYL_BOX) && s->count) {
110         tsmb_size = s->count * STYLE_RECORD_SIZE + SIZE_ADD;
111         tsmb_size = AV_RB32(&tsmb_size);
112         style_entries = AV_RB16(&s->count);
113         /*The above three attributes are hard coded for now
114         but will come from ASS style in the future*/
115         av_bprint_append_any(&s->buffer, &tsmb_size, 4);
116         av_bprint_append_any(&s->buffer, &tsmb_type, 4);
117         av_bprint_append_any(&s->buffer, &style_entries, 2);
118         for (j = 0; j < s->count; j++) {
119             uint16_t style_start, style_end, style_fontID;
120             uint32_t style_color;
121
122             style_start  = AV_RB16(&s->style_attributes[j]->style_start);
123             style_end    = AV_RB16(&s->style_attributes[j]->style_end);
124             style_color  = AV_RB32(&s->style_attributes[j]->style_color);
125             style_fontID = AV_RB16(&s->d.style_fontID);
126
127             av_bprint_append_any(&s->buffer, &style_start, 2);
128             av_bprint_append_any(&s->buffer, &style_end, 2);
129             av_bprint_append_any(&s->buffer, &style_fontID, 2);
130             av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_flag, 1);
131             av_bprint_append_any(&s->buffer, &s->d.style_fontsize, 1);
132             av_bprint_append_any(&s->buffer, &style_color, 4);
133         }
134     }
135     mov_text_cleanup(s);
136 }
137
138 static void encode_hlit(MovTextContext *s, uint32_t tsmb_type)
139 {
140     uint32_t tsmb_size;
141     uint16_t start, end;
142     if (s->box_flags & HLIT_BOX) {
143         tsmb_size = 12;
144         tsmb_size = AV_RB32(&tsmb_size);
145         start     = AV_RB16(&s->hlit.start);
146         end       = AV_RB16(&s->hlit.end);
147         av_bprint_append_any(&s->buffer, &tsmb_size, 4);
148         av_bprint_append_any(&s->buffer, &tsmb_type, 4);
149         av_bprint_append_any(&s->buffer, &start, 2);
150         av_bprint_append_any(&s->buffer, &end, 2);
151     }
152 }
153
154 static void encode_hclr(MovTextContext *s, uint32_t tsmb_type)
155 {
156     uint32_t tsmb_size, color;
157     if (s->box_flags & HCLR_BOX) {
158         tsmb_size = 12;
159         tsmb_size = AV_RB32(&tsmb_size);
160         color     = AV_RB32(&s->hclr.color);
161         av_bprint_append_any(&s->buffer, &tsmb_size, 4);
162         av_bprint_append_any(&s->buffer, &tsmb_type, 4);
163         av_bprint_append_any(&s->buffer, &color, 4);
164     }
165 }
166
167 static const Box box_types[] = {
168     { MKTAG('s','t','y','l'), encode_styl },
169     { MKTAG('h','l','i','t'), encode_hlit },
170     { MKTAG('h','c','l','r'), encode_hclr },
171 };
172
173 const static size_t box_count = FF_ARRAY_ELEMS(box_types);
174
175 static av_cold int mov_text_encode_init(AVCodecContext *avctx)
176 {
177     /*
178      * For now, we'll use a fixed default style. When we add styling
179      * support, this will be generated from the ASS style.
180      */
181     static const uint8_t text_sample_entry[] = {
182         0x00, 0x00, 0x00, 0x00, // uint32_t displayFlags
183         0x01,                   // int8_t horizontal-justification
184         0xFF,                   // int8_t vertical-justification
185         0x00, 0x00, 0x00, 0x00, // uint8_t background-color-rgba[4]
186         // BoxRecord {
187         0x00, 0x00,             // int16_t top
188         0x00, 0x00,             // int16_t left
189         0x00, 0x00,             // int16_t bottom
190         0x00, 0x00,             // int16_t right
191         // };
192         // StyleRecord {
193         0x00, 0x00,             // uint16_t startChar
194         0x00, 0x00,             // uint16_t endChar
195         0x00, 0x01,             // uint16_t font-ID
196         0x00,                   // uint8_t face-style-flags
197         0x12,                   // uint8_t font-size
198         0xFF, 0xFF, 0xFF, 0xFF, // uint8_t text-color-rgba[4]
199         // };
200         // FontTableBox {
201         0x00, 0x00, 0x00, 0x12, // uint32_t size
202         'f', 't', 'a', 'b',     // uint8_t name[4]
203         0x00, 0x01,             // uint16_t entry-count
204         // FontRecord {
205         0x00, 0x01,             // uint16_t font-ID
206         0x05,                   // uint8_t font-name-length
207         'S', 'e', 'r', 'i', 'f',// uint8_t font[font-name-length]
208         // };
209         // };
210     };
211
212     MovTextContext *s = avctx->priv_data;
213     s->avctx = avctx;
214
215     s->style_attributes_temp = av_mallocz(sizeof(*s->style_attributes_temp));
216     if (!s->style_attributes_temp) {
217         return AVERROR(ENOMEM);
218     }
219
220     avctx->extradata_size = sizeof text_sample_entry;
221     avctx->extradata = av_mallocz(avctx->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
222     if (!avctx->extradata)
223         return AVERROR(ENOMEM);
224
225     av_bprint_init(&s->buffer, 0, AV_BPRINT_SIZE_UNLIMITED);
226
227     memcpy(avctx->extradata, text_sample_entry, avctx->extradata_size);
228
229     s->ass_ctx = ff_ass_split(avctx->subtitle_header);
230
231     // TODO: Initialize from ASS style record
232     s->d.style_fontID   = DEFAULT_STYLE_FONT_ID;
233     s->d.style_fontsize = DEFAULT_STYLE_FONTSIZE;
234     s->d.style_color    = DEFAULT_STYLE_COLOR;
235     s->d.style_flag     = DEFAULT_STYLE_FLAG;
236
237     return s->ass_ctx ? 0 : AVERROR_INVALIDDATA;
238 }
239
240 // Start a new style box if needed
241 static int mov_text_style_start(MovTextContext *s)
242 {
243     // there's an existing style entry
244     if (s->style_attributes_temp->style_start == s->text_pos)
245         // Still at same text pos, use same entry
246         return 1;
247     if (s->style_attributes_temp->style_flag  != s->d.style_flag ||
248         s->style_attributes_temp->style_color != s->d.style_color) {
249         // last style != defaults, end the style entry and start a new one
250         s->box_flags |= STYL_BOX;
251         s->style_attributes_temp->style_end = s->text_pos;
252         av_dynarray_add(&s->style_attributes, &s->count, s->style_attributes_temp);
253         s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
254         if (!s->style_attributes_temp) {
255             mov_text_cleanup(s);
256             av_bprint_clear(&s->buffer);
257             s->box_flags &= ~STYL_BOX;
258             return 0;
259         }
260
261         s->style_attributes_temp->style_flag = s->style_attributes[s->count - 1]->style_flag;
262         s->style_attributes_temp->style_color = s->style_attributes[s->count - 1]->style_color;
263         s->style_attributes_temp->style_start = s->text_pos;
264     } else { // style entry matches defaults, drop entry
265         s->style_attributes_temp->style_flag = s->d.style_flag;
266         s->style_attributes_temp->style_color = s->d.style_color;
267         s->style_attributes_temp->style_start = s->text_pos;
268     }
269     return 1;
270 }
271
272 static uint8_t mov_text_style_to_flag(const char style)
273 {
274     uint8_t style_flag = 0;
275
276     switch (style){
277     case 'b':
278         style_flag = STYLE_FLAG_BOLD;
279         break;
280     case 'i':
281         style_flag = STYLE_FLAG_ITALIC;
282         break;
283     case 'u':
284         style_flag = STYLE_FLAG_UNDERLINE;
285         break;
286     }
287     return style_flag;
288 }
289
290 static void mov_text_style_set(MovTextContext *s, uint8_t style_flags)
291 {
292     if (!s->style_attributes_temp ||
293         !((s->style_attributes_temp->style_flag & style_flags) ^ style_flags)) {
294         // setting flags that that are already set
295         return;
296     }
297     if (mov_text_style_start(s))
298         s->style_attributes_temp->style_flag |= style_flags;
299 }
300
301 static void mov_text_style_cb(void *priv, const char style, int close)
302 {
303     MovTextContext *s = priv;
304     uint8_t style_flag = mov_text_style_to_flag(style);
305
306     if (!s->style_attributes_temp ||
307         !!(s->style_attributes_temp->style_flag & style_flag) != close) {
308         // setting flag that is already set
309         return;
310     }
311     if (mov_text_style_start(s)) {
312         if (!close)
313             s->style_attributes_temp->style_flag |= style_flag;
314         else
315             s->style_attributes_temp->style_flag &= ~style_flag;
316     }
317 }
318
319 static void mov_text_color_set(MovTextContext *s, uint32_t color)
320 {
321     if (!s->style_attributes_temp ||
322         (s->style_attributes_temp->style_color & 0xffffff00) == color) {
323         // color hasn't changed
324         return;
325     }
326     if (mov_text_style_start(s))
327         s->style_attributes_temp->style_color = (color & 0xffffff00) |
328                             (s->style_attributes_temp->style_color & 0xff);
329 }
330
331 static void mov_text_color_cb(void *priv, unsigned int color, unsigned int color_id)
332 {
333     MovTextContext *s = priv;
334
335     color = BGR_TO_RGB(color) << 8;
336     if (color_id == 1) {    //primary color changes
337         mov_text_color_set(s, color);
338     } else if (color_id == 2) {    //secondary color changes
339         if (s->box_flags & HLIT_BOX) {  //close tag
340             s->hlit.end = s->text_pos;
341         } else {
342             s->box_flags |= HCLR_BOX;
343             s->box_flags |= HLIT_BOX;
344             s->hlit.start = s->text_pos;
345             s->hclr.color = color | 0xFF;  //set alpha value to FF
346         }
347     }
348     /* If there are more than one secondary color changes in ASS, take start of
349        first section and end of last section. Movtext allows only one
350        highlight box per sample.
351      */
352 }
353
354 static void mov_text_alpha_set(MovTextContext *s, uint8_t alpha)
355 {
356     if (!s->style_attributes_temp ||
357         (s->style_attributes_temp->style_color & 0xff) == alpha) {
358         // color hasn't changed
359         return;
360     }
361     if (mov_text_style_start(s))
362         s->style_attributes_temp->style_color =
363                 (s->style_attributes_temp->style_color & 0xffffff00) | alpha;
364 }
365
366 static void mov_text_alpha_cb(void *priv, int alpha, int alpha_id)
367 {
368     MovTextContext *s = priv;
369
370     if (alpha_id == 1) // primary alpha changes
371         mov_text_alpha_set(s, 255 - alpha);
372 }
373
374 static void mov_text_end_cb(void *priv)
375 {
376     // End of text, close any open style record
377     mov_text_style_start((MovTextContext*)priv);
378 }
379
380 static void mov_text_dialog(MovTextContext *s, ASSDialog *dialog)
381 {
382     ASSStyle * style = ff_ass_style_get(s->ass_ctx, dialog->style);
383     uint8_t    style_flags, alpha;
384     uint32_t   color;
385
386     if (style) {
387         style_flags = (!!style->bold      * STYLE_FLAG_BOLD)   |
388                       (!!style->italic    * STYLE_FLAG_ITALIC) |
389                       (!!style->underline * STYLE_FLAG_UNDERLINE);
390         mov_text_style_set(s, style_flags);
391         color = BGR_TO_RGB(style->primary_color & 0xffffff) << 8;
392         mov_text_color_set(s, color);
393         alpha = 255 - ((uint32_t)style->primary_color >> 24);
394         mov_text_alpha_set(s, alpha);
395     }
396 }
397
398 static uint16_t utf8_strlen(const char *text, int len)
399 {
400     uint16_t i = 0, ret = 0;
401     while (i < len) {
402         char c = text[i];
403         if ((c & 0x80) == 0)
404             i += 1;
405         else if ((c & 0xE0) == 0xC0)
406             i += 2;
407         else if ((c & 0xF0) == 0xE0)
408             i += 3;
409         else if ((c & 0xF8) == 0xF0)
410             i += 4;
411         else
412             return 0;
413         ret++;
414     }
415     return ret;
416 }
417
418 static void mov_text_text_cb(void *priv, const char *text, int len)
419 {
420     uint16_t utf8_len = utf8_strlen(text, len);
421     MovTextContext *s = priv;
422     av_bprint_append_data(&s->buffer, text, len);
423     // If it's not utf-8, just use the byte length
424     s->text_pos += utf8_len ? utf8_len : len;
425     s->byte_count += len;
426 }
427
428 static void mov_text_new_line_cb(void *priv, int forced)
429 {
430     MovTextContext *s = priv;
431     av_bprint_append_data(&s->buffer, "\n", 1);
432     s->text_pos += 1;
433     s->byte_count += 1;
434 }
435
436 static const ASSCodesCallbacks mov_text_callbacks = {
437     .text     = mov_text_text_cb,
438     .new_line = mov_text_new_line_cb,
439     .style    = mov_text_style_cb,
440     .color    = mov_text_color_cb,
441     .alpha    = mov_text_alpha_cb,
442     .end      = mov_text_end_cb,
443 };
444
445 static int mov_text_encode_frame(AVCodecContext *avctx, unsigned char *buf,
446                                  int bufsize, const AVSubtitle *sub)
447 {
448     MovTextContext *s = avctx->priv_data;
449     ASSDialog *dialog;
450     int i, length;
451     size_t j;
452
453     s->byte_count = 0;
454     s->text_pos = 0;
455     s->count = 0;
456     s->box_flags = 0;
457     for (i = 0; i < sub->num_rects; i++) {
458         const char *ass = sub->rects[i]->ass;
459
460         if (sub->rects[i]->type != SUBTITLE_ASS) {
461             av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n");
462             return AVERROR(ENOSYS);
463         }
464
465 #if FF_API_ASS_TIMING
466         if (!strncmp(ass, "Dialogue: ", 10)) {
467             int num;
468             dialog = ff_ass_split_dialog(s->ass_ctx, ass, 0, &num);
469             for (; dialog && num--; dialog++) {
470                 mov_text_dialog(s, dialog);
471                 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
472             }
473         } else {
474 #endif
475             dialog = ff_ass_split_dialog2(s->ass_ctx, ass);
476             if (!dialog)
477                 return AVERROR(ENOMEM);
478             mov_text_dialog(s, dialog);
479             ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
480             ff_ass_free_dialog(&dialog);
481 #if FF_API_ASS_TIMING
482         }
483 #endif
484
485         for (j = 0; j < box_count; j++) {
486             box_types[j].encode(s, box_types[j].type);
487         }
488     }
489
490     AV_WB16(buf, s->byte_count);
491     buf += 2;
492
493     if (!av_bprint_is_complete(&s->buffer)) {
494         length = AVERROR(ENOMEM);
495         goto exit;
496     }
497
498     if (!s->buffer.len) {
499         length = 0;
500         goto exit;
501     }
502
503     if (s->buffer.len > bufsize - 3) {
504         av_log(avctx, AV_LOG_ERROR, "Buffer too small for ASS event.\n");
505         length = AVERROR(EINVAL);
506         goto exit;
507     }
508
509     memcpy(buf, s->buffer.str, s->buffer.len);
510     length = s->buffer.len + 2;
511
512 exit:
513     av_bprint_clear(&s->buffer);
514     return length;
515 }
516
517 static int mov_text_encode_close(AVCodecContext *avctx)
518 {
519     MovTextContext *s = avctx->priv_data;
520     ff_ass_split_free(s->ass_ctx);
521     av_bprint_finalize(&s->buffer, NULL);
522     return 0;
523 }
524
525 AVCodec ff_movtext_encoder = {
526     .name           = "mov_text",
527     .long_name      = NULL_IF_CONFIG_SMALL("3GPP Timed Text subtitle"),
528     .type           = AVMEDIA_TYPE_SUBTITLE,
529     .id             = AV_CODEC_ID_MOV_TEXT,
530     .priv_data_size = sizeof(MovTextContext),
531     .init           = mov_text_encode_init,
532     .encode_sub     = mov_text_encode_frame,
533     .close          = mov_text_encode_close,
534 };