]> git.sesse.net Git - ffmpeg/blob - libavcodec/movtextenc.c
lavc/movtextenc: simplify initialization of new style record
[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     ASSStyle *ass_dialog_style;
73     AVBPrint buffer;
74     StyleBox **style_attributes;
75     StyleBox *style_attributes_temp;
76     HighlightBox hlit;
77     HilightcolorBox hclr;
78     int count;
79     uint8_t box_flags;
80     StyleBox d;
81     uint16_t text_pos;
82     uint16_t byte_count;
83 } MovTextContext;
84
85 typedef struct {
86     uint32_t type;
87     void (*encode)(MovTextContext *s, uint32_t tsmb_type);
88 } Box;
89
90 static void mov_text_cleanup(MovTextContext *s)
91 {
92     int j;
93     if (s->box_flags & STYL_BOX) {
94         for (j = 0; j < s->count; j++) {
95             av_freep(&s->style_attributes[j]);
96         }
97         av_freep(&s->style_attributes);
98     }
99     if (s->style_attributes_temp) {
100         *s->style_attributes_temp = s->d;
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->style_attributes[j]->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->style_attributes[j]->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         s->style_attributes_temp->style_fontsize != s->d.style_fontsize) {
250         // last style != defaults, end the style entry and start a new one
251         s->box_flags |= STYL_BOX;
252         s->style_attributes_temp->style_end = s->text_pos;
253         av_dynarray_add(&s->style_attributes, &s->count, s->style_attributes_temp);
254         s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
255         if (!s->style_attributes_temp) {
256             mov_text_cleanup(s);
257             av_bprint_clear(&s->buffer);
258             s->box_flags &= ~STYL_BOX;
259             return 0;
260         }
261
262         *s->style_attributes_temp = s->d;
263         s->style_attributes_temp->style_start = s->text_pos;
264     } else { // style entry matches defaults, drop entry
265         *s->style_attributes_temp = s->d;
266         s->style_attributes_temp->style_start = s->text_pos;
267     }
268     return 1;
269 }
270
271 static uint8_t mov_text_style_to_flag(const char style)
272 {
273     uint8_t style_flag = 0;
274
275     switch (style){
276     case 'b':
277         style_flag = STYLE_FLAG_BOLD;
278         break;
279     case 'i':
280         style_flag = STYLE_FLAG_ITALIC;
281         break;
282     case 'u':
283         style_flag = STYLE_FLAG_UNDERLINE;
284         break;
285     }
286     return style_flag;
287 }
288
289 static void mov_text_style_set(MovTextContext *s, uint8_t style_flags)
290 {
291     if (!s->style_attributes_temp ||
292         !((s->style_attributes_temp->style_flag & style_flags) ^ style_flags)) {
293         // setting flags that that are already set
294         return;
295     }
296     if (mov_text_style_start(s))
297         s->style_attributes_temp->style_flag |= style_flags;
298 }
299
300 static void mov_text_style_cb(void *priv, const char style, int close)
301 {
302     MovTextContext *s = priv;
303     uint8_t style_flag = mov_text_style_to_flag(style);
304
305     if (!s->style_attributes_temp ||
306         !!(s->style_attributes_temp->style_flag & style_flag) != close) {
307         // setting flag that is already set
308         return;
309     }
310     if (mov_text_style_start(s)) {
311         if (!close)
312             s->style_attributes_temp->style_flag |= style_flag;
313         else
314             s->style_attributes_temp->style_flag &= ~style_flag;
315     }
316 }
317
318 static void mov_text_color_set(MovTextContext *s, uint32_t color)
319 {
320     if (!s->style_attributes_temp ||
321         (s->style_attributes_temp->style_color & 0xffffff00) == color) {
322         // color hasn't changed
323         return;
324     }
325     if (mov_text_style_start(s))
326         s->style_attributes_temp->style_color = (color & 0xffffff00) |
327                             (s->style_attributes_temp->style_color & 0xff);
328 }
329
330 static void mov_text_color_cb(void *priv, unsigned int color, unsigned int color_id)
331 {
332     MovTextContext *s = priv;
333
334     color = BGR_TO_RGB(color) << 8;
335     if (color_id == 1) {    //primary color changes
336         mov_text_color_set(s, color);
337     } else if (color_id == 2) {    //secondary color changes
338         if (s->box_flags & HLIT_BOX) {  //close tag
339             s->hlit.end = s->text_pos;
340         } else {
341             s->box_flags |= HCLR_BOX;
342             s->box_flags |= HLIT_BOX;
343             s->hlit.start = s->text_pos;
344             s->hclr.color = color | 0xFF;  //set alpha value to FF
345         }
346     }
347     /* If there are more than one secondary color changes in ASS, take start of
348        first section and end of last section. Movtext allows only one
349        highlight box per sample.
350      */
351 }
352
353 static void mov_text_alpha_set(MovTextContext *s, uint8_t alpha)
354 {
355     if (!s->style_attributes_temp ||
356         (s->style_attributes_temp->style_color & 0xff) == alpha) {
357         // color hasn't changed
358         return;
359     }
360     if (mov_text_style_start(s))
361         s->style_attributes_temp->style_color =
362                 (s->style_attributes_temp->style_color & 0xffffff00) | alpha;
363 }
364
365 static void mov_text_alpha_cb(void *priv, int alpha, int alpha_id)
366 {
367     MovTextContext *s = priv;
368
369     if (alpha_id == 1) // primary alpha changes
370         mov_text_alpha_set(s, 255 - alpha);
371 }
372
373 static void mov_text_font_size_set(MovTextContext *s, int size)
374 {
375     if (!s->style_attributes_temp ||
376         s->style_attributes_temp->style_fontsize == size) {
377         // color hasn't changed
378         return;
379     }
380     if (mov_text_style_start(s))
381         s->style_attributes_temp->style_fontsize = size;
382 }
383
384 static void mov_text_font_size_cb(void *priv, int size)
385 {
386     mov_text_font_size_set((MovTextContext*)priv, size);
387 }
388
389 static void mov_text_end_cb(void *priv)
390 {
391     // End of text, close any open style record
392     mov_text_style_start((MovTextContext*)priv);
393 }
394
395 static void mov_text_ass_style_set(MovTextContext *s, ASSStyle *style)
396 {
397     uint8_t    style_flags, alpha;
398     uint32_t   color;
399
400     if (style) {
401         style_flags = (!!style->bold      * STYLE_FLAG_BOLD)   |
402                       (!!style->italic    * STYLE_FLAG_ITALIC) |
403                       (!!style->underline * STYLE_FLAG_UNDERLINE);
404         mov_text_style_set(s, style_flags);
405         color = BGR_TO_RGB(style->primary_color & 0xffffff) << 8;
406         mov_text_color_set(s, color);
407         alpha = 255 - ((uint32_t)style->primary_color >> 24);
408         mov_text_alpha_set(s, alpha);
409         mov_text_font_size_set(s, style->font_size);
410     } else {
411         // End current style record, go back to defaults
412         mov_text_style_start(s);
413     }
414 }
415
416 static void mov_text_dialog(MovTextContext *s, ASSDialog *dialog)
417 {
418     ASSStyle * style = ff_ass_style_get(s->ass_ctx, dialog->style);
419
420     s->ass_dialog_style = style;
421     mov_text_ass_style_set(s, style);
422 }
423
424 static void mov_text_cancel_overrides_cb(void *priv, const char * style_name)
425 {
426     MovTextContext *s = priv;
427     ASSStyle * style;
428
429     if (!style_name || !*style_name)
430         style = s->ass_dialog_style;
431     else
432         style= ff_ass_style_get(s->ass_ctx, style_name);
433
434     mov_text_ass_style_set(s, style);
435 }
436
437 static uint16_t utf8_strlen(const char *text, int len)
438 {
439     uint16_t i = 0, ret = 0;
440     while (i < len) {
441         char c = text[i];
442         if ((c & 0x80) == 0)
443             i += 1;
444         else if ((c & 0xE0) == 0xC0)
445             i += 2;
446         else if ((c & 0xF0) == 0xE0)
447             i += 3;
448         else if ((c & 0xF8) == 0xF0)
449             i += 4;
450         else
451             return 0;
452         ret++;
453     }
454     return ret;
455 }
456
457 static void mov_text_text_cb(void *priv, const char *text, int len)
458 {
459     uint16_t utf8_len = utf8_strlen(text, len);
460     MovTextContext *s = priv;
461     av_bprint_append_data(&s->buffer, text, len);
462     // If it's not utf-8, just use the byte length
463     s->text_pos += utf8_len ? utf8_len : len;
464     s->byte_count += len;
465 }
466
467 static void mov_text_new_line_cb(void *priv, int forced)
468 {
469     MovTextContext *s = priv;
470     av_bprint_append_data(&s->buffer, "\n", 1);
471     s->text_pos += 1;
472     s->byte_count += 1;
473 }
474
475 static const ASSCodesCallbacks mov_text_callbacks = {
476     .text             = mov_text_text_cb,
477     .new_line         = mov_text_new_line_cb,
478     .style            = mov_text_style_cb,
479     .color            = mov_text_color_cb,
480     .alpha            = mov_text_alpha_cb,
481     .font_size        = mov_text_font_size_cb,
482     .cancel_overrides = mov_text_cancel_overrides_cb,
483     .end              = mov_text_end_cb,
484 };
485
486 static int mov_text_encode_frame(AVCodecContext *avctx, unsigned char *buf,
487                                  int bufsize, const AVSubtitle *sub)
488 {
489     MovTextContext *s = avctx->priv_data;
490     ASSDialog *dialog;
491     int i, length;
492     size_t j;
493
494     s->byte_count = 0;
495     s->text_pos = 0;
496     s->count = 0;
497     s->box_flags = 0;
498     for (i = 0; i < sub->num_rects; i++) {
499         const char *ass = sub->rects[i]->ass;
500
501         if (sub->rects[i]->type != SUBTITLE_ASS) {
502             av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n");
503             return AVERROR(ENOSYS);
504         }
505
506 #if FF_API_ASS_TIMING
507         if (!strncmp(ass, "Dialogue: ", 10)) {
508             int num;
509             dialog = ff_ass_split_dialog(s->ass_ctx, ass, 0, &num);
510             for (; dialog && num--; dialog++) {
511                 mov_text_dialog(s, dialog);
512                 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
513             }
514         } else {
515 #endif
516             dialog = ff_ass_split_dialog2(s->ass_ctx, ass);
517             if (!dialog)
518                 return AVERROR(ENOMEM);
519             mov_text_dialog(s, dialog);
520             ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
521             ff_ass_free_dialog(&dialog);
522 #if FF_API_ASS_TIMING
523         }
524 #endif
525
526         for (j = 0; j < box_count; j++) {
527             box_types[j].encode(s, box_types[j].type);
528         }
529     }
530
531     AV_WB16(buf, s->byte_count);
532     buf += 2;
533
534     if (!av_bprint_is_complete(&s->buffer)) {
535         length = AVERROR(ENOMEM);
536         goto exit;
537     }
538
539     if (!s->buffer.len) {
540         length = 0;
541         goto exit;
542     }
543
544     if (s->buffer.len > bufsize - 3) {
545         av_log(avctx, AV_LOG_ERROR, "Buffer too small for ASS event.\n");
546         length = AVERROR(EINVAL);
547         goto exit;
548     }
549
550     memcpy(buf, s->buffer.str, s->buffer.len);
551     length = s->buffer.len + 2;
552
553 exit:
554     av_bprint_clear(&s->buffer);
555     return length;
556 }
557
558 static int mov_text_encode_close(AVCodecContext *avctx)
559 {
560     MovTextContext *s = avctx->priv_data;
561     ff_ass_split_free(s->ass_ctx);
562     av_bprint_finalize(&s->buffer, NULL);
563     return 0;
564 }
565
566 AVCodec ff_movtext_encoder = {
567     .name           = "mov_text",
568     .long_name      = NULL_IF_CONFIG_SMALL("3GPP Timed Text subtitle"),
569     .type           = AVMEDIA_TYPE_SUBTITLE,
570     .id             = AV_CODEC_ID_MOV_TEXT,
571     .priv_data_size = sizeof(MovTextContext),
572     .init           = mov_text_encode_init,
573     .encode_sub     = mov_text_encode_frame,
574     .close          = mov_text_encode_close,
575 };