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