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