Coverage Report

Created: 2025-11-24 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vlc/modules/codec/ttml/encttml.c
Line
Count
Source
1
/*****************************************************************************
2
 * encttml.c : TTML encoder
3
 *****************************************************************************
4
 * Copyright (C) 2018-2024 VideoLabs, VLC authors and VideoLAN
5
 *
6
 * This program is free software; you can redistribute it and/or modify it
7
 * under the terms of the GNU Lesser General Public License as published by
8
 * the Free Software Foundation; either version 2.1 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public License
17
 * along with this program; if not, write to the Free Software Foundation,
18
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19
 *****************************************************************************/
20
21
#ifdef HAVE_CONFIG_H
22
# include "config.h"
23
#endif
24
25
#include <vlc_common.h>
26
#include <vlc_plugin.h>
27
#include <vlc_codec.h>
28
#include <vlc_subpicture.h>
29
30
#include "ttml.h"
31
32
#define HEX_COLOR_MAX 10
33
static void FillHexColor( uint32_t argb, bool withalpha, char text[HEX_COLOR_MAX] )
34
0
{
35
0
    if( withalpha )
36
0
        snprintf( text, HEX_COLOR_MAX, "#%08x", (argb << 8) | (argb >> 24) );
37
0
    else
38
0
        snprintf( text, HEX_COLOR_MAX, "#%06x", argb & 0x00FFFFFF );
39
0
}
40
41
static void AddTextNode( tt_node_t *p_parent, const char *psz_text )
42
0
{
43
0
    const char *psz = psz_text;
44
0
    const char *nl;
45
0
    do
46
0
    {
47
0
        nl = strchr( psz, '\n' );
48
0
        if( nl )
49
0
        {
50
0
            tt_subtextnode_New( p_parent, psz, nl - psz );
51
0
            tt_node_New( p_parent, "br", NULL );
52
0
            psz += nl - psz + 1;
53
0
            if( *psz == '\0' )
54
0
                break;
55
0
        }
56
0
        else
57
0
        {
58
0
            tt_textnode_New( p_parent, psz );
59
0
        }
60
0
    } while ( nl );
61
0
}
62
63
static block_t *Encode( encoder_t *p_enc, subpicture_t *p_spu )
64
0
{
65
0
    VLC_UNUSED( p_enc );
66
67
0
    if( p_spu == NULL )
68
0
        return NULL;
69
70
0
    tt_node_t *p_root = tt_node_New( NULL, "tt", TT_NS );
71
0
    if( !p_root )
72
0
        return NULL;
73
74
0
    tt_node_AddAttribute( p_root, "xmlns", TT_NS );
75
0
    tt_node_AddAttribute( p_root, "xmlns:tts", TT_NS_STYLING );
76
77
0
    tt_node_t *p_body = tt_node_New( p_root, "body", NULL );
78
0
    if( !p_body )
79
0
    {
80
0
        tt_node_RecursiveDelete( p_root );
81
0
        return NULL;
82
0
    }
83
84
0
    tt_node_t *p_div = tt_node_New( p_body, "div", NULL );
85
0
    if( !p_div )
86
0
    {
87
0
        tt_node_RecursiveDelete( p_root );
88
0
        return NULL;
89
0
    }
90
91
0
    subpicture_region_t *p_region;
92
0
    vlc_spu_regions_foreach(p_region, &p_spu->regions)
93
0
    {
94
0
        if( !subpicture_region_IsText( p_region ) ||
95
0
            p_region->p_text == NULL ||
96
0
            p_region->p_text->psz_text == NULL )
97
0
            continue;
98
99
0
        tt_node_t *p_par = tt_node_New( p_div, "p", NULL );
100
0
        if( !p_par )
101
0
            continue;
102
103
0
        if( p_spu->i_start != VLC_TICK_INVALID )
104
0
        {
105
0
            p_par->timings.begin = tt_time_Create( p_spu->i_start - VLC_TICK_0 );
106
0
            if( p_spu->i_stop != VLC_TICK_INVALID &&  p_spu->i_stop > p_spu->i_start )
107
0
                p_par->timings.end = tt_time_Create( p_spu->i_stop - VLC_TICK_0 );
108
0
            tt_node_AddAttribute( p_par, "begin", "" );
109
0
        }
110
111
0
        for( const text_segment_t *p_segment = p_region->p_text;
112
0
             p_segment; p_segment = p_segment->p_next )
113
0
        {
114
0
            if( p_segment->psz_text == NULL )
115
0
                continue;
116
117
0
            const text_style_t *style = p_segment->style;
118
0
            if( style && style->i_features )
119
0
            {
120
0
                tt_node_t *p_span = tt_node_New( p_par, "span", NULL );
121
0
                if( !p_span )
122
0
                    continue;
123
124
0
                if( style->f_font_relsize && p_spu->i_original_picture_height )
125
0
                {
126
0
                    char fontsize[10];
127
0
                    unsigned relem = p_spu->i_original_picture_height * style->f_font_relsize / 16;
128
0
                    snprintf( fontsize, 10, "%u%%", relem );
129
0
                    tt_node_AddAttribute( p_span, "tts:fontSize", fontsize );
130
0
                }
131
0
                else if ( style->i_font_size )
132
0
                {
133
0
                    char fontsize[10];
134
0
                    snprintf( fontsize, 10, "%upx", style->i_font_size );
135
0
                    tt_node_AddAttribute( p_span, "tts:fontSize", fontsize );
136
0
                }
137
138
0
                if( style->psz_fontname )
139
0
                    tt_node_AddAttribute( p_span, "tts:fontFamily", style->psz_fontname );
140
141
0
                if( style->i_features & STYLE_HAS_FLAGS )
142
0
                {
143
0
                    if( style->i_style_flags & STYLE_BOLD )
144
0
                        tt_node_AddAttribute( p_span, "tts:fontWeight", "bold" );
145
0
                    if( style->i_style_flags & STYLE_ITALIC )
146
0
                        tt_node_AddAttribute( p_span, "tts:fontStyle", "italic" );
147
0
                    if( style->i_style_flags & STYLE_UNDERLINE )
148
0
                        tt_node_AddAttribute( p_span, "tts:textDecoration", "underline" );
149
0
                    if( style->i_style_flags & STYLE_STRIKEOUT )
150
0
                        tt_node_AddAttribute( p_span, "tts:textDecoration", "lineThrough" );
151
0
                    if( style->i_style_flags & STYLE_OUTLINE )
152
0
                    {
153
0
                        char color[HEX_COLOR_MAX];
154
0
                        uint32_t argb = style->i_outline_color;
155
0
                        if( style->i_features & STYLE_HAS_OUTLINE_ALPHA )
156
0
                            argb |= style->i_outline_alpha << 24;
157
0
                        FillHexColor( argb, style->i_features & STYLE_HAS_OUTLINE_ALPHA, color );
158
0
                        tt_node_AddAttribute( p_span, "tts:textOutline", color );
159
0
                    }
160
0
                }
161
162
0
                if( style->i_features & STYLE_HAS_FONT_COLOR )
163
0
                {
164
0
                    char color[HEX_COLOR_MAX];
165
0
                    uint32_t argb = style->i_font_color;
166
0
                    if( style->i_features & STYLE_HAS_FONT_ALPHA )
167
0
                        argb |= style->i_font_alpha << 24;
168
0
                    FillHexColor( argb, style->i_features & STYLE_HAS_FONT_ALPHA, color );
169
0
                    tt_node_AddAttribute( p_span, "tts:color", color );
170
0
                }
171
172
0
                if( style->i_features & STYLE_HAS_BACKGROUND_COLOR )
173
0
                {
174
0
                    char color[HEX_COLOR_MAX];
175
0
                    uint32_t argb = style->i_background_color;
176
0
                    if( style->i_features & STYLE_HAS_BACKGROUND_ALPHA )
177
0
                        argb |= style->i_background_alpha << 24;
178
0
                    FillHexColor( argb, style->i_features & STYLE_HAS_BACKGROUND_ALPHA, color );
179
0
                    tt_node_AddAttribute( p_span, "tts:backgroundColor", color );
180
0
                }
181
182
0
                AddTextNode( p_span, p_segment->psz_text );
183
0
            }
184
0
            else
185
0
            {
186
0
                AddTextNode( p_par, p_segment->psz_text );
187
0
            }
188
0
        }
189
0
    }
190
191
0
    block_t* p_block = NULL;
192
0
    struct vlc_memstream stream;
193
194
0
    if( !vlc_memstream_open( &stream ) )
195
0
    {
196
0
        tt_time_t playbacktime = tt_time_Create( p_spu->i_start );
197
198
0
        tt_node_ToText( &stream, (tt_basenode_t *)p_root, &playbacktime );
199
0
        if( !vlc_memstream_close( &stream ) )
200
0
        {
201
0
            p_block = block_heap_Alloc( stream.ptr, stream.length );
202
0
            if( p_block )
203
0
            {
204
0
                p_block->i_dts = p_block->i_pts = VLC_TICK_0 + p_spu->i_start;
205
0
                if( p_spu->i_stop != VLC_TICK_INVALID && p_spu->i_stop > p_spu->i_start )
206
0
                    p_block->i_length = p_spu->i_stop - p_spu->i_start;
207
0
            }
208
0
        }
209
0
    }
210
211
0
    tt_node_RecursiveDelete( p_root );
212
213
0
    return p_block;
214
0
}
215
216
int tt_OpenEncoder( vlc_object_t *p_this )
217
0
{
218
0
    encoder_t *p_enc = (encoder_t *)p_this;
219
220
0
    if( p_enc->fmt_out.i_codec != VLC_CODEC_TTML )
221
0
        return VLC_EGENERIC;
222
223
0
    p_enc->p_sys = NULL;
224
225
0
    static const struct vlc_encoder_operations ops =
226
0
        { .encode_sub = Encode };
227
0
    p_enc->ops = &ops;
228
229
0
    p_enc->fmt_out.i_cat = SPU_ES;
230
0
    return VLC_SUCCESS;
231
0
}