GCC Code Coverage Report
Directory: ../../../ffmpeg/ Exec Total Coverage
File: src/libavcodec/movtextenc.c Lines: 72 194 37.1 %
Date: 2019-11-18 18:00:01 Branches: 19 76 25.0 %

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