]> git.sesse.net Git - ffmpeg/blob - libavcodec/movtextenc.c
avformat/argo_brp: support MASK streams
[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/opt.h"
25 #include "libavutil/avassert.h"
26 #include "libavutil/avstring.h"
27 #include "libavutil/intreadwrite.h"
28 #include "libavutil/mem.h"
29 #include "libavutil/common.h"
30 #include "ass_split.h"
31 #include "ass.h"
32
33 #define STYLE_FLAG_BOLD         (1<<0)
34 #define STYLE_FLAG_ITALIC       (1<<1)
35 #define STYLE_FLAG_UNDERLINE    (1<<2)
36 #define STYLE_RECORD_SIZE       12
37 #define SIZE_ADD                10
38
39 #define STYL_BOX   (1<<0)
40 #define HLIT_BOX   (1<<1)
41 #define HCLR_BOX   (1<<2)
42
43 #define DEFAULT_STYLE_FONT_ID  0x01
44 #define DEFAULT_STYLE_FONTSIZE 0x12
45 #define DEFAULT_STYLE_COLOR    0xffffffff
46 #define DEFAULT_STYLE_FLAG     0x00
47
48 #define BGR_TO_RGB(c) (((c) & 0xff) << 16 | ((c) & 0xff00) | (((c) >> 16) & 0xff))
49 #define FONTSIZE_SCALE(s,fs) ((fs) * (s)->font_scale_factor + 0.5)
50 #define av_bprint_append_any(buf, data, size)   av_bprint_append_data(buf, ((const char*)data), size)
51
52 typedef struct {
53     uint16_t style_start;
54     uint16_t style_end;
55     uint8_t style_flag;
56     uint16_t style_fontID;
57     uint8_t style_fontsize;
58     uint32_t style_color;
59 } StyleBox;
60
61 typedef struct {
62     uint16_t start;
63     uint16_t end;
64 } HighlightBox;
65
66 typedef struct {
67    uint32_t color;
68 } HilightcolorBox;
69
70 typedef struct {
71     AVClass *class;
72     AVCodecContext *avctx;
73
74     ASSSplitContext *ass_ctx;
75     ASSStyle *ass_dialog_style;
76     AVBPrint buffer;
77     StyleBox **style_attributes;
78     StyleBox *style_attributes_temp;
79     HighlightBox hlit;
80     HilightcolorBox hclr;
81     int count;
82     uint8_t box_flags;
83     StyleBox d;
84     uint16_t text_pos;
85     uint16_t byte_count;
86     char ** fonts;
87     int font_count;
88     double font_scale_factor;
89     int frame_height;
90 } MovTextContext;
91
92 typedef struct {
93     uint32_t type;
94     void (*encode)(MovTextContext *s, uint32_t tsmb_type);
95 } Box;
96
97 static void mov_text_cleanup(MovTextContext *s)
98 {
99     int j;
100     if (s->box_flags & STYL_BOX) {
101         for (j = 0; j < s->count; j++) {
102             av_freep(&s->style_attributes[j]);
103         }
104         av_freep(&s->style_attributes);
105     }
106     if (s->style_attributes_temp) {
107         *s->style_attributes_temp = s->d;
108     }
109 }
110
111 static void encode_styl(MovTextContext *s, uint32_t tsmb_type)
112 {
113     int j;
114     uint32_t tsmb_size;
115     uint16_t style_entries;
116     if ((s->box_flags & STYL_BOX) && s->count) {
117         tsmb_size = s->count * STYLE_RECORD_SIZE + SIZE_ADD;
118         tsmb_size = AV_RB32(&tsmb_size);
119         style_entries = AV_RB16(&s->count);
120         /*The above three attributes are hard coded for now
121         but will come from ASS style in the future*/
122         av_bprint_append_any(&s->buffer, &tsmb_size, 4);
123         av_bprint_append_any(&s->buffer, &tsmb_type, 4);
124         av_bprint_append_any(&s->buffer, &style_entries, 2);
125         for (j = 0; j < s->count; j++) {
126             uint16_t style_start, style_end, style_fontID;
127             uint32_t style_color;
128
129             style_start  = AV_RB16(&s->style_attributes[j]->style_start);
130             style_end    = AV_RB16(&s->style_attributes[j]->style_end);
131             style_color  = AV_RB32(&s->style_attributes[j]->style_color);
132             style_fontID = AV_RB16(&s->style_attributes[j]->style_fontID);
133
134             av_bprint_append_any(&s->buffer, &style_start, 2);
135             av_bprint_append_any(&s->buffer, &style_end, 2);
136             av_bprint_append_any(&s->buffer, &style_fontID, 2);
137             av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_flag, 1);
138             av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_fontsize, 1);
139             av_bprint_append_any(&s->buffer, &style_color, 4);
140         }
141     }
142     mov_text_cleanup(s);
143 }
144
145 static void encode_hlit(MovTextContext *s, uint32_t tsmb_type)
146 {
147     uint32_t tsmb_size;
148     uint16_t start, end;
149     if (s->box_flags & HLIT_BOX) {
150         tsmb_size = 12;
151         tsmb_size = AV_RB32(&tsmb_size);
152         start     = AV_RB16(&s->hlit.start);
153         end       = AV_RB16(&s->hlit.end);
154         av_bprint_append_any(&s->buffer, &tsmb_size, 4);
155         av_bprint_append_any(&s->buffer, &tsmb_type, 4);
156         av_bprint_append_any(&s->buffer, &start, 2);
157         av_bprint_append_any(&s->buffer, &end, 2);
158     }
159 }
160
161 static void encode_hclr(MovTextContext *s, uint32_t tsmb_type)
162 {
163     uint32_t tsmb_size, color;
164     if (s->box_flags & HCLR_BOX) {
165         tsmb_size = 12;
166         tsmb_size = AV_RB32(&tsmb_size);
167         color     = AV_RB32(&s->hclr.color);
168         av_bprint_append_any(&s->buffer, &tsmb_size, 4);
169         av_bprint_append_any(&s->buffer, &tsmb_type, 4);
170         av_bprint_append_any(&s->buffer, &color, 4);
171     }
172 }
173
174 static const Box box_types[] = {
175     { MKTAG('s','t','y','l'), encode_styl },
176     { MKTAG('h','l','i','t'), encode_hlit },
177     { MKTAG('h','c','l','r'), encode_hclr },
178 };
179
180 const static size_t box_count = FF_ARRAY_ELEMS(box_types);
181
182 static int mov_text_encode_close(AVCodecContext *avctx)
183 {
184     MovTextContext *s = avctx->priv_data;
185     int i;
186
187     ff_ass_split_free(s->ass_ctx);
188     if (s->style_attributes) {
189         for (i = 0; i < s->count; i++) {
190             av_freep(&s->style_attributes[i]);
191         }
192         av_freep(&s->style_attributes);
193     }
194     av_freep(&s->fonts);
195     av_freep(&s->style_attributes_temp);
196     av_bprint_finalize(&s->buffer, NULL);
197     return 0;
198 }
199
200 static int encode_sample_description(AVCodecContext *avctx)
201 {
202     ASS * ass;
203     ASSStyle * style;
204     int i, j;
205     uint32_t tsmb_size, tsmb_type, back_color, style_color;
206     uint16_t style_start, style_end, fontID, count;
207     int font_names_total_len = 0;
208     MovTextContext *s = avctx->priv_data;
209
210     static const uint8_t display_and_justification[] = {
211         0x00, 0x00, 0x00, 0x00, // uint32_t displayFlags
212         0x01,                   // int8_t horizontal-justification
213         0xFF,                   // int8_t vertical-justification
214     };
215     //  0x00, 0x00, 0x00, 0x00, // uint8_t background-color-rgba[4]
216     static const uint8_t box_record[] = {
217     //     BoxRecord {
218         0x00, 0x00,             // int16_t top
219         0x00, 0x00,             // int16_t left
220         0x00, 0x00,             // int16_t bottom
221         0x00, 0x00,             // int16_t right
222     //     };
223     };
224     //     StyleRecord {
225     //  0x00, 0x00,             // uint16_t startChar
226     //  0x00, 0x00,             // uint16_t endChar
227     //  0x00, 0x01,             // uint16_t font-ID
228     //  0x00,                   // uint8_t face-style-flags
229     //  0x12,                   // uint8_t font-size
230     //  0xFF, 0xFF, 0xFF, 0xFF, // uint8_t text-color-rgba[4]
231     //     };
232     //     FontTableBox {
233     //  0x00, 0x00, 0x00, 0x12, // uint32_t size
234     //  'f', 't', 'a', 'b',     // uint8_t name[4]
235     //  0x00, 0x01,             // uint16_t entry-count
236     //     FontRecord {
237     //  0x00, 0x01,             // uint16_t font-ID
238     //  0x05,                   // uint8_t font-name-length
239     //  'S', 'e', 'r', 'i', 'f',// uint8_t font[font-name-length]
240     //     };
241     //     };
242
243     // Populate sample description from ASS header
244     ass = (ASS*)s->ass_ctx;
245     // Compute font scaling factor based on (optionally) provided
246     // output video height and ASS script play_res_y
247     if (s->frame_height && ass->script_info.play_res_y)
248         s->font_scale_factor = (double)s->frame_height / ass->script_info.play_res_y;
249     else
250         s->font_scale_factor = 1;
251
252     style = ff_ass_style_get(s->ass_ctx, "Default");
253     if (!style && ass->styles_count) {
254         style = &ass->styles[0];
255     }
256     s->d.style_fontID   = DEFAULT_STYLE_FONT_ID;
257     s->d.style_fontsize = DEFAULT_STYLE_FONTSIZE;
258     s->d.style_color    = DEFAULT_STYLE_COLOR;
259     s->d.style_flag     = DEFAULT_STYLE_FLAG;
260     if (style) {
261         s->d.style_fontsize = FONTSIZE_SCALE(s, style->font_size);
262         s->d.style_color = BGR_TO_RGB(style->primary_color & 0xffffff) << 8 |
263                            255 - ((uint32_t)style->primary_color >> 24);
264         s->d.style_flag = (!!style->bold      * STYLE_FLAG_BOLD)   |
265                           (!!style->italic    * STYLE_FLAG_ITALIC) |
266                           (!!style->underline * STYLE_FLAG_UNDERLINE);
267         back_color = (BGR_TO_RGB(style->back_color & 0xffffff) << 8) |
268                      (255 - ((uint32_t)style->back_color >> 24));
269     }
270
271     av_bprint_append_any(&s->buffer, display_and_justification,
272                                      sizeof(display_and_justification));
273     back_color = AV_RB32(&back_color);
274     av_bprint_append_any(&s->buffer, &back_color, 4);
275     //     BoxRecord {
276     av_bprint_append_any(&s->buffer, box_record, sizeof(box_record));
277     //     };
278     //     StyleRecord {
279     style_start  = AV_RB16(&s->d.style_start);
280     style_end    = AV_RB16(&s->d.style_end);
281     fontID = AV_RB16(&s->d.style_fontID);
282     style_color  = AV_RB32(&s->d.style_color);
283     av_bprint_append_any(&s->buffer, &style_start, 2);
284     av_bprint_append_any(&s->buffer, &style_end, 2);
285     av_bprint_append_any(&s->buffer, &fontID, 2);
286     av_bprint_append_any(&s->buffer, &s->d.style_flag, 1);
287     av_bprint_append_any(&s->buffer, &s->d.style_fontsize, 1);
288     av_bprint_append_any(&s->buffer, &style_color, 4);
289     //     };
290
291     // Build font table
292     // We can't build a complete font table since that would require
293     // scanning all dialogs first.  But we can at least fill in what
294     // is avaiable in the ASS header
295     if (style && ass->styles_count) {
296         // Find unique font names
297         av_dynarray_add(&s->fonts, &s->font_count, style->font_name);
298         font_names_total_len += strlen(style->font_name);
299         for (i = 0; i < ass->styles_count; i++) {
300             int found = 0;
301             for (j = 0; j < s->font_count; j++) {
302                 if (!strcmp(s->fonts[j], ass->styles[i].font_name)) {
303                     found = 1;
304                     break;
305                 }
306             }
307             if (!found) {
308                 av_dynarray_add(&s->fonts, &s->font_count,
309                                            ass->styles[i].font_name);
310                 font_names_total_len += strlen(ass->styles[i].font_name);
311             }
312         }
313     } else
314         av_dynarray_add(&s->fonts, &s->font_count, (char*)"Serif");
315
316     //     FontTableBox {
317     tsmb_size = SIZE_ADD + 3 * s->font_count + font_names_total_len;
318     tsmb_size = AV_RB32(&tsmb_size);
319     tsmb_type = MKTAG('f','t','a','b');
320     count = AV_RB16(&s->font_count);
321     av_bprint_append_any(&s->buffer, &tsmb_size, 4);
322     av_bprint_append_any(&s->buffer, &tsmb_type, 4);
323     av_bprint_append_any(&s->buffer, &count, 2);
324     //     FontRecord {
325     for (i = 0; i < s->font_count; i++) {
326         int len;
327         fontID = i + 1;
328         fontID = AV_RB16(&fontID);
329         av_bprint_append_any(&s->buffer, &fontID, 2);
330         len = strlen(s->fonts[i]);
331         av_bprint_append_any(&s->buffer, &len, 1);
332         av_bprint_append_any(&s->buffer, s->fonts[i], len);
333     }
334     //     };
335     //     };
336
337     if (!av_bprint_is_complete(&s->buffer)) {
338         return AVERROR(ENOMEM);
339     }
340
341     avctx->extradata_size = s->buffer.len;
342     avctx->extradata = av_mallocz(avctx->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
343     if (!avctx->extradata) {
344         return AVERROR(ENOMEM);
345     }
346
347     memcpy(avctx->extradata, s->buffer.str, avctx->extradata_size);
348     av_bprint_clear(&s->buffer);
349
350     return 0;
351 }
352
353 static av_cold int mov_text_encode_init(AVCodecContext *avctx)
354 {
355     int ret;
356     MovTextContext *s = avctx->priv_data;
357     s->avctx = avctx;
358
359     av_bprint_init(&s->buffer, 0, AV_BPRINT_SIZE_UNLIMITED);
360
361     s->style_attributes_temp = av_mallocz(sizeof(*s->style_attributes_temp));
362     if (!s->style_attributes_temp) {
363         ret = AVERROR(ENOMEM);
364         goto fail;
365     }
366
367     s->ass_ctx = ff_ass_split(avctx->subtitle_header);
368     if (!s->ass_ctx) {
369         ret = AVERROR_INVALIDDATA;
370         goto fail;
371     }
372     ret = encode_sample_description(avctx);
373     if (ret < 0)
374         goto fail;
375
376     return 0;
377
378 fail:
379     mov_text_encode_close(avctx);
380     return ret;
381 }
382
383 // Start a new style box if needed
384 static int mov_text_style_start(MovTextContext *s)
385 {
386     // there's an existing style entry
387     if (s->style_attributes_temp->style_start == s->text_pos)
388         // Still at same text pos, use same entry
389         return 1;
390     if (s->style_attributes_temp->style_flag     != s->d.style_flag   ||
391         s->style_attributes_temp->style_color    != s->d.style_color  ||
392         s->style_attributes_temp->style_fontID   != s->d.style_fontID ||
393         s->style_attributes_temp->style_fontsize != s->d.style_fontsize) {
394         // last style != defaults, end the style entry and start a new one
395         s->box_flags |= STYL_BOX;
396         s->style_attributes_temp->style_end = s->text_pos;
397         av_dynarray_add(&s->style_attributes, &s->count, s->style_attributes_temp);
398         s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
399         if (!s->style_attributes_temp) {
400             mov_text_cleanup(s);
401             av_bprint_clear(&s->buffer);
402             s->box_flags &= ~STYL_BOX;
403             return 0;
404         }
405
406         *s->style_attributes_temp = s->d;
407         s->style_attributes_temp->style_start = s->text_pos;
408     } else { // style entry matches defaults, drop entry
409         *s->style_attributes_temp = s->d;
410         s->style_attributes_temp->style_start = s->text_pos;
411     }
412     return 1;
413 }
414
415 static uint8_t mov_text_style_to_flag(const char style)
416 {
417     uint8_t style_flag = 0;
418
419     switch (style){
420     case 'b':
421         style_flag = STYLE_FLAG_BOLD;
422         break;
423     case 'i':
424         style_flag = STYLE_FLAG_ITALIC;
425         break;
426     case 'u':
427         style_flag = STYLE_FLAG_UNDERLINE;
428         break;
429     }
430     return style_flag;
431 }
432
433 static void mov_text_style_set(MovTextContext *s, uint8_t style_flags)
434 {
435     if (!s->style_attributes_temp ||
436         !((s->style_attributes_temp->style_flag & style_flags) ^ style_flags)) {
437         // setting flags that that are already set
438         return;
439     }
440     if (mov_text_style_start(s))
441         s->style_attributes_temp->style_flag |= style_flags;
442 }
443
444 static void mov_text_style_cb(void *priv, const char style, int close)
445 {
446     MovTextContext *s = priv;
447     uint8_t style_flag = mov_text_style_to_flag(style);
448
449     if (!s->style_attributes_temp ||
450         !!(s->style_attributes_temp->style_flag & style_flag) != close) {
451         // setting flag that is already set
452         return;
453     }
454     if (mov_text_style_start(s)) {
455         if (!close)
456             s->style_attributes_temp->style_flag |= style_flag;
457         else
458             s->style_attributes_temp->style_flag &= ~style_flag;
459     }
460 }
461
462 static void mov_text_color_set(MovTextContext *s, uint32_t color)
463 {
464     if (!s->style_attributes_temp ||
465         (s->style_attributes_temp->style_color & 0xffffff00) == color) {
466         // color hasn't changed
467         return;
468     }
469     if (mov_text_style_start(s))
470         s->style_attributes_temp->style_color = (color & 0xffffff00) |
471                             (s->style_attributes_temp->style_color & 0xff);
472 }
473
474 static void mov_text_color_cb(void *priv, unsigned int color, unsigned int color_id)
475 {
476     MovTextContext *s = priv;
477
478     color = BGR_TO_RGB(color) << 8;
479     if (color_id == 1) {    //primary color changes
480         mov_text_color_set(s, color);
481     } else if (color_id == 2) {    //secondary color changes
482         if (!(s->box_flags & HCLR_BOX))
483             // Highlight alpha not set yet, use current primary alpha
484             s->hclr.color = s->style_attributes_temp->style_color;
485         if (!(s->box_flags & HLIT_BOX) || s->hlit.start == s->text_pos) {
486             s->box_flags |= HCLR_BOX;
487             s->box_flags |= HLIT_BOX;
488             s->hlit.start = s->text_pos;
489             s->hclr.color = color | (s->hclr.color & 0xFF);
490         }
491         else //close tag
492             s->hlit.end = s->text_pos;
493         /* If there are more than one secondary color changes in ASS,
494            take start of first section and end of last section. Movtext
495            allows only one highlight box per sample.
496          */
497     }
498     // Movtext does not support changes to other color_id (outline, background)
499 }
500
501 static void mov_text_alpha_set(MovTextContext *s, uint8_t alpha)
502 {
503     if (!s->style_attributes_temp ||
504         (s->style_attributes_temp->style_color & 0xff) == alpha) {
505         // color hasn't changed
506         return;
507     }
508     if (mov_text_style_start(s))
509         s->style_attributes_temp->style_color =
510                 (s->style_attributes_temp->style_color & 0xffffff00) | alpha;
511 }
512
513 static void mov_text_alpha_cb(void *priv, int alpha, int alpha_id)
514 {
515     MovTextContext *s = priv;
516
517     alpha = 255 - alpha;
518     if (alpha_id == 1) // primary alpha changes
519         mov_text_alpha_set(s, alpha);
520     else if (alpha_id == 2) {    //secondary alpha changes
521         if (!(s->box_flags & HCLR_BOX))
522             // Highlight color not set yet, use current primary color
523             s->hclr.color = s->style_attributes_temp->style_color;
524         if (!(s->box_flags & HLIT_BOX) || s->hlit.start == s->text_pos) {
525             s->box_flags |= HCLR_BOX;
526             s->box_flags |= HLIT_BOX;
527             s->hlit.start = s->text_pos;
528             s->hclr.color = (s->hclr.color & 0xffffff00) | alpha;
529         }
530         else //close tag
531             s->hlit.end = s->text_pos;
532     }
533     // Movtext does not support changes to other alpha_id (outline, background)
534 }
535
536 static uint16_t find_font_id(MovTextContext * s, const char * name)
537 {
538     int i;
539     for (i = 0; i < s->font_count; i++) {
540         if (!strcmp(name, s->fonts[i]))
541             return i + 1;
542     }
543     return 1;
544 }
545
546 static void mov_text_font_name_set(MovTextContext *s, const char *name)
547 {
548     int fontID = find_font_id(s, name);
549     if (!s->style_attributes_temp ||
550         s->style_attributes_temp->style_fontID == fontID) {
551         // color hasn't changed
552         return;
553     }
554     if (mov_text_style_start(s))
555         s->style_attributes_temp->style_fontID = fontID;
556 }
557
558 static void mov_text_font_name_cb(void *priv, const char *name)
559 {
560     mov_text_font_name_set((MovTextContext*)priv, name);
561 }
562
563 static void mov_text_font_size_set(MovTextContext *s, int size)
564 {
565     size = FONTSIZE_SCALE(s, size);
566     if (!s->style_attributes_temp ||
567         s->style_attributes_temp->style_fontsize == size) {
568         // color hasn't changed
569         return;
570     }
571     if (mov_text_style_start(s))
572         s->style_attributes_temp->style_fontsize = size;
573 }
574
575 static void mov_text_font_size_cb(void *priv, int size)
576 {
577     mov_text_font_size_set((MovTextContext*)priv, size);
578 }
579
580 static void mov_text_end_cb(void *priv)
581 {
582     // End of text, close any open style record
583     mov_text_style_start((MovTextContext*)priv);
584 }
585
586 static void mov_text_ass_style_set(MovTextContext *s, ASSStyle *style)
587 {
588     uint8_t    style_flags, alpha;
589     uint32_t   color;
590
591     if (style) {
592         style_flags = (!!style->bold      * STYLE_FLAG_BOLD)   |
593                       (!!style->italic    * STYLE_FLAG_ITALIC) |
594                       (!!style->underline * STYLE_FLAG_UNDERLINE);
595         mov_text_style_set(s, style_flags);
596         color = BGR_TO_RGB(style->primary_color & 0xffffff) << 8;
597         mov_text_color_set(s, color);
598         alpha = 255 - ((uint32_t)style->primary_color >> 24);
599         mov_text_alpha_set(s, alpha);
600         mov_text_font_size_set(s, style->font_size);
601         mov_text_font_name_set(s, style->font_name);
602     } else {
603         // End current style record, go back to defaults
604         mov_text_style_start(s);
605     }
606 }
607
608 static void mov_text_dialog(MovTextContext *s, ASSDialog *dialog)
609 {
610     ASSStyle * style = ff_ass_style_get(s->ass_ctx, dialog->style);
611
612     s->ass_dialog_style = style;
613     mov_text_ass_style_set(s, style);
614 }
615
616 static void mov_text_cancel_overrides_cb(void *priv, const char * style_name)
617 {
618     MovTextContext *s = priv;
619     ASSStyle * style;
620
621     if (!style_name || !*style_name)
622         style = s->ass_dialog_style;
623     else
624         style= ff_ass_style_get(s->ass_ctx, style_name);
625
626     mov_text_ass_style_set(s, style);
627 }
628
629 static uint16_t utf8_strlen(const char *text, int len)
630 {
631     uint16_t i = 0, ret = 0;
632     while (i < len) {
633         char c = text[i];
634         if ((c & 0x80) == 0)
635             i += 1;
636         else if ((c & 0xE0) == 0xC0)
637             i += 2;
638         else if ((c & 0xF0) == 0xE0)
639             i += 3;
640         else if ((c & 0xF8) == 0xF0)
641             i += 4;
642         else
643             return 0;
644         ret++;
645     }
646     return ret;
647 }
648
649 static void mov_text_text_cb(void *priv, const char *text, int len)
650 {
651     uint16_t utf8_len = utf8_strlen(text, len);
652     MovTextContext *s = priv;
653     av_bprint_append_data(&s->buffer, text, len);
654     // If it's not utf-8, just use the byte length
655     s->text_pos += utf8_len ? utf8_len : len;
656     s->byte_count += len;
657 }
658
659 static void mov_text_new_line_cb(void *priv, int forced)
660 {
661     MovTextContext *s = priv;
662     av_bprint_append_data(&s->buffer, "\n", 1);
663     s->text_pos += 1;
664     s->byte_count += 1;
665 }
666
667 static const ASSCodesCallbacks mov_text_callbacks = {
668     .text             = mov_text_text_cb,
669     .new_line         = mov_text_new_line_cb,
670     .style            = mov_text_style_cb,
671     .color            = mov_text_color_cb,
672     .alpha            = mov_text_alpha_cb,
673     .font_name        = mov_text_font_name_cb,
674     .font_size        = mov_text_font_size_cb,
675     .cancel_overrides = mov_text_cancel_overrides_cb,
676     .end              = mov_text_end_cb,
677 };
678
679 static int mov_text_encode_frame(AVCodecContext *avctx, unsigned char *buf,
680                                  int bufsize, const AVSubtitle *sub)
681 {
682     MovTextContext *s = avctx->priv_data;
683     ASSDialog *dialog;
684     int i, length;
685     size_t j;
686
687     s->byte_count = 0;
688     s->text_pos = 0;
689     s->count = 0;
690     s->box_flags = 0;
691     for (i = 0; i < sub->num_rects; i++) {
692         const char *ass = sub->rects[i]->ass;
693
694         if (sub->rects[i]->type != SUBTITLE_ASS) {
695             av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n");
696             return AVERROR(EINVAL);
697         }
698
699 #if FF_API_ASS_TIMING
700         if (!strncmp(ass, "Dialogue: ", 10)) {
701             int num;
702             dialog = ff_ass_split_dialog(s->ass_ctx, ass, 0, &num);
703             for (; dialog && num--; dialog++) {
704                 mov_text_dialog(s, dialog);
705                 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
706             }
707         } else {
708 #endif
709             dialog = ff_ass_split_dialog2(s->ass_ctx, ass);
710             if (!dialog)
711                 return AVERROR(ENOMEM);
712             mov_text_dialog(s, dialog);
713             ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
714             ff_ass_free_dialog(&dialog);
715 #if FF_API_ASS_TIMING
716         }
717 #endif
718
719         for (j = 0; j < box_count; j++) {
720             box_types[j].encode(s, box_types[j].type);
721         }
722     }
723
724     AV_WB16(buf, s->byte_count);
725     buf += 2;
726
727     if (!av_bprint_is_complete(&s->buffer)) {
728         length = AVERROR(ENOMEM);
729         goto exit;
730     }
731
732     if (!s->buffer.len) {
733         length = 0;
734         goto exit;
735     }
736
737     if (s->buffer.len > bufsize - 3) {
738         av_log(avctx, AV_LOG_ERROR, "Buffer too small for ASS event.\n");
739         length = AVERROR_BUFFER_TOO_SMALL;
740         goto exit;
741     }
742
743     memcpy(buf, s->buffer.str, s->buffer.len);
744     length = s->buffer.len + 2;
745
746 exit:
747     av_bprint_clear(&s->buffer);
748     return length;
749 }
750
751 #define OFFSET(x) offsetof(MovTextContext, x)
752 #define FLAGS AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_SUBTITLE_PARAM
753 static const AVOption options[] = {
754     { "height", "Frame height, usually video height", OFFSET(frame_height), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS },
755     { NULL },
756 };
757
758 static const AVClass mov_text_encoder_class = {
759     .class_name = "MOV text enoder",
760     .item_name  = av_default_item_name,
761     .option     = options,
762     .version    = LIBAVUTIL_VERSION_INT,
763 };
764
765 AVCodec ff_movtext_encoder = {
766     .name           = "mov_text",
767     .long_name      = NULL_IF_CONFIG_SMALL("3GPP Timed Text subtitle"),
768     .type           = AVMEDIA_TYPE_SUBTITLE,
769     .id             = AV_CODEC_ID_MOV_TEXT,
770     .priv_data_size = sizeof(MovTextContext),
771     .priv_class     = &mov_text_encoder_class,
772     .init           = mov_text_encode_init,
773     .encode_sub     = mov_text_encode_frame,
774     .close          = mov_text_encode_close,
775 };