Coverage Report

Created: 2025-10-10 06:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vlc/modules/codec/stl.c
Line
Count
Source
1
/*****************************************************************************
2
 * stl.c: EBU STL decoder
3
 *****************************************************************************
4
 * Copyright (C) 2010 Laurent Aimar
5
 *
6
 * Authors: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
7
 *
8
 * This program is free software; you can redistribute it and/or modify it
9
 * under the terms of the GNU Lesser General Public License as published by
10
 * the Free Software Foundation; either version 2.1 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
 * GNU Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public License
19
 * along with this program; if not, write to the Free Software Foundation,
20
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21
 *****************************************************************************/
22
23
/*****************************************************************************
24
 * Preamble
25
 *****************************************************************************/
26
#ifdef HAVE_CONFIG_H
27
# include "config.h"
28
#endif
29
#include <assert.h>
30
31
#include <vlc_common.h>
32
#include <vlc_plugin.h>
33
#include <vlc_codec.h>
34
#include <vlc_charset.h>
35
36
#include "substext.h" /* required for font scaling / updater */
37
38
/*****************************************************************************
39
 * Module descriptor
40
 *****************************************************************************/
41
static int  Open (vlc_object_t *);
42
static void Close(vlc_object_t *);
43
44
108
vlc_module_begin()
45
54
    set_description(N_("EBU STL subtitles decoder"))
46
54
    set_subcategory(SUBCAT_INPUT_SCODEC)
47
54
    set_capability("spu decoder", 10)
48
108
    set_callbacks(Open, Close)
49
54
vlc_module_end()
50
51
/*****************************************************************************
52
 * Local definitions/prototypes
53
 *****************************************************************************/
54
0
#define GSI_BLOCK_SIZE 1024
55
56
0
#define STL_GROUPS_MAX 255
57
58
0
#define STL_TEXTFIELD_SIZE     112
59
0
#define STL_TTI_HEADER_SIZE    16
60
0
#define STL_TTI_SIZE           (STL_TTI_HEADER_SIZE + STL_TEXTFIELD_SIZE)
61
62
#define STL_TF_TELETEXT_FIRST     0x00
63
0
#define STL_TF_TELETEXT_LAST      0x1f
64
0
#define STL_TF_CHARCODE1_FIRST    0x20
65
0
#define STL_TF_CHARCODE1_LAST     0x7f
66
0
#define STL_TF_ITALICS_ON         0x80
67
0
#define STL_TF_ITALICS_OFF        0x81
68
0
#define STL_TF_UNDERLINE_ON       0x82
69
0
#define STL_TF_UNDERLINE_OFF      0x83
70
0
#define STL_TF_BOXING_ON          0x84
71
0
#define STL_TF_BOXING_OFF         0x85
72
0
#define STL_TF_LINEBREAK          0x8a
73
0
#define STL_TF_END_FILL           0x8f
74
0
#define STL_TF_CHARCODE2_FIRST    0xa1
75
76
typedef enum {
77
    CCT_ISO_6937_2 = 0x3030, CCT_BEGIN = CCT_ISO_6937_2,
78
    CCT_ISO_8859_5 = 0x3031,
79
    CCT_ISO_8859_6 = 0x3032,
80
    CCT_ISO_8859_7 = 0x3033,
81
    CCT_ISO_8859_8 = 0x3034, CCT_END = CCT_ISO_8859_8
82
} cct_number_value_t;
83
84
typedef struct
85
{
86
    uint8_t i_accumulating;
87
    uint8_t i_justify;
88
    vlc_tick_t i_start;
89
    vlc_tick_t i_end;
90
    text_style_t *p_style;
91
    text_segment_t *p_segment;
92
    text_segment_t **pp_segment_last;
93
} stl_sg_t;
94
95
typedef struct {
96
    cct_number_value_t value;
97
    const char *str;
98
} cct_number_t;
99
100
typedef struct
101
{
102
    stl_sg_t groups[STL_GROUPS_MAX + 1];
103
    cct_number_value_t cct;
104
    uint8_t i_fps;
105
} decoder_sys_t;
106
107
static cct_number_t cct_nums[] = { {CCT_ISO_6937_2, "ISO_6937-2"},
108
                                   {CCT_ISO_8859_5, "ISO_8859-5"},
109
                                   {CCT_ISO_8859_6, "ISO_8859-6"},
110
                                   {CCT_ISO_8859_7, "ISO_8859-7"},
111
                                   {CCT_ISO_8859_8, "ISO_8859-8"} };
112
113
static text_style_t * CreateGroupStyle(void)
114
0
{
115
0
    text_style_t *p_style = text_style_Create(STYLE_NO_DEFAULTS);
116
0
    if(p_style)
117
0
    {
118
0
        p_style->i_features = STYLE_HAS_FLAGS|STYLE_HAS_BACKGROUND_ALPHA|STYLE_HAS_BACKGROUND_COLOR;
119
        /* Teletext needs default background to black */
120
0
        p_style->i_background_alpha = STYLE_ALPHA_OPAQUE;
121
0
        p_style->i_background_color = 0x000000;
122
0
        p_style->i_font_size = 0;
123
0
        p_style->f_font_relsize = STYLE_DEFAULT_REL_FONT_SIZE;
124
0
    }
125
0
    return p_style;
126
0
}
127
128
static void TextBufferFlush(stl_sg_t *p_group, uint8_t *p_buf, uint8_t *pi_buf,
129
                            const char *psz_charset)
130
0
{
131
0
    if(*pi_buf == 0)
132
0
        return;
133
134
0
    char *psz_utf8 = FromCharset(psz_charset, p_buf, *pi_buf);
135
0
    if(psz_utf8)
136
0
    {
137
0
        *p_group->pp_segment_last = text_segment_New(psz_utf8);
138
0
        if(*p_group->pp_segment_last)
139
0
        {
140
0
            if(p_group->p_style)
141
0
                (*p_group->pp_segment_last)->style = text_style_Duplicate(p_group->p_style);
142
0
            p_group->pp_segment_last = &((*p_group->pp_segment_last)->p_next);
143
0
        }
144
0
        free(psz_utf8);
145
0
    }
146
147
0
    *pi_buf = 0;
148
0
}
149
150
static void GroupParseTeletext(stl_sg_t *p_group, uint8_t code)
151
0
{
152
0
    if(p_group->p_style == NULL &&
153
0
      !(p_group->p_style = CreateGroupStyle()))
154
0
        return;
155
156
    /* See ETS 300 706 Table 26 as EBU 3264 does only name values
157
       and does not explain at all */
158
159
0
    static const uint32_t colors[] =
160
0
    {
161
0
        0x000000,
162
0
        0xFF0000,
163
0
        0x00FF00,
164
0
        0xFFFF00,
165
0
        0x0000FF,
166
0
        0xFF00FF,
167
0
        0x00FFFF,
168
0
        0xFFFFFF,
169
0
    };
170
171
    /* Teletext data received, so we need to enable background */
172
0
    p_group->p_style->i_style_flags |= STYLE_BACKGROUND;
173
174
0
    switch(code)
175
0
    {
176
0
        case 0x0c:
177
0
            p_group->p_style->f_font_relsize = STYLE_DEFAULT_REL_FONT_SIZE;
178
0
            p_group->p_style->i_style_flags &= ~(STYLE_DOUBLEWIDTH|STYLE_HALFWIDTH);
179
0
            break;
180
181
0
        case 0x0d: /* double height */
182
0
            p_group->p_style->f_font_relsize = STYLE_DEFAULT_REL_FONT_SIZE * 2;
183
0
            p_group->p_style->i_style_flags &= ~STYLE_DOUBLEWIDTH;
184
0
            p_group->p_style->i_style_flags |= STYLE_HALFWIDTH;
185
0
            break;
186
187
0
        case 0x0e: /* double width */
188
0
            p_group->p_style->f_font_relsize = STYLE_DEFAULT_REL_FONT_SIZE;
189
0
            p_group->p_style->i_style_flags &= ~STYLE_HALFWIDTH;
190
0
            p_group->p_style->i_style_flags |= STYLE_DOUBLEWIDTH;
191
0
            break;
192
193
0
        case 0x0f: /* double size */
194
0
            p_group->p_style->f_font_relsize = STYLE_DEFAULT_REL_FONT_SIZE * 2;
195
0
            p_group->p_style->i_style_flags &= ~(STYLE_DOUBLEWIDTH|STYLE_HALFWIDTH);
196
0
            break;
197
198
0
        case 0x1d:
199
0
            p_group->p_style->i_background_color = p_group->p_style->i_font_color;
200
0
            p_group->p_style->i_features &= ~STYLE_HAS_FONT_COLOR;
201
0
            break;
202
203
0
        case 0x1c:
204
0
            p_group->p_style->i_background_color = colors[0];
205
0
            break;
206
207
0
        default:
208
0
            if(code < 8)
209
0
            {
210
0
                p_group->p_style->i_font_color = colors[code];
211
0
                p_group->p_style->i_features |= STYLE_HAS_FONT_COLOR;
212
0
            }
213
214
            /* Need to handle Mosaic ? Really ? */
215
0
            break;
216
0
    }
217
218
0
}
219
220
static void GroupApplyStyle(stl_sg_t *p_group, uint8_t code)
221
0
{
222
0
    if(p_group->p_style == NULL &&
223
0
      !(p_group->p_style = CreateGroupStyle()))
224
0
        return;
225
226
0
    switch(code)
227
0
    {
228
0
        case STL_TF_ITALICS_ON:
229
0
            p_group->p_style->i_style_flags |= STYLE_ITALIC;
230
0
            break;
231
0
        case STL_TF_ITALICS_OFF:
232
0
            p_group->p_style->i_style_flags &= ~STYLE_ITALIC;
233
0
            break;
234
0
        case STL_TF_UNDERLINE_ON:
235
0
            p_group->p_style->i_style_flags |= STYLE_UNDERLINE;
236
0
            break;
237
0
        case STL_TF_UNDERLINE_OFF:
238
0
            p_group->p_style->i_style_flags &= ~STYLE_UNDERLINE;
239
0
            break;
240
0
        case STL_TF_BOXING_ON:
241
0
        case STL_TF_BOXING_OFF:
242
0
        default:
243
0
            break;
244
0
    }
245
0
}
246
247
static vlc_tick_t ParseTimeCode(const uint8_t *data, double fps)
248
0
{
249
0
    return vlc_tick_from_sec( data[0] * 3600 +
250
0
                         data[1] *   60 +
251
0
                         data[2] *    1 +
252
0
                         data[3] /  fps);
253
0
}
254
255
static void ClearTeletextStyles(stl_sg_t *p_group)
256
0
{
257
0
    if(p_group->p_style)
258
0
    {
259
0
        p_group->p_style->i_features &= ~STYLE_HAS_FONT_COLOR;
260
0
        p_group->p_style->i_background_color = 0x000000;
261
0
        p_group->p_style->f_font_relsize = STYLE_DEFAULT_REL_FONT_SIZE;
262
0
        p_group->p_style->i_style_flags &= ~(STYLE_DOUBLEWIDTH|STYLE_HALFWIDTH);
263
0
    }
264
0
}
265
266
/* Returns true if group is we need to output group */
267
static bool ParseTTI(stl_sg_t *p_group, const uint8_t *p_data, const char *psz_charset, double fps)
268
0
{
269
0
    uint8_t p_buffer[STL_TEXTFIELD_SIZE];
270
0
    uint8_t i_buffer = 0;
271
272
    /* Header */
273
0
    uint8_t ebn = p_data[3];
274
0
    if(ebn > 0xef && ebn != 0xff)
275
0
        return false;
276
277
0
    if(p_data[15] != 0x00) /* comment flag */
278
0
        return false;
279
280
0
    if(p_data[14] > 0x00)
281
0
        p_group->i_justify = p_data[14];
282
283
    /* Accumulating started or continuing.
284
     * We must not flush current segments on output and continue on next block */
285
0
    p_group->i_accumulating = (p_data[4] == 0x01 || p_data[4] == 0x02);
286
287
0
    p_group->i_start = ParseTimeCode( &p_data[5], fps );
288
0
    p_group->i_end = ParseTimeCode( &p_data[9], fps );
289
290
    /* Text Field */
291
0
    for (size_t i = STL_TTI_HEADER_SIZE; i < STL_TTI_SIZE; i++)
292
0
    {
293
0
        const uint8_t code = p_data[i];
294
0
        switch(code)
295
0
        {
296
0
            case STL_TF_LINEBREAK:
297
0
                p_buffer[i_buffer++] = '\n';
298
0
                TextBufferFlush(p_group, p_buffer, &i_buffer, psz_charset);
299
                /* Clear teletext styles on each new row */
300
0
                ClearTeletextStyles(p_group);
301
0
                break;
302
303
0
            case STL_TF_END_FILL:
304
0
                TextBufferFlush(p_group, p_buffer, &i_buffer, psz_charset);
305
0
                ClearTeletextStyles(p_group);
306
0
                return true;
307
308
0
            default:
309
0
                if(code <= STL_TF_TELETEXT_LAST)
310
0
                {
311
0
                    TextBufferFlush(p_group, p_buffer, &i_buffer, psz_charset);
312
0
                    GroupParseTeletext(p_group, code);
313
0
                }
314
0
                else if((code >= STL_TF_CHARCODE1_FIRST && code <= STL_TF_CHARCODE1_LAST) ||
315
0
                    code >= STL_TF_CHARCODE2_FIRST)
316
0
                {
317
0
                    p_buffer[i_buffer++] = code;
318
0
                }
319
0
                else if(code >= STL_TF_ITALICS_ON && code <= STL_TF_BOXING_OFF)
320
0
                {
321
0
                    TextBufferFlush(p_group, p_buffer, &i_buffer, psz_charset);
322
0
                    GroupApplyStyle(p_group, code);
323
0
                }
324
0
                break;
325
0
        }
326
0
    }
327
328
0
    TextBufferFlush(p_group, p_buffer, &i_buffer, psz_charset);
329
330
0
    return false;
331
0
}
332
333
static void FillSubpictureUpdater(stl_sg_t *p_group, subtext_updater_sys_t *p_spu_sys)
334
0
{
335
0
    if(p_group->i_accumulating)
336
0
    {
337
0
        p_spu_sys->region.p_segments = text_segment_Copy(p_group->p_segment);
338
0
    }
339
0
    else
340
0
    {
341
0
        p_spu_sys->region.p_segments = p_group->p_segment;
342
0
        p_group->p_segment = NULL;
343
0
        p_group->pp_segment_last = &p_group->p_segment;
344
0
    }
345
346
0
    p_spu_sys->region.b_absolute = false;
347
0
    p_spu_sys->region.b_in_window = false;
348
0
    p_spu_sys->region.align = SUBPICTURE_ALIGN_BOTTOM;
349
0
    if(p_group->i_justify == 0x01)
350
0
        p_spu_sys->region.inner_align = SUBPICTURE_ALIGN_LEFT;
351
0
    else if(p_group->i_justify == 0x03)
352
0
        p_spu_sys->region.inner_align = SUBPICTURE_ALIGN_RIGHT;
353
0
}
354
355
static void ResetGroups(decoder_sys_t *p_sys)
356
0
{
357
0
    for(size_t i=0; i<=STL_GROUPS_MAX; i++)
358
0
    {
359
0
        stl_sg_t *p_group = &p_sys->groups[i];
360
0
        if(p_group->p_segment)
361
0
        {
362
0
            text_segment_ChainDelete(p_group->p_segment);
363
0
            p_group->p_segment = NULL;
364
0
            p_group->pp_segment_last = &p_group->p_segment;
365
0
        }
366
367
0
        if(p_group->p_style)
368
0
        {
369
0
            text_style_Delete(p_group->p_style);
370
0
            p_group->p_style = NULL;
371
0
        }
372
373
0
        p_group->i_accumulating = false;
374
0
        p_group->i_end = VLC_TICK_INVALID;
375
0
        p_group->i_start = VLC_TICK_INVALID;
376
0
        p_group->i_justify = 0;
377
0
    }
378
0
}
379
380
static int Decode(decoder_t *p_dec, block_t *p_block)
381
0
{
382
0
    if (p_block == NULL) /* No Drain */
383
0
        return VLCDEC_SUCCESS;
384
385
0
    decoder_sys_t *p_sys = p_dec->p_sys;
386
387
0
    if(p_block->i_buffer < STL_TTI_SIZE)
388
0
        p_block->i_flags |= BLOCK_FLAG_CORRUPTED;
389
390
0
    if(p_block->i_flags & (BLOCK_FLAG_CORRUPTED|BLOCK_FLAG_DISCONTINUITY))
391
0
    {
392
0
        ResetGroups(p_dec->p_sys);
393
394
0
        if(p_block->i_flags & BLOCK_FLAG_CORRUPTED)
395
0
        {
396
0
            block_Release(p_block);
397
0
            return VLCDEC_SUCCESS;
398
0
        }
399
0
    }
400
401
0
    const char *psz_charset = cct_nums[p_sys->cct - CCT_BEGIN].str;
402
0
    for (size_t i = 0; i < p_block->i_buffer / STL_TTI_SIZE; i++)
403
0
    {
404
0
        stl_sg_t *p_group = &p_sys->groups[p_block->p_buffer[0]];
405
0
        if(ParseTTI(p_group, &p_block->p_buffer[i * STL_TTI_SIZE], psz_charset, p_sys->i_fps) &&
406
0
           p_group->p_segment != NULL )
407
0
        {
408
            /* output */
409
0
            subpicture_t *p_sub = decoder_NewSubpictureText(p_dec);
410
0
            if( p_sub )
411
0
            {
412
0
                FillSubpictureUpdater(p_group, p_sub->updater.sys );
413
414
0
                if(p_group->i_end != VLC_TICK_INVALID && p_group->i_start >= p_block->i_dts)
415
0
                {
416
0
                    p_sub->i_start = VLC_TICK_0 + p_group->i_start;
417
0
                    p_sub->i_stop =  VLC_TICK_0 + p_group->i_end;
418
0
                }
419
0
                else
420
0
                {
421
0
                    p_sub->i_start    = p_block->i_pts;
422
0
                    p_sub->i_stop     = p_block->i_pts + p_block->i_length;
423
0
                    p_sub->b_ephemer  = (p_block->i_length == 0);
424
0
                }
425
0
                decoder_QueueSub(p_dec, p_sub);
426
0
            }
427
0
        }
428
0
    }
429
430
0
    ResetGroups(p_sys);
431
432
0
    block_Release(p_block);
433
0
    return VLCDEC_SUCCESS;
434
0
}
435
436
static int ParseGSI(decoder_t *dec, decoder_sys_t *p_sys)
437
0
{
438
0
    uint8_t *header = dec->fmt_in->p_extra;
439
0
    if (!header) {
440
0
        msg_Err(dec, "NULL EBU header (GSI block)\n");
441
0
        return VLC_EGENERIC;
442
0
    }
443
444
0
    if (GSI_BLOCK_SIZE != dec->fmt_in->i_extra) {
445
0
        msg_Err(dec, "EBU header is not in expected size (%zu)\n", dec->fmt_in->i_extra);
446
0
        return VLC_EGENERIC;
447
0
    }
448
449
0
    char dfc_fps_str[] = { header[6], header[7], '\0' };
450
0
    int fps = strtol(dfc_fps_str, NULL, 10);
451
0
    if (1 > fps || 60 < fps) {
452
0
        msg_Warn(dec, "EBU header contains unsupported DFC fps ('%s'); falling back to 25\n", dfc_fps_str);
453
0
        fps = 25;
454
0
    }
455
456
0
    int cct = (header[12] << 8) | header[13];
457
0
    if (CCT_BEGIN > cct || CCT_END < cct) {
458
0
        msg_Err(dec, "EBU header contains illegal CCT (0x%x)\n", cct);
459
0
        return VLC_EGENERIC;
460
0
    }
461
462
0
    msg_Dbg(dec, "DFC fps=%d, CCT=0x%x", fps, cct);
463
0
    p_sys->i_fps = fps;
464
0
    p_sys->cct = cct;
465
466
0
    return VLC_SUCCESS;
467
0
}
468
469
static int Open(vlc_object_t *object)
470
390
{
471
390
    decoder_t *dec = (decoder_t*)object;
472
473
390
    if (dec->fmt_in->i_codec != VLC_CODEC_EBU_STL)
474
390
        return VLC_EGENERIC;
475
476
0
    decoder_sys_t *sys = calloc(1, sizeof(*sys));
477
0
    if (!sys)
478
0
        return VLC_ENOMEM;
479
480
0
    int rc = ParseGSI(dec, sys);
481
0
    if (VLC_SUCCESS != rc) {
482
0
        free(sys);
483
0
        return rc;
484
0
    }
485
486
0
    for(size_t i=0; i<=STL_GROUPS_MAX; i++)
487
0
        sys->groups[i].pp_segment_last = &sys->groups[i].p_segment;
488
489
0
    dec->p_sys = sys;
490
0
    dec->pf_decode = Decode;
491
0
    dec->fmt_out.i_codec = 0;
492
0
    return VLC_SUCCESS;
493
0
}
494
495
static void Close(vlc_object_t *object)
496
0
{
497
0
    decoder_t *dec = (decoder_t*)object;
498
0
    decoder_sys_t *p_sys = dec->p_sys;
499
500
0
    ResetGroups(p_sys);
501
0
    free(p_sys);
502
0
}