/src/vlc/modules/codec/ttml/substtml.c
Line | Count | Source |
1 | | /***************************************************************************** |
2 | | * substtml.c : TTML subtitles decoder |
3 | | ***************************************************************************** |
4 | | * Copyright (C) 2015-2017 VLC authors and VideoLAN |
5 | | * |
6 | | * Authors: Hugo Beauzée-Luyssen <hugo@beauzee.fr> |
7 | | * Sushma Reddy <sushma.reddy@research.iiit.ac.in> |
8 | | * |
9 | | * This program is free software; you can redistribute it and/or modify it |
10 | | * under the terms of the GNU Lesser General Public License as published by |
11 | | * the Free Software Foundation; either version 2.1 of the License, or |
12 | | * (at your option) any later version. |
13 | | * |
14 | | * This program is distributed in the hope that it will be useful, |
15 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 | | * GNU Lesser General Public License for more details. |
18 | | * |
19 | | * You should have received a copy of the GNU Lesser General Public License |
20 | | * along with this program; if not, write to the Free Software Foundation, |
21 | | * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. |
22 | | *****************************************************************************/ |
23 | | #ifdef HAVE_CONFIG_H |
24 | | # include "config.h" |
25 | | #endif |
26 | | |
27 | | #include <vlc_common.h> |
28 | | #include <vlc_codec.h> |
29 | | #include <vlc_xml.h> |
30 | | #include <vlc_stream.h> |
31 | | #include <vlc_text_style.h> |
32 | | #include <vlc_charset.h> |
33 | | #include <vlc_image.h> |
34 | | #include <vlc_memstream.h> |
35 | | |
36 | | #include <ctype.h> |
37 | | #include <assert.h> |
38 | | |
39 | | #include "../substext.h" |
40 | | #include "ttml.h" |
41 | | #include "imageupdater.h" |
42 | | #include "ttmlpes.h" |
43 | | |
44 | | //#define TTML_DEBUG |
45 | | |
46 | | /***************************************************************************** |
47 | | * Local prototypes |
48 | | *****************************************************************************/ |
49 | | typedef struct |
50 | | { |
51 | | float i_value; |
52 | | enum |
53 | | { |
54 | | TTML_UNIT_UNKNOWN = 0, |
55 | | TTML_UNIT_PERCENT, |
56 | | TTML_UNIT_CELL, |
57 | | TTML_UNIT_PIXELS, |
58 | | } unit; |
59 | | } ttml_length_t; |
60 | | |
61 | 9.42k | #define TTML_DEFAULT_CELL_RESOLUTION_H 32 |
62 | 9.42k | #define TTML_DEFAULT_CELL_RESOLUTION_V 15 |
63 | 21.9k | #define TTML_LINE_TO_HEIGHT_RATIO 1.06 |
64 | | |
65 | | |
66 | | typedef struct |
67 | | { |
68 | | text_style_t* font_style; |
69 | | ttml_length_t font_size; |
70 | | /* sizes override */ |
71 | | ttml_length_t extent_h, extent_v; |
72 | | ttml_length_t origin_h, origin_v; |
73 | | int i_text_align; |
74 | | bool b_text_align_set; |
75 | | int i_direction; |
76 | | bool b_direction_set; |
77 | | bool b_preserve_space; |
78 | | enum |
79 | | { |
80 | | TTML_DISPLAY_UNKNOWN = 0, |
81 | | TTML_DISPLAY_AUTO, |
82 | | TTML_DISPLAY_NONE, |
83 | | } display; |
84 | | } ttml_style_t; |
85 | | |
86 | | typedef struct |
87 | | { |
88 | | vlc_dictionary_t regions; |
89 | | tt_node_t * p_rootnode; /* for now. FIXME: split header */ |
90 | | ttml_length_t root_extent_h, root_extent_v; |
91 | | unsigned i_cell_resolution_v; |
92 | | unsigned i_cell_resolution_h; |
93 | | } ttml_context_t; |
94 | | |
95 | | typedef struct |
96 | | { |
97 | | substext_updater_region_t updt; |
98 | | text_segment_t **pp_last_segment; |
99 | | struct |
100 | | { |
101 | | uint8_t *p_bytes; |
102 | | size_t i_bytes; |
103 | | } bgbitmap; /* SMPTE-TT */ |
104 | | } ttml_region_t; |
105 | | |
106 | | typedef struct |
107 | | { |
108 | | int i_align; |
109 | | struct ttml_in_pes_ctx pes; |
110 | | } decoder_sys_t; |
111 | | |
112 | | enum |
113 | | { |
114 | | UNICODE_BIDI_LTR = 0, |
115 | | UNICODE_BIDI_RTL = 1, |
116 | | UNICODE_BIDI_EMBEDDED = 2, |
117 | | UNICODE_BIDI_OVERRIDE = 4, |
118 | | }; |
119 | | |
120 | | /* |
121 | | * TTML Parsing and inheritance order: |
122 | | * Each time a text node is found and belongs to out time interval, |
123 | | * we backward merge attributes dictionary up to root. |
124 | | * Then we convert attributes, merging with style by id or region |
125 | | * style, and sets from parent node. |
126 | | */ |
127 | | static tt_node_t *ParseTTML( decoder_t *, tt_namespaces_t *, |
128 | | const uint8_t *, size_t ); |
129 | | |
130 | | static void ttml_style_Delete( ttml_style_t* p_ttml_style ) |
131 | 23.1k | { |
132 | 23.1k | text_style_Delete( p_ttml_style->font_style ); |
133 | 23.1k | free( p_ttml_style ); |
134 | 23.1k | } |
135 | | |
136 | | static ttml_style_t * ttml_style_New( void ) |
137 | 23.1k | { |
138 | 23.1k | ttml_style_t *p_ttml_style = calloc( 1, sizeof( ttml_style_t ) ); |
139 | 23.1k | if( unlikely( !p_ttml_style ) ) |
140 | 0 | return NULL; |
141 | | |
142 | 23.1k | p_ttml_style->extent_h.unit = TTML_UNIT_UNKNOWN; |
143 | 23.1k | p_ttml_style->extent_v.unit = TTML_UNIT_UNKNOWN; |
144 | 23.1k | p_ttml_style->origin_h.unit = TTML_UNIT_UNKNOWN; |
145 | 23.1k | p_ttml_style->origin_v.unit = TTML_UNIT_UNKNOWN; |
146 | 23.1k | p_ttml_style->font_size.i_value = 1.0; |
147 | 23.1k | p_ttml_style->font_size.unit = TTML_UNIT_CELL; |
148 | 23.1k | p_ttml_style->font_style = text_style_Create( STYLE_NO_DEFAULTS ); |
149 | 23.1k | if( unlikely( !p_ttml_style->font_style ) ) |
150 | 0 | { |
151 | 0 | free( p_ttml_style ); |
152 | 0 | return NULL; |
153 | 0 | } |
154 | 23.1k | return p_ttml_style; |
155 | 23.1k | } |
156 | | |
157 | | static void ttml_region_Delete( ttml_region_t *p_region ) |
158 | 20.2k | { |
159 | 20.2k | SubpictureUpdaterSysRegionClean( &p_region->updt ); |
160 | 20.2k | free( p_region->bgbitmap.p_bytes ); |
161 | 20.2k | free( p_region ); |
162 | 20.2k | } |
163 | | |
164 | | static ttml_style_t * ttml_style_Duplicate( const ttml_style_t *p_src ) |
165 | 0 | { |
166 | 0 | ttml_style_t *p_dup = ttml_style_New( ); |
167 | 0 | if( p_dup ) |
168 | 0 | { |
169 | 0 | *p_dup = *p_src; |
170 | 0 | p_dup->font_style = text_style_Duplicate( p_src->font_style ); |
171 | 0 | } |
172 | 0 | return p_dup; |
173 | 0 | } |
174 | | |
175 | | static void ttml_style_Merge( const ttml_style_t *p_src, ttml_style_t *p_dst ) |
176 | 0 | { |
177 | 0 | if( p_src && p_dst ) |
178 | 0 | { |
179 | 0 | if( p_src->font_style ) |
180 | 0 | { |
181 | 0 | if( p_dst->font_style ) |
182 | 0 | text_style_Merge( p_dst->font_style, p_src->font_style, true ); |
183 | 0 | else |
184 | 0 | p_dst->font_style = text_style_Duplicate( p_src->font_style ); |
185 | 0 | } |
186 | |
|
187 | 0 | if( p_src->b_direction_set ) |
188 | 0 | { |
189 | 0 | p_dst->b_direction_set = true; |
190 | 0 | p_dst->i_direction = p_src->i_direction; |
191 | 0 | } |
192 | |
|
193 | 0 | if( p_src->display != TTML_DISPLAY_UNKNOWN ) |
194 | 0 | p_dst->display = p_src->display; |
195 | 0 | } |
196 | 0 | } |
197 | | |
198 | | static ttml_region_t *ttml_region_New( bool b_root ) |
199 | 20.2k | { |
200 | 20.2k | ttml_region_t *p_ttml_region = calloc( 1, sizeof( ttml_region_t ) ); |
201 | 20.2k | if( unlikely( !p_ttml_region ) ) |
202 | 0 | return NULL; |
203 | | |
204 | 20.2k | SubpictureUpdaterSysRegionInit( &p_ttml_region->updt ); |
205 | 20.2k | p_ttml_region->pp_last_segment = &p_ttml_region->updt.p_segments; |
206 | | /* Align to top by default. !Warn: center align is obtained with NO flags */ |
207 | 20.2k | p_ttml_region->updt.align = SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_LEFT; |
208 | 20.2k | if( b_root ) |
209 | 4.98k | { |
210 | 4.98k | p_ttml_region->updt.inner_align = SUBPICTURE_ALIGN_BOTTOM; |
211 | 4.98k | p_ttml_region->updt.extent.x = 1.0; |
212 | 4.98k | p_ttml_region->updt.extent.y = 1.0; |
213 | 4.98k | p_ttml_region->updt.flags = UPDT_REGION_EXTENT_X_IS_RATIO|UPDT_REGION_EXTENT_Y_IS_RATIO; |
214 | 4.98k | } |
215 | 15.2k | else |
216 | 15.2k | { |
217 | 15.2k | p_ttml_region->updt.inner_align = SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_LEFT; |
218 | 15.2k | } |
219 | | |
220 | 20.2k | return p_ttml_region; |
221 | 20.2k | } |
222 | | |
223 | | static ttml_length_t ttml_read_length( const char *psz ) |
224 | 26.2k | { |
225 | 26.2k | ttml_length_t len = { 0.0, TTML_UNIT_UNKNOWN }; |
226 | | |
227 | 26.2k | char* psz_end = NULL; |
228 | 26.2k | float size = vlc_strtof_c( psz, &psz_end ); |
229 | 26.2k | len.i_value = size; |
230 | 26.2k | if( psz_end ) |
231 | 26.2k | { |
232 | 26.2k | if( *psz_end == 'c' || *psz_end == 'r' ) |
233 | 2.24k | len.unit = TTML_UNIT_CELL; |
234 | 23.9k | else if( *psz_end == '%' ) |
235 | 4.88k | len.unit = TTML_UNIT_PERCENT; |
236 | 19.1k | else if( *psz_end == 'p' && *(psz_end + 1) == 'x' ) |
237 | 13.3k | len.unit = TTML_UNIT_PIXELS; |
238 | 26.2k | } |
239 | 26.2k | return len; |
240 | 26.2k | } |
241 | | |
242 | | static ttml_length_t ttml_rebase_length( unsigned i_cell_resolution, |
243 | | ttml_length_t value, |
244 | | ttml_length_t reference ) |
245 | 35.3k | { |
246 | 35.3k | if( value.unit == TTML_UNIT_PERCENT ) |
247 | 3.68k | { |
248 | 3.68k | value.i_value *= reference.i_value / 100.0; |
249 | 3.68k | value.unit = reference.unit; |
250 | 3.68k | } |
251 | 31.6k | else if( value.unit == TTML_UNIT_CELL ) |
252 | 23.4k | { |
253 | 23.4k | value.i_value *= reference.i_value / i_cell_resolution; |
254 | 23.4k | value.unit = reference.unit; |
255 | 23.4k | } |
256 | | // pixels as-is |
257 | 35.3k | return value; |
258 | 35.3k | } |
259 | | |
260 | | static bool ttml_read_coords( const char *value, ttml_length_t *h, ttml_length_t *v ) |
261 | 11.5k | { |
262 | 11.5k | ttml_length_t vals[2] = { { 0.0, TTML_UNIT_UNKNOWN }, |
263 | 11.5k | { 0.0, TTML_UNIT_UNKNOWN } }; |
264 | 11.5k | char *dup = strdup( value ); |
265 | 11.5k | char* psz_saveptr = NULL; |
266 | 11.5k | char* token = (dup) ? strtok_r( dup, " ", &psz_saveptr ) : NULL; |
267 | 33.9k | for(int i=0; i<2 && token != NULL; i++) |
268 | 22.4k | { |
269 | 22.4k | vals[i] = ttml_read_length( token ); |
270 | 22.4k | token = strtok_r( NULL, " ", &psz_saveptr ); |
271 | 22.4k | } |
272 | 11.5k | free( dup ); |
273 | | |
274 | 11.5k | if( vals[0].unit != TTML_UNIT_UNKNOWN && |
275 | 7.74k | vals[1].unit != TTML_UNIT_UNKNOWN ) |
276 | 6.69k | { |
277 | 6.69k | *h = vals[0]; |
278 | 6.69k | *v = vals[1]; |
279 | 6.69k | return true; |
280 | 6.69k | } |
281 | 4.88k | return false; |
282 | 11.5k | } |
283 | | |
284 | | static tt_node_t * FindNode( tt_namespaces_t *p_nss, tt_node_t *p_node, |
285 | | const char *psz_nodename, const char *psz_namespace, |
286 | | size_t i_maxdepth, const char *psz_id ) |
287 | 3.49M | { |
288 | 3.49M | if( tt_node_Match( p_node, psz_nodename, psz_namespace ) ) |
289 | 81.2k | { |
290 | 81.2k | if( psz_id != NULL ) |
291 | 71.8k | { |
292 | 71.8k | const char *psz = tt_node_GetAttribute( p_nss, p_node, "id", TT_NS_XML ); |
293 | 71.8k | if( !psz ) /* People can't do xml properly */ |
294 | 2.99k | psz = tt_node_GetAttribute( p_nss, p_node, "id", NULL ); |
295 | 71.8k | if( psz && !strcmp( psz, psz_id ) ) |
296 | 37.4k | return p_node; |
297 | 71.8k | } |
298 | 9.42k | else return p_node; |
299 | 81.2k | } |
300 | | |
301 | 3.45M | if( i_maxdepth == 0 ) |
302 | 5.47k | return NULL; |
303 | | |
304 | 3.44M | for( tt_basenode_t *p_child = p_node->p_child; |
305 | 6.78M | p_child; p_child = p_child->p_next ) |
306 | 3.83M | { |
307 | 3.83M | if( p_child->i_type == TT_NODE_TYPE_TEXT ) |
308 | 422k | continue; |
309 | | |
310 | 3.41M | p_node = FindNode( p_nss, (tt_node_t *) p_child, |
311 | 3.41M | psz_nodename, psz_namespace, |
312 | 3.41M | i_maxdepth - 1, psz_id ); |
313 | 3.41M | if( p_node ) |
314 | 496k | return p_node; |
315 | 3.41M | } |
316 | | |
317 | 2.94M | return NULL; |
318 | 3.44M | } |
319 | | |
320 | | static void FillTextStyle( const char *psz_attr, const char *psz_val, |
321 | | text_style_t *p_text_style ) |
322 | 38.9k | { |
323 | 38.9k | if( !strcasecmp ( "fontFamily", psz_attr ) ) |
324 | 3.37k | { |
325 | 3.37k | free( p_text_style->psz_fontname ); |
326 | 3.37k | p_text_style->psz_fontname = strdup( psz_val ); |
327 | 3.37k | } |
328 | 35.5k | else if( !strcasecmp( "opacity", psz_attr ) ) |
329 | 210 | { |
330 | 210 | p_text_style->i_background_alpha = atoi( psz_val ); |
331 | 210 | p_text_style->i_font_alpha = atoi( psz_val ); |
332 | 210 | p_text_style->i_features |= STYLE_HAS_BACKGROUND_ALPHA | STYLE_HAS_FONT_ALPHA; |
333 | 210 | } |
334 | 35.3k | else if( !strcasecmp( "color", psz_attr ) ) |
335 | 4.84k | { |
336 | 4.84k | unsigned int i_color = vlc_html_color( psz_val, NULL ); |
337 | 4.84k | p_text_style->i_font_color = (i_color & 0xffffff); |
338 | 4.84k | p_text_style->i_font_alpha = (i_color & 0xFF000000) >> 24; |
339 | 4.84k | p_text_style->i_features |= STYLE_HAS_FONT_COLOR | STYLE_HAS_FONT_ALPHA; |
340 | 4.84k | } |
341 | 30.4k | else if( !strcasecmp( "backgroundColor", psz_attr ) ) |
342 | 8.37k | { |
343 | 8.37k | unsigned int i_color = vlc_html_color( psz_val, NULL ); |
344 | 8.37k | p_text_style->i_background_color = i_color & 0xFFFFFF; |
345 | 8.37k | p_text_style->i_background_alpha = (i_color & 0xFF000000) >> 24; |
346 | 8.37k | p_text_style->i_features |= STYLE_HAS_BACKGROUND_COLOR |
347 | 8.37k | | STYLE_HAS_BACKGROUND_ALPHA; |
348 | 8.37k | p_text_style->i_style_flags |= STYLE_BACKGROUND; |
349 | 8.37k | } |
350 | 22.0k | else if( !strcasecmp( "fontStyle", psz_attr ) ) |
351 | 0 | { |
352 | 0 | if( !strcasecmp ( "italic", psz_val ) || !strcasecmp ( "oblique", psz_val ) ) |
353 | 0 | p_text_style->i_style_flags |= STYLE_ITALIC; |
354 | 0 | else |
355 | 0 | p_text_style->i_style_flags &= ~STYLE_ITALIC; |
356 | 0 | p_text_style->i_features |= STYLE_HAS_FLAGS; |
357 | 0 | } |
358 | 22.0k | else if( !strcasecmp ( "fontWeight", psz_attr ) ) |
359 | 88 | { |
360 | 88 | if( !strcasecmp ( "bold", psz_val ) ) |
361 | 0 | p_text_style->i_style_flags |= STYLE_BOLD; |
362 | 88 | else |
363 | 88 | p_text_style->i_style_flags &= ~STYLE_BOLD; |
364 | 88 | p_text_style->i_features |= STYLE_HAS_FLAGS; |
365 | 88 | } |
366 | 22.0k | else if( !strcasecmp ( "textDecoration", psz_attr ) ) |
367 | 5 | { |
368 | 5 | if( !strcasecmp ( "underline", psz_val ) ) |
369 | 0 | p_text_style->i_style_flags |= STYLE_UNDERLINE; |
370 | 5 | else if( !strcasecmp ( "noUnderline", psz_val ) ) |
371 | 0 | p_text_style->i_style_flags &= ~STYLE_UNDERLINE; |
372 | 5 | if( !strcasecmp ( "lineThrough", psz_val ) ) |
373 | 0 | p_text_style->i_style_flags |= STYLE_STRIKEOUT; |
374 | 5 | else if( !strcasecmp ( "noLineThrough", psz_val ) ) |
375 | 0 | p_text_style->i_style_flags &= ~STYLE_STRIKEOUT; |
376 | 5 | p_text_style->i_features |= STYLE_HAS_FLAGS; |
377 | 5 | } |
378 | 22.0k | else if( !strcasecmp( "textOutline", psz_attr ) ) |
379 | 0 | { |
380 | 0 | char *value = strdup( psz_val ); |
381 | 0 | char* psz_saveptr = NULL; |
382 | 0 | char* token = (value) ? strtok_r( value, " ", &psz_saveptr ) : NULL; |
383 | | // <color>? <length> <length>? |
384 | 0 | if( token != NULL ) |
385 | 0 | { |
386 | 0 | bool b_ok = false; |
387 | 0 | unsigned int color = vlc_html_color( token, &b_ok ); |
388 | 0 | if( b_ok ) |
389 | 0 | { |
390 | 0 | p_text_style->i_outline_color = color & 0xFFFFFF; |
391 | 0 | p_text_style->i_outline_alpha = (color & 0xFF000000) >> 24; |
392 | 0 | token = strtok_r( NULL, " ", &psz_saveptr ); |
393 | 0 | if( token != NULL ) |
394 | 0 | { |
395 | 0 | char* psz_end = NULL; |
396 | 0 | int i_outline_width = strtol( token, &psz_end, 10 ); |
397 | 0 | if( psz_end != token ) |
398 | 0 | { |
399 | | // Assume unit is pixel, and ignore border radius |
400 | 0 | p_text_style->i_outline_width = i_outline_width; |
401 | 0 | } |
402 | 0 | } |
403 | 0 | } |
404 | 0 | } |
405 | 0 | free( value ); |
406 | 0 | } |
407 | 38.9k | } |
408 | | |
409 | | static void FillCoord( ttml_length_t v, int i_flag, float *p_val, int *pi_flags ) |
410 | 13.3k | { |
411 | 13.3k | *p_val = v.i_value; |
412 | 13.3k | if( v.unit == TTML_UNIT_PERCENT ) |
413 | 5.25k | { |
414 | 5.25k | *p_val /= 100.0; |
415 | 5.25k | *pi_flags |= i_flag; |
416 | 5.25k | } |
417 | 8.13k | else *pi_flags &= ~i_flag; |
418 | 13.3k | } |
419 | | |
420 | | static void FillUpdaterCoords( ttml_context_t *p_ctx, ttml_length_t h, ttml_length_t v, |
421 | | bool b_origin, substext_updater_region_t *p_updt ) |
422 | 6.69k | { |
423 | 6.69k | ttml_length_t base = { 100.0, TTML_UNIT_PERCENT }; |
424 | 6.69k | ttml_length_t x = ttml_rebase_length( p_ctx->i_cell_resolution_h, h, base ); |
425 | 6.69k | ttml_length_t y = ttml_rebase_length( p_ctx->i_cell_resolution_v, v, base ); |
426 | 6.69k | if( b_origin ) |
427 | 824 | { |
428 | 824 | FillCoord( x, UPDT_REGION_ORIGIN_X_IS_RATIO, &p_updt->origin.x, &p_updt->flags ); |
429 | 824 | FillCoord( y, UPDT_REGION_ORIGIN_Y_IS_RATIO, &p_updt->origin.y, &p_updt->flags ); |
430 | 824 | p_updt->align = SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_LEFT; |
431 | 824 | } |
432 | 5.86k | else |
433 | 5.86k | { |
434 | 5.86k | FillCoord( x, UPDT_REGION_EXTENT_X_IS_RATIO, &p_updt->extent.x, &p_updt->flags ); |
435 | 5.86k | FillCoord( y, UPDT_REGION_EXTENT_Y_IS_RATIO, &p_updt->extent.y, &p_updt->flags ); |
436 | 5.86k | } |
437 | 6.69k | } |
438 | | |
439 | | static void FillRegionStyle( ttml_context_t *p_ctx, |
440 | | const char *psz_attr, const char *psz_namespace, |
441 | | const char *psz_val, ttml_region_t *p_region ) |
442 | 14.4k | { |
443 | 14.4k | if( strcmp( psz_namespace, TT_NS_STYLING ) ) |
444 | 4.23k | return; |
445 | | |
446 | 10.2k | if( !strcasecmp( "displayAlign", psz_attr ) ) |
447 | 1.61k | { |
448 | 1.61k | p_region->updt.inner_align &= ~(SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_BOTTOM); |
449 | 1.61k | if( !strcasecmp( "after", psz_val ) ) |
450 | 1.23k | p_region->updt.inner_align |= SUBPICTURE_ALIGN_BOTTOM; |
451 | 376 | else if( strcasecmp( "center", psz_val ) ) |
452 | | /* "before" */ |
453 | 376 | p_region->updt.inner_align |= SUBPICTURE_ALIGN_TOP; |
454 | 1.61k | } |
455 | 8.62k | else if( !strcasecmp ( "origin", psz_attr ) || |
456 | 8.36k | !strcasecmp ( "extent", psz_attr ) ) |
457 | 2.11k | { |
458 | 2.11k | ttml_length_t x, y; |
459 | 2.11k | if( ttml_read_coords( psz_val, &x, &y ) ) |
460 | 1.41k | FillUpdaterCoords( p_ctx, x, y, (psz_attr[0] == 'o'), &p_region->updt ); |
461 | 2.11k | } |
462 | 10.2k | } |
463 | | |
464 | | static void ComputeTTMLStyles( ttml_context_t *p_ctx, const vlc_dictionary_t *p_dict, |
465 | | ttml_style_t *p_ttml_style ) |
466 | 23.1k | { |
467 | 23.1k | VLC_UNUSED(p_dict); |
468 | | /* Values depending on multiple others are converted last |
469 | | * Default value conversion must also not depend on attribute presence */ |
470 | 23.1k | text_style_t *p_text_style = p_ttml_style->font_style; |
471 | 23.1k | ttml_length_t len = p_ttml_style->font_size; |
472 | | |
473 | | /* font size is pixels, cells or, % of cell */ |
474 | 23.1k | if( len.unit == TTML_UNIT_PERCENT ) |
475 | 841 | { |
476 | 841 | len.i_value /= 100.0; |
477 | 841 | len.unit = TTML_UNIT_CELL; |
478 | 841 | } |
479 | | |
480 | | /* font size is now pixels or cells */ |
481 | | /* if cell (and indirectly cell %), rebase as line height depending on resolution */ |
482 | 23.1k | if( len.unit == TTML_UNIT_CELL ) |
483 | 21.9k | len = ttml_rebase_length( p_ctx->i_cell_resolution_v, len, p_ctx->root_extent_v ); |
484 | | |
485 | | /* font size is root_extent height % or pixels */ |
486 | 23.1k | if( len.unit == TTML_UNIT_PERCENT ) |
487 | 21.9k | p_text_style->f_font_relsize = len.i_value / TTML_LINE_TO_HEIGHT_RATIO; |
488 | 1.23k | else |
489 | 1.23k | if( len.unit == TTML_UNIT_PIXELS ) |
490 | 1.23k | p_text_style->i_font_size = len.i_value; |
491 | 23.1k | } |
492 | | |
493 | | static void FillTTMLStyle( const char *psz_attr, const char *psz_namespace, |
494 | | const char *psz_val, ttml_style_t *p_ttml_style ) |
495 | 105k | { |
496 | 105k | if( !strcmp( psz_namespace, TT_NS_XML ) ) |
497 | 18.9k | { |
498 | 18.9k | if( !strcasecmp( "space", psz_attr ) ) |
499 | 0 | p_ttml_style->b_preserve_space = !strcmp( "preserve", psz_val ); |
500 | 18.9k | return; |
501 | 18.9k | } |
502 | | |
503 | 86.0k | if( strcmp( psz_namespace, TT_NS_STYLING ) ) |
504 | 29.1k | return; |
505 | | |
506 | 56.8k | if( !strcasecmp( "extent", psz_attr ) ) |
507 | 8.67k | { |
508 | 8.67k | ttml_read_coords( psz_val, &p_ttml_style->extent_h, |
509 | 8.67k | &p_ttml_style->extent_v ); |
510 | 8.67k | } |
511 | 48.2k | else if( !strcasecmp( "origin", psz_attr ) ) |
512 | 781 | { |
513 | 781 | ttml_read_coords( psz_val, &p_ttml_style->origin_h, |
514 | 781 | &p_ttml_style->origin_v ); |
515 | 781 | } |
516 | 47.4k | else if( !strcasecmp( "textAlign", psz_attr ) ) |
517 | 4.61k | { |
518 | 4.61k | p_ttml_style->i_text_align &= ~(SUBPICTURE_ALIGN_LEFT|SUBPICTURE_ALIGN_RIGHT); |
519 | 4.61k | if( !strcasecmp ( "left", psz_val ) ) |
520 | 0 | p_ttml_style->i_text_align |= SUBPICTURE_ALIGN_LEFT; |
521 | 4.61k | else if( !strcasecmp ( "right", psz_val ) ) |
522 | 0 | p_ttml_style->i_text_align |= SUBPICTURE_ALIGN_RIGHT; |
523 | 4.61k | else if( !strcasecmp ( "end", psz_val ) ) /* FIXME: should be BIDI based */ |
524 | 1.52k | p_ttml_style->i_text_align |= SUBPICTURE_ALIGN_RIGHT; |
525 | 3.08k | else if( strcasecmp ( "center", psz_val ) ) |
526 | | /* == "start" FIXME: should be BIDI based */ |
527 | 844 | p_ttml_style->i_text_align |= SUBPICTURE_ALIGN_LEFT; |
528 | 4.61k | p_ttml_style->b_text_align_set = true; |
529 | | #ifdef TTML_DEBUG |
530 | | printf("**%s %x\n", psz_val, p_ttml_style->i_text_align); |
531 | | #endif |
532 | 4.61k | } |
533 | 42.8k | else if( !strcasecmp( "fontSize", psz_attr ) ) |
534 | 3.82k | { |
535 | 3.82k | ttml_length_t len = ttml_read_length( psz_val ); |
536 | 3.82k | if( len.unit != TTML_UNIT_UNKNOWN && len.i_value > 0.0 ) |
537 | 2.62k | p_ttml_style->font_size = len; |
538 | 3.82k | } |
539 | 38.9k | else if( !strcasecmp( "direction", psz_attr ) ) |
540 | 0 | { |
541 | 0 | if( !strcasecmp( "rtl", psz_val ) ) |
542 | 0 | { |
543 | 0 | p_ttml_style->i_direction |= UNICODE_BIDI_RTL; |
544 | 0 | p_ttml_style->b_direction_set = true; |
545 | 0 | } |
546 | 0 | else if( !strcasecmp( "ltr", psz_val ) ) |
547 | 0 | { |
548 | 0 | p_ttml_style->i_direction |= UNICODE_BIDI_LTR; |
549 | 0 | p_ttml_style->b_direction_set = true; |
550 | 0 | } |
551 | 0 | } |
552 | 38.9k | else if( !strcasecmp( "unicodeBidi", psz_attr ) ) |
553 | 0 | { |
554 | 0 | if( !strcasecmp( "bidiOverride", psz_val ) ) |
555 | 0 | p_ttml_style->i_direction |= UNICODE_BIDI_OVERRIDE & ~UNICODE_BIDI_EMBEDDED; |
556 | 0 | else if( !strcasecmp( "embed", psz_val ) ) |
557 | 0 | p_ttml_style->i_direction |= UNICODE_BIDI_EMBEDDED & ~UNICODE_BIDI_OVERRIDE; |
558 | 0 | } |
559 | 38.9k | else if( !strcasecmp( "writingMode", psz_attr ) ) |
560 | 0 | { |
561 | 0 | if( !strcasecmp( "rl", psz_val ) || !strcasecmp( "rltb", psz_val ) ) |
562 | 0 | { |
563 | 0 | p_ttml_style->i_direction = UNICODE_BIDI_RTL | UNICODE_BIDI_OVERRIDE; |
564 | | //p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_RIGHT; |
565 | 0 | p_ttml_style->b_direction_set = true; |
566 | 0 | } |
567 | 0 | else if( !strcasecmp( "lr", psz_val ) || !strcasecmp( "lrtb", psz_val ) ) |
568 | 0 | { |
569 | 0 | p_ttml_style->i_direction = UNICODE_BIDI_LTR | UNICODE_BIDI_OVERRIDE; |
570 | | //p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_LEFT; |
571 | 0 | p_ttml_style->b_direction_set = true; |
572 | 0 | } |
573 | 0 | } |
574 | 38.9k | else if( !strcmp( "display", psz_attr ) ) |
575 | 88 | { |
576 | 88 | if( !strcmp( "none", psz_val ) ) |
577 | 0 | p_ttml_style->display = TTML_DISPLAY_NONE; |
578 | 88 | else |
579 | 88 | p_ttml_style->display = TTML_DISPLAY_AUTO; |
580 | 88 | } |
581 | 38.9k | else FillTextStyle( psz_attr, psz_val, p_ttml_style->font_style ); |
582 | 56.8k | } |
583 | | |
584 | | static void DictionaryMerge( const vlc_dictionary_t *p_src, vlc_dictionary_t *p_dst, |
585 | | bool b_override ) |
586 | 391k | { |
587 | 853k | for (size_t i = 0; i < p_src->i_size; ++i) |
588 | 461k | { |
589 | 461k | for ( const vlc_dictionary_entry_t* p_entry = p_src->p_entries[i]; |
590 | 1.15M | p_entry != NULL; p_entry = p_entry->p_next ) |
591 | 688k | { |
592 | 688k | if( vlc_dictionary_has_key( p_dst, p_entry->psz_key ) ) |
593 | 406k | { |
594 | 406k | if( b_override ) |
595 | 0 | { |
596 | 0 | vlc_dictionary_remove_value_for_key( p_dst, p_entry->psz_key, NULL, NULL ); |
597 | 0 | vlc_dictionary_insert( p_dst, p_entry->psz_key, p_entry->p_value ); |
598 | 0 | } |
599 | 406k | } |
600 | 281k | else |
601 | 281k | vlc_dictionary_insert( p_dst, p_entry->psz_key, p_entry->p_value ); |
602 | 688k | } |
603 | 461k | } |
604 | 391k | } |
605 | | |
606 | | static void DictMergeWithStyleID( ttml_context_t *p_ctx, tt_namespaces_t *p_nss, |
607 | | const char *psz_styles, vlc_dictionary_t *p_dst ) |
608 | 25.6k | { |
609 | 25.6k | assert(p_ctx->p_rootnode); |
610 | 25.6k | char *psz_dup; |
611 | 25.6k | if( psz_styles && p_ctx->p_rootnode && (psz_dup = strdup( psz_styles )) ) |
612 | 25.6k | { |
613 | | /* Use temp dict instead of reverse token processing to |
614 | | * resolve styles in specified order */ |
615 | 25.6k | vlc_dictionary_t tempdict; |
616 | 25.6k | vlc_dictionary_init( &tempdict, 0 ); |
617 | | |
618 | 25.6k | char *saveptr; |
619 | 25.6k | char *psz_id = strtok_r( psz_dup, " ", &saveptr ); |
620 | 51.3k | while( psz_id ) |
621 | 25.6k | { |
622 | | /* Lookup referenced style ID */ |
623 | 25.6k | const tt_node_t *p_node = FindNode( p_nss, |
624 | 25.6k | p_ctx->p_rootnode, |
625 | 25.6k | "style", TT_NS, |
626 | 25.6k | -1, psz_id ); |
627 | 25.6k | if( p_node ) |
628 | 15.9k | DictionaryMerge( &p_node->attr_dict, &tempdict, true ); |
629 | | |
630 | 25.6k | psz_id = strtok_r( NULL, " ", &saveptr ); |
631 | 25.6k | } |
632 | | |
633 | 25.6k | if( !vlc_dictionary_is_empty( &tempdict ) ) |
634 | 15.9k | DictionaryMerge( &tempdict, p_dst, false ); |
635 | | |
636 | 25.6k | vlc_dictionary_clear( &tempdict, NULL, NULL ); |
637 | 25.6k | free( psz_dup ); |
638 | 25.6k | } |
639 | 25.6k | } |
640 | | |
641 | | static void DictMergeWithRegionID( ttml_context_t *p_ctx, tt_namespaces_t *p_nss, |
642 | | const char *psz_id, vlc_dictionary_t *p_dst ) |
643 | 48.7k | { |
644 | 48.7k | assert(p_ctx->p_rootnode); |
645 | 48.7k | if( psz_id && p_ctx->p_rootnode ) |
646 | 48.7k | { |
647 | 48.7k | const tt_node_t *p_regionnode = FindNode( p_nss, |
648 | 48.7k | p_ctx->p_rootnode, |
649 | 48.7k | "region", TT_NS, |
650 | 48.7k | -1, psz_id ); |
651 | 48.7k | if( !p_regionnode ) |
652 | 27.2k | return; |
653 | | |
654 | 21.5k | DictionaryMerge( &p_regionnode->attr_dict, p_dst, false ); |
655 | | |
656 | 21.5k | const char *psz_styleid = |
657 | 21.5k | tt_node_GetAttribute( p_nss, p_regionnode, "style", NULL ); |
658 | 21.5k | if( psz_styleid ) |
659 | 19.5k | DictMergeWithStyleID( p_ctx, p_nss, psz_styleid, p_dst ); |
660 | | |
661 | 21.5k | for( const tt_basenode_t *p_child = p_regionnode->p_child; |
662 | 22.0k | p_child; p_child = p_child->p_next ) |
663 | 549 | { |
664 | 549 | if( unlikely( p_child->i_type == TT_NODE_TYPE_TEXT ) ) |
665 | 208 | continue; |
666 | | |
667 | 341 | const tt_node_t *p_node = (const tt_node_t *) p_child; |
668 | 341 | if( tt_node_Match( p_node, "style", TT_NS ) ) |
669 | 0 | { |
670 | 0 | DictionaryMerge( &p_node->attr_dict, p_dst, false ); |
671 | 0 | } |
672 | 341 | } |
673 | 21.5k | } |
674 | 48.7k | } |
675 | | |
676 | | static void DictToTTMLStyle( ttml_context_t *p_ctx, tt_namespaces_t *p_nss, |
677 | | const vlc_dictionary_t *p_dict, |
678 | | ttml_style_t *p_ttml_style ) |
679 | 23.1k | { |
680 | 190k | for (size_t i = 0; i < p_dict->i_size; ++i) |
681 | 167k | { |
682 | 167k | for ( vlc_dictionary_entry_t* p_entry = p_dict->p_entries[i]; |
683 | 374k | p_entry != NULL; p_entry = p_entry->p_next ) |
684 | 207k | { |
685 | 207k | const char *psz_namespace = tt_namespaces_GetURI( p_nss, p_entry->psz_key ); |
686 | 207k | if( !psz_namespace ) |
687 | 102k | continue; |
688 | 105k | const char *psz_name = tt_LocalName( p_entry->psz_key ); |
689 | 105k | FillTTMLStyle( psz_name, psz_namespace, p_entry->p_value, p_ttml_style ); |
690 | 105k | } |
691 | 167k | } |
692 | 23.1k | ComputeTTMLStyles( p_ctx, p_dict, p_ttml_style ); |
693 | 23.1k | } |
694 | | |
695 | | static ttml_style_t * InheritTTMLStyles( ttml_context_t *p_ctx, tt_namespaces_t *p_nss, |
696 | | tt_node_t *p_node ) |
697 | 22.9k | { |
698 | 22.9k | assert( p_node ); |
699 | 22.9k | ttml_style_t *p_ttml_style = NULL; |
700 | 22.9k | vlc_dictionary_t merged; |
701 | 22.9k | vlc_dictionary_init( &merged, 0 ); |
702 | | |
703 | | /* Merge dics backwards without overwriting */ |
704 | 360k | for( ; p_node; p_node = p_node->p_parent ) |
705 | 338k | { |
706 | 338k | DictionaryMerge( &p_node->attr_dict, &merged, false ); |
707 | | |
708 | 338k | const char *psz_styleid = tt_node_GetAttribute( p_nss, p_node, "style", NULL ); |
709 | 338k | if( psz_styleid ) |
710 | 6.09k | DictMergeWithStyleID( p_ctx, p_nss, psz_styleid, &merged ); |
711 | | |
712 | 338k | const char *psz_regionid = tt_node_GetAttribute( p_nss, p_node, "region", NULL ); |
713 | 338k | if( psz_regionid ) |
714 | 33.5k | DictMergeWithRegionID( p_ctx, p_nss, psz_regionid, &merged ); |
715 | 338k | } |
716 | | |
717 | 22.9k | if( !vlc_dictionary_is_empty( &merged ) && (p_ttml_style = ttml_style_New()) ) |
718 | 22.9k | { |
719 | 22.9k | DictToTTMLStyle( p_ctx, p_nss, &merged, p_ttml_style ); |
720 | 22.9k | } |
721 | | |
722 | 22.9k | vlc_dictionary_clear( &merged, NULL, NULL ); |
723 | | |
724 | 22.9k | return p_ttml_style; |
725 | 22.9k | } |
726 | | |
727 | | static int ParseTTMLChunk( xml_reader_t *p_reader, tt_namespaces_t *p_nss, |
728 | | tt_node_t **pp_rootnode ) |
729 | 24.7k | { |
730 | 24.7k | const char *psz_node_name, *psz_node_namespace; |
731 | | |
732 | 24.7k | do |
733 | 39.7k | { |
734 | 39.7k | int i_type = xml_ReaderNextNodeNS( p_reader, &psz_node_name, &psz_node_namespace ); |
735 | | |
736 | 39.7k | if( i_type <= XML_READER_NONE ) |
737 | 24.1k | break; |
738 | | |
739 | 15.6k | switch(i_type) |
740 | 15.6k | { |
741 | 0 | default: |
742 | 0 | break; |
743 | | |
744 | 15.6k | case XML_READER_STARTELEM: |
745 | 15.6k | if( strcmp( psz_node_namespace, TT_NS ) || |
746 | 15.6k | strcmp( tt_LocalName( psz_node_name ), "tt" ) || |
747 | 15.6k | *pp_rootnode != NULL ) |
748 | 0 | return VLC_EGENERIC; |
749 | 15.6k | *pp_rootnode = tt_node_NewRead( p_reader, p_nss, NULL, |
750 | 15.6k | psz_node_name, psz_node_namespace ); |
751 | 15.6k | if( !*pp_rootnode || |
752 | 15.6k | tt_nodes_Read( p_reader, p_nss, *pp_rootnode ) != VLC_SUCCESS ) |
753 | 595 | return VLC_EGENERIC; |
754 | 15.0k | break; |
755 | | |
756 | 15.0k | case XML_READER_ENDELEM: |
757 | 0 | if( !*pp_rootnode || |
758 | 0 | strcmp( psz_node_name, (*pp_rootnode)->psz_node_name ) ) |
759 | 0 | return VLC_EGENERIC; |
760 | 0 | break; |
761 | 15.6k | } |
762 | | |
763 | 15.6k | } while( 1 ); |
764 | | |
765 | 24.1k | if( *pp_rootnode == NULL ) |
766 | 9.03k | return VLC_EGENERIC; |
767 | | |
768 | 15.0k | return VLC_SUCCESS; |
769 | 24.1k | } |
770 | | |
771 | | static void BIDIConvert( text_segment_t *p_segment, int i_direction ) |
772 | 0 | { |
773 | | /* |
774 | | * For bidirectionnal support, we use different enum |
775 | | * to recognize different cases, en then we add the |
776 | | * corresponding unicode character to the text of |
777 | | * the text_segment. |
778 | | */ |
779 | 0 | static const struct |
780 | 0 | { |
781 | 0 | const char* psz_uni_start; |
782 | 0 | const char* psz_uni_end; |
783 | 0 | } p_bidi[] = { |
784 | 0 | { "\u2066", "\u2069" }, |
785 | 0 | { "\u2067", "\u2069" }, |
786 | 0 | { "\u202A", "\u202C" }, |
787 | 0 | { "\u202B", "\u202C" }, |
788 | 0 | { "\u202D", "\u202C" }, |
789 | 0 | { "\u202E", "\u202C" }, |
790 | 0 | }; |
791 | |
|
792 | 0 | if( unlikely((size_t)i_direction >= ARRAY_SIZE(p_bidi)) ) |
793 | 0 | return; |
794 | | |
795 | 0 | char *psz_text = NULL; |
796 | 0 | if( asprintf( &psz_text, "%s%s%s", p_bidi[i_direction].psz_uni_start, |
797 | 0 | p_segment->psz_text, p_bidi[i_direction].psz_uni_end ) != -1 ) |
798 | 0 | { |
799 | 0 | free( p_segment->psz_text ); |
800 | 0 | p_segment->psz_text = psz_text; |
801 | 0 | } |
802 | 0 | } |
803 | | |
804 | | static void StripSpacing( text_segment_t *p_segment ) |
805 | 22.9k | { |
806 | | /* Newlines must be replaced */ |
807 | 22.9k | char *p = p_segment->psz_text; |
808 | 38.7k | while( (p = strchr( p, '\n' )) ) |
809 | 15.7k | *p = ' '; |
810 | 22.9k | } |
811 | | |
812 | | static ttml_region_t *GetTTMLRegion( ttml_context_t *p_ctx, tt_namespaces_t *p_nss, |
813 | | const char *psz_region_id ) |
814 | 34.6k | { |
815 | 34.6k | ttml_region_t *p_region = ( ttml_region_t * ) |
816 | 34.6k | vlc_dictionary_value_for_key( &p_ctx->regions, psz_region_id ? psz_region_id : "" ); |
817 | 34.6k | if( p_region == NULL ) |
818 | 20.2k | { |
819 | 20.2k | if( psz_region_id && strcmp( psz_region_id, "" ) ) /* not default region */ |
820 | 15.2k | { |
821 | | /* Create region if if missing */ |
822 | | |
823 | 15.2k | vlc_dictionary_t merged; |
824 | 15.2k | vlc_dictionary_init( &merged, 0 ); |
825 | | /* Get all attributes, including region > style */ |
826 | 15.2k | DictMergeWithRegionID( p_ctx, p_nss, psz_region_id, &merged ); |
827 | 15.2k | if( (p_region = ttml_region_New( false )) ) |
828 | 15.2k | { |
829 | | /* Fill from its own attributes */ |
830 | 28.1k | for (size_t i = 0; i < merged.i_size; ++i) |
831 | 12.8k | { |
832 | 12.8k | for ( vlc_dictionary_entry_t* p_entry = merged.p_entries[i]; |
833 | 30.1k | p_entry != NULL; p_entry = p_entry->p_next ) |
834 | 17.2k | { |
835 | 17.2k | const char *psz_namespace = tt_namespaces_GetURI( p_nss, p_entry->psz_key ); |
836 | 17.2k | if( !psz_namespace ) |
837 | 2.74k | continue; |
838 | 14.4k | const char *psz_name = tt_LocalName( p_entry->psz_key ); |
839 | 14.4k | FillRegionStyle( p_ctx, psz_name, psz_namespace, |
840 | 14.4k | p_entry->p_value, p_region ); |
841 | 14.4k | } |
842 | 12.8k | } |
843 | 15.2k | } |
844 | 15.2k | vlc_dictionary_clear( &merged, NULL, NULL ); |
845 | | |
846 | 15.2k | vlc_dictionary_insert( &p_ctx->regions, psz_region_id, p_region ); |
847 | 15.2k | } |
848 | 4.98k | else if( (p_region = ttml_region_New( true )) ) /* create default */ |
849 | 4.98k | { |
850 | 4.98k | vlc_dictionary_insert( &p_ctx->regions, "", p_region ); |
851 | 4.98k | } |
852 | 20.2k | } |
853 | 34.6k | return p_region; |
854 | 34.6k | } |
855 | | |
856 | | static void AppendLineBreakToRegion( ttml_region_t *p_region ) |
857 | 73.5k | { |
858 | 73.5k | text_segment_t *p_segment = text_segment_New( "\n" ); |
859 | 73.5k | if( p_segment ) |
860 | 73.5k | { |
861 | 73.5k | *p_region->pp_last_segment = p_segment; |
862 | 73.5k | p_region->pp_last_segment = &p_segment->p_next; |
863 | 73.5k | } |
864 | 73.5k | } |
865 | | |
866 | | static void AppendTextToRegion( ttml_context_t *p_ctx, tt_namespaces_t *p_nss, |
867 | | const tt_textnode_t *p_ttnode, |
868 | | const ttml_style_t *p_set_styles, ttml_region_t *p_region ) |
869 | 22.9k | { |
870 | 22.9k | text_segment_t *p_segment; |
871 | | |
872 | 22.9k | if( p_region == NULL ) |
873 | 0 | return; |
874 | | |
875 | 22.9k | p_segment = text_segment_New( p_ttnode->psz_text ); |
876 | 22.9k | if( p_segment ) |
877 | 22.9k | { |
878 | 22.9k | bool b_preserve_space = false; |
879 | 22.9k | ttml_style_t *s = InheritTTMLStyles( p_ctx, p_nss, p_ttnode->p_parent ); |
880 | 22.9k | if( s ) |
881 | 22.9k | { |
882 | 22.9k | if( p_set_styles ) |
883 | 0 | ttml_style_Merge( p_set_styles, s ); |
884 | | |
885 | 22.9k | p_segment->style = s->font_style; |
886 | 22.9k | s->font_style = NULL; |
887 | | |
888 | 22.9k | b_preserve_space = s->b_preserve_space; |
889 | 22.9k | if( s->b_direction_set ) |
890 | 0 | BIDIConvert( p_segment, s->i_direction ); |
891 | | |
892 | 22.9k | if( s->display == TTML_DISPLAY_NONE ) |
893 | 0 | { |
894 | | /* Must not display, but still occupies space */ |
895 | 0 | p_segment->style->i_features &= ~(STYLE_BACKGROUND|STYLE_OUTLINE|STYLE_STRIKEOUT|STYLE_SHADOW); |
896 | 0 | p_segment->style->i_font_alpha = STYLE_ALPHA_TRANSPARENT; |
897 | 0 | p_segment->style->i_features |= STYLE_HAS_FONT_ALPHA; |
898 | 0 | } |
899 | | |
900 | | /* we don't have paragraph, so no per text line alignment. |
901 | | * Text style brings horizontal textAlign to region. |
902 | | * Region itself is styled with vertical displayAlign */ |
903 | 22.9k | if( s->b_text_align_set ) |
904 | 4.61k | { |
905 | 4.61k | p_region->updt.inner_align &= ~(SUBPICTURE_ALIGN_LEFT|SUBPICTURE_ALIGN_RIGHT); |
906 | 4.61k | p_region->updt.inner_align |= s->i_text_align; |
907 | 4.61k | } |
908 | | |
909 | 22.9k | if( s->extent_h.unit != TTML_UNIT_UNKNOWN ) |
910 | 4.65k | FillUpdaterCoords( p_ctx, s->extent_h, s->extent_v, false, &p_region->updt ); |
911 | | |
912 | 22.9k | if( s->origin_h.unit != TTML_UNIT_UNKNOWN ) |
913 | 623 | FillUpdaterCoords( p_ctx, s->origin_h, s->origin_v, true, &p_region->updt ); |
914 | | |
915 | 22.9k | ttml_style_Delete( s ); |
916 | 22.9k | } |
917 | | |
918 | 22.9k | if( !b_preserve_space ) |
919 | 22.9k | StripSpacing( p_segment ); |
920 | 22.9k | } |
921 | | |
922 | 22.9k | *p_region->pp_last_segment = p_segment; |
923 | 22.9k | p_region->pp_last_segment = &p_segment->p_next; |
924 | 22.9k | } |
925 | | |
926 | | static const char * GetSMPTEImage( ttml_context_t *p_ctx, tt_namespaces_t *p_nss, |
927 | | const char *psz_id ) |
928 | 0 | { |
929 | 0 | if( !p_ctx->p_rootnode ) |
930 | 0 | return NULL; |
931 | | |
932 | 0 | tt_node_t *p_head = FindNode( p_nss, p_ctx->p_rootnode, "head", TT_NS, 1, NULL ); |
933 | 0 | if( !p_head ) |
934 | 0 | return NULL; |
935 | | |
936 | 0 | for( tt_basenode_t *p_child = p_head->p_child; |
937 | 0 | p_child; p_child = p_child->p_next ) |
938 | 0 | { |
939 | 0 | if( p_child->i_type == TT_NODE_TYPE_TEXT ) |
940 | 0 | continue; |
941 | | |
942 | 0 | tt_node_t *p_node = (tt_node_t *) p_child; |
943 | 0 | if( !tt_node_Match( p_node, "metadata", TT_NS ) ) |
944 | 0 | continue; |
945 | | |
946 | 0 | tt_node_t *p_imagenode = FindNode( p_nss, p_node, "image", TT_NS_SMPTE_TT_EXT, |
947 | 0 | 1, psz_id ); |
948 | 0 | if( !p_imagenode ) |
949 | 0 | continue; |
950 | | |
951 | 0 | if( !p_imagenode->p_child || p_imagenode->p_child->i_type != TT_NODE_TYPE_TEXT ) |
952 | 0 | return NULL; /* was found but empty or not text node */ |
953 | | |
954 | 0 | tt_textnode_t *p_textnode = (tt_textnode_t *) p_imagenode->p_child; |
955 | 0 | const char *psz_text = p_textnode->psz_text; |
956 | 0 | while( isspace( *psz_text ) ) |
957 | 0 | psz_text++; |
958 | 0 | return psz_text; |
959 | 0 | } |
960 | | |
961 | 0 | return NULL; |
962 | 0 | } |
963 | | |
964 | | static void ConvertNodesToRegionContent( ttml_context_t *p_ctx, tt_namespaces_t *p_nss, |
965 | | const tt_node_t *p_node, |
966 | | ttml_region_t *p_region, |
967 | | const ttml_style_t *p_upper_set_styles, |
968 | | tt_time_t playbacktime ) |
969 | 264k | { |
970 | 264k | if( tt_time_Valid( &playbacktime ) && |
971 | 264k | !tt_timings_Contains( &p_node->timings, &playbacktime ) ) |
972 | 6.18k | return; |
973 | | |
974 | 258k | const char *psz_regionid = tt_node_GetAttribute( p_nss, p_node, "region", NULL ); |
975 | | |
976 | | /* Region isn't set or is changing */ |
977 | 258k | if( psz_regionid || p_region == NULL ) |
978 | 34.6k | p_region = GetTTMLRegion( p_ctx, p_nss, psz_regionid ); |
979 | | |
980 | | /* Check for bitmap profile defined by ST2052 / SMPTE-TT */ |
981 | 258k | if( tt_node_Match( p_node, "div", TT_NS ) ) |
982 | 8.17k | { |
983 | 8.17k | const char *psz_id = tt_node_GetAttribute( p_nss, p_node, "backgroundImage", |
984 | 8.17k | TT_NS_SMPTE_TT_EXT ); |
985 | 8.17k | if( !p_region->bgbitmap.p_bytes && psz_id && *psz_id == '#' ) |
986 | 0 | { |
987 | | /* Seems SMPTE can't make diff between html and xml.. */ |
988 | 0 | const char *psz_base64 = GetSMPTEImage( p_ctx, p_nss, &psz_id[1] ); |
989 | 0 | if( psz_base64 ) |
990 | 0 | p_region->bgbitmap.i_bytes = |
991 | 0 | vlc_b64_decode_binary( &p_region->bgbitmap.p_bytes, psz_base64 ); |
992 | 0 | } |
993 | 8.17k | } |
994 | | |
995 | | /* awkward paragraph handling */ |
996 | 258k | if( tt_node_Match( p_node, "p", TT_NS ) && |
997 | 91.2k | p_region->updt.p_segments ) |
998 | 67.5k | { |
999 | 67.5k | AppendLineBreakToRegion( p_region ); |
1000 | 67.5k | } |
1001 | | |
1002 | | /* Styles from <set> element */ |
1003 | 258k | ttml_style_t *p_set_styles = (p_upper_set_styles) |
1004 | 258k | ? ttml_style_Duplicate( p_upper_set_styles ) |
1005 | 258k | : NULL; |
1006 | | |
1007 | 258k | for( const tt_basenode_t *p_child = p_node->p_child; |
1008 | 543k | p_child; p_child = p_child->p_next ) |
1009 | 284k | { |
1010 | 284k | if( p_child->i_type == TT_NODE_TYPE_TEXT ) |
1011 | 22.9k | { |
1012 | 22.9k | AppendTextToRegion( p_ctx, p_nss, (const tt_textnode_t *) p_child, |
1013 | 22.9k | p_set_styles, p_region ); |
1014 | 22.9k | } |
1015 | 261k | else if( tt_node_Match( (const tt_node_t *)p_child, "set", TT_NS ) ) |
1016 | 237 | { |
1017 | 237 | const tt_node_t *p_set = (const tt_node_t *)p_child; |
1018 | 237 | if( !tt_time_Valid( &playbacktime ) || |
1019 | 237 | tt_timings_Contains( &p_set->timings, &playbacktime ) ) |
1020 | 237 | { |
1021 | 237 | if( p_set_styles != NULL || (p_set_styles = ttml_style_New()) ) |
1022 | 237 | { |
1023 | | /* Merge with or create a local set of styles to apply to following childs */ |
1024 | 237 | DictToTTMLStyle( p_ctx, p_nss, &p_set->attr_dict, p_set_styles ); |
1025 | 237 | } |
1026 | 237 | } |
1027 | 237 | } |
1028 | 261k | else if( tt_node_Match( (const tt_node_t *)p_child, "br", TT_NS ) ) |
1029 | 6.00k | { |
1030 | 6.00k | AppendLineBreakToRegion( p_region ); |
1031 | 6.00k | } |
1032 | 255k | else |
1033 | 255k | { |
1034 | 255k | ConvertNodesToRegionContent( p_ctx, p_nss, (const tt_node_t *) p_child, |
1035 | 255k | p_region, p_set_styles, playbacktime ); |
1036 | 255k | } |
1037 | 284k | } |
1038 | | |
1039 | 258k | if( p_set_styles ) |
1040 | 237 | ttml_style_Delete( p_set_styles ); |
1041 | 258k | } |
1042 | | |
1043 | | static tt_node_t *ParseTTML( decoder_t *p_dec, tt_namespaces_t *p_nss, |
1044 | | const uint8_t *p_buffer, size_t i_buffer ) |
1045 | 24.7k | { |
1046 | 24.7k | stream_t* p_sub; |
1047 | 24.7k | xml_reader_t* p_xml_reader; |
1048 | | |
1049 | 24.7k | p_sub = vlc_stream_MemoryNew( p_dec, (uint8_t*) p_buffer, i_buffer, true ); |
1050 | 24.7k | if( unlikely( p_sub == NULL ) ) |
1051 | 0 | return NULL; |
1052 | | |
1053 | 24.7k | p_xml_reader = xml_ReaderCreate( p_dec, p_sub ); |
1054 | 24.7k | if( unlikely( p_xml_reader == NULL ) ) |
1055 | 0 | { |
1056 | 0 | vlc_stream_Delete( p_sub ); |
1057 | 0 | return NULL; |
1058 | 0 | } |
1059 | | |
1060 | 24.7k | tt_node_t *p_rootnode = NULL; |
1061 | 24.7k | if( ParseTTMLChunk( p_xml_reader, p_nss, &p_rootnode ) != VLC_SUCCESS ) |
1062 | 9.62k | { |
1063 | 9.62k | if( p_rootnode ) |
1064 | 595 | tt_node_RecursiveDelete( p_rootnode ); |
1065 | 9.62k | p_rootnode = NULL; |
1066 | 9.62k | } |
1067 | | |
1068 | 24.7k | xml_ReaderDelete( p_xml_reader ); |
1069 | 24.7k | vlc_stream_Delete( p_sub ); |
1070 | | |
1071 | 24.7k | return p_rootnode; |
1072 | 24.7k | } |
1073 | | |
1074 | | static void InitTTMLContext( tt_namespaces_t *p_nss, tt_node_t *p_rootnode, |
1075 | | ttml_context_t *p_ctx ) |
1076 | 9.42k | { |
1077 | 9.42k | p_ctx->p_rootnode = p_rootnode; |
1078 | | /* set defaults required for size/cells computation */ |
1079 | 9.42k | p_ctx->root_extent_h.i_value = 100; |
1080 | 9.42k | p_ctx->root_extent_h.unit = TTML_UNIT_PERCENT; |
1081 | 9.42k | p_ctx->root_extent_v.i_value = 100; |
1082 | 9.42k | p_ctx->root_extent_v.unit = TTML_UNIT_PERCENT; |
1083 | 9.42k | p_ctx->i_cell_resolution_v = TTML_DEFAULT_CELL_RESOLUTION_V; |
1084 | 9.42k | p_ctx->i_cell_resolution_h = TTML_DEFAULT_CELL_RESOLUTION_H; |
1085 | | /* and override them */ |
1086 | 9.42k | const char *value = tt_node_GetAttribute( p_nss, p_rootnode, "extent", TT_NS_STYLING ); |
1087 | 9.42k | if( value ) |
1088 | 4 | { |
1089 | 4 | ttml_read_coords( value, &p_ctx->root_extent_h, |
1090 | 4 | &p_ctx->root_extent_v ); |
1091 | 4 | } |
1092 | 9.42k | value = tt_node_GetAttribute( p_nss, p_rootnode, "cellResolution", TT_NS_PARAMETER ); |
1093 | 9.42k | if( value ) |
1094 | 369 | { |
1095 | 369 | unsigned w, h; |
1096 | 369 | if( sscanf( value, "%u %u", &w, &h) == 2 && w && h ) |
1097 | 227 | { |
1098 | 227 | p_ctx->i_cell_resolution_h = w; |
1099 | 227 | p_ctx->i_cell_resolution_v = h; |
1100 | 227 | } |
1101 | 369 | } |
1102 | 9.42k | } |
1103 | | |
1104 | | static ttml_region_t *GenerateRegions( tt_namespaces_t *p_nss, tt_node_t *p_rootnode, |
1105 | | tt_time_t playbacktime ) |
1106 | 10.6k | { |
1107 | 10.6k | ttml_region_t* p_regions = NULL; |
1108 | 10.6k | ttml_region_t** pp_region_last = &p_regions; |
1109 | | |
1110 | 10.6k | if( tt_node_Match( p_rootnode, "tt", TT_NS ) ) |
1111 | 10.6k | { |
1112 | 10.6k | const tt_node_t *p_bodynode = FindNode( p_nss, p_rootnode, "body", TT_NS, 1, NULL ); |
1113 | 10.6k | if( p_bodynode ) |
1114 | 9.42k | { |
1115 | 9.42k | ttml_context_t context; |
1116 | 9.42k | InitTTMLContext( p_nss, p_rootnode, &context ); |
1117 | 9.42k | context.p_rootnode = p_rootnode; |
1118 | | |
1119 | 9.42k | vlc_dictionary_init( &context.regions, 1 ); |
1120 | 9.42k | ConvertNodesToRegionContent( &context, p_nss, p_bodynode, NULL, NULL, playbacktime ); |
1121 | | |
1122 | 29.9k | for (size_t i = 0; i < context.regions.i_size; ++i) |
1123 | 20.5k | { |
1124 | 20.5k | for ( const vlc_dictionary_entry_t* p_entry = context.regions.p_entries[i]; |
1125 | 40.7k | p_entry != NULL; p_entry = p_entry->p_next ) |
1126 | 20.2k | { |
1127 | 20.2k | *pp_region_last = (ttml_region_t *) p_entry->p_value; |
1128 | 20.2k | pp_region_last = (ttml_region_t **) &(*pp_region_last)->updt.p_next; |
1129 | 20.2k | } |
1130 | 20.5k | } |
1131 | | |
1132 | 9.42k | vlc_dictionary_clear( &context.regions, NULL, NULL ); |
1133 | 9.42k | } |
1134 | 10.6k | } |
1135 | 0 | else if ( tt_node_Match( p_rootnode, "div", TT_NS ) || |
1136 | 0 | tt_node_Match( p_rootnode, "p", TT_NS ) ) |
1137 | 0 | { |
1138 | | /* TODO */ |
1139 | 0 | } |
1140 | | |
1141 | 10.6k | return p_regions; |
1142 | 10.6k | } |
1143 | | |
1144 | | static void TTMLRegionsToSpuTextRegions( decoder_t *p_dec, subpicture_t *p_spu, |
1145 | | ttml_region_t *p_regions ) |
1146 | 9.33k | { |
1147 | 9.33k | decoder_sys_t *p_dec_sys = p_dec->p_sys; |
1148 | 9.33k | subtext_updater_sys_t *p_spu_sys = p_spu->updater.sys; |
1149 | 9.33k | substext_updater_region_t *p_updtregion = NULL; |
1150 | | |
1151 | | /* Create region update info from each ttml region */ |
1152 | 9.33k | for( ttml_region_t *p_region = p_regions; |
1153 | 29.5k | p_region; p_region = (ttml_region_t *) p_region->updt.p_next ) |
1154 | 20.2k | { |
1155 | 20.2k | if( p_updtregion == NULL ) |
1156 | 9.33k | { |
1157 | 9.33k | p_updtregion = &p_spu_sys->region; |
1158 | 9.33k | } |
1159 | 10.8k | else |
1160 | 10.8k | { |
1161 | 10.8k | p_updtregion = SubpictureUpdaterSysRegionNew(); |
1162 | 10.8k | if( p_updtregion == NULL ) |
1163 | 0 | break; |
1164 | 10.8k | SubpictureUpdaterSysRegionAdd( &p_spu_sys->region, p_updtregion ); |
1165 | 10.8k | } |
1166 | | |
1167 | | /* broken legacy align var (can't handle center...). Will change only regions content. */ |
1168 | 20.2k | if( p_dec_sys->i_align & SUBPICTURE_ALIGN_MASK ) |
1169 | 0 | p_spu_sys->region.inner_align = p_dec_sys->i_align; |
1170 | 20.2k | p_updtregion->b_absolute = true; p_updtregion->b_in_window = false; |
1171 | | |
1172 | 20.2k | p_spu_sys->margin_ratio = 0.0; |
1173 | | |
1174 | | /* copy and take ownership of pointeds */ |
1175 | 20.2k | *p_updtregion = p_region->updt; |
1176 | 20.2k | p_updtregion->p_next = NULL; |
1177 | 20.2k | p_region->updt.p_region_style = NULL; |
1178 | 20.2k | p_region->updt.p_segments = NULL; |
1179 | 20.2k | } |
1180 | 9.33k | } |
1181 | | |
1182 | | static bool isValidPixelsExtent( int flags, float extent_x, float extent_y ) |
1183 | 0 | { |
1184 | 0 | return !( flags & UPDT_REGION_EXTENT_X_IS_RATIO ) && !( flags & UPDT_REGION_EXTENT_Y_IS_RATIO ) |
1185 | 0 | && extent_x > 0 && extent_y > 0; |
1186 | 0 | } |
1187 | | |
1188 | | static bool isValidPercentsExtent( int flags, float extent_x, float extent_y ) |
1189 | 0 | { |
1190 | 0 | return ( flags & UPDT_REGION_EXTENT_X_IS_RATIO ) && ( flags & UPDT_REGION_EXTENT_Y_IS_RATIO ) |
1191 | 0 | && extent_x > 0 && extent_y > 0; |
1192 | 0 | } |
1193 | | |
1194 | | static void setPixelsWidthAndHeight( video_format_t* fmt, float extent_x, float extent_y ) |
1195 | 0 | { |
1196 | 0 | fmt->i_width = extent_x; |
1197 | 0 | fmt->i_visible_width = extent_x; |
1198 | |
|
1199 | 0 | fmt->i_height = extent_y; |
1200 | 0 | fmt->i_visible_height = extent_y; |
1201 | 0 | } |
1202 | | |
1203 | | static void setPercentsWidthAndHeight( video_format_t* fmt, float extent_x, float extent_y ) |
1204 | 0 | { |
1205 | 0 | fmt->i_width = extent_x * fmt->i_width; |
1206 | 0 | fmt->i_visible_width = fmt->i_width; |
1207 | |
|
1208 | 0 | fmt->i_height = extent_y * fmt->i_height; |
1209 | 0 | fmt->i_visible_height = fmt->i_height; |
1210 | 0 | } |
1211 | | |
1212 | | static picture_t * rescalePicture( image_handler_t *p_image, picture_t *p_picture, float extent_x, float extent_y ) |
1213 | 0 | { |
1214 | 0 | video_format_t fmt_out; |
1215 | 0 | video_format_Copy( &fmt_out, &p_picture->format ); |
1216 | 0 | setPercentsWidthAndHeight( &fmt_out, extent_x, extent_y ); |
1217 | |
|
1218 | 0 | picture_t *p_scaled_picture = image_Convert( p_image, p_picture, &p_picture->format, &fmt_out ); |
1219 | |
|
1220 | 0 | video_format_Clean( &fmt_out ); |
1221 | |
|
1222 | 0 | return p_scaled_picture; |
1223 | 0 | } |
1224 | | |
1225 | | static picture_t * picture_CreateFromPNG( decoder_t *p_dec, |
1226 | | const uint8_t *p_data, size_t i_data, |
1227 | | int flags, float extent_x, float extent_y ) |
1228 | 0 | { |
1229 | 0 | if( i_data < 16 ) |
1230 | 0 | return NULL; |
1231 | | |
1232 | 0 | video_format_t fmt_out; |
1233 | 0 | video_format_Init( &fmt_out, VLC_CODEC_RGBA ); |
1234 | |
|
1235 | 0 | if ( isValidPixelsExtent( flags, extent_x, extent_y ) ) |
1236 | 0 | { |
1237 | 0 | setPixelsWidthAndHeight( &fmt_out, extent_x, extent_y ); |
1238 | 0 | } |
1239 | |
|
1240 | 0 | es_format_t es_in; |
1241 | 0 | es_format_Init( &es_in, VIDEO_ES, VLC_CODEC_PNG ); |
1242 | 0 | es_in.video.i_chroma = es_in.i_codec; |
1243 | |
|
1244 | 0 | block_t *p_block = block_Alloc( i_data ); |
1245 | 0 | if( !p_block ) |
1246 | 0 | return NULL; |
1247 | 0 | memcpy( p_block->p_buffer, p_data, i_data ); |
1248 | |
|
1249 | 0 | picture_t *p_pic = NULL; |
1250 | 0 | struct vlc_logger *logger = p_dec->obj.logger; |
1251 | 0 | bool no_interact = p_dec->obj.no_interact; |
1252 | 0 | p_dec->obj.logger = NULL; |
1253 | 0 | p_dec->obj.no_interact = true; |
1254 | 0 | image_handler_t *p_image = image_HandlerCreate( p_dec ); |
1255 | 0 | if( p_image ) |
1256 | 0 | { |
1257 | 0 | p_pic = image_Read( p_image, p_block, &es_in, &fmt_out ); |
1258 | |
|
1259 | 0 | if ( p_pic && isValidPercentsExtent( flags, extent_x, extent_y ) ) |
1260 | 0 | { |
1261 | 0 | picture_t *p_scaled_pic = rescalePicture( p_image, p_pic, extent_x, extent_y ); |
1262 | |
|
1263 | 0 | if ( p_scaled_pic ) |
1264 | 0 | { |
1265 | 0 | picture_Release( p_pic ); |
1266 | 0 | p_pic = p_scaled_pic; |
1267 | 0 | } |
1268 | 0 | } |
1269 | |
|
1270 | 0 | image_HandlerDelete( p_image ); |
1271 | 0 | } |
1272 | 0 | else block_Release( p_block ); |
1273 | 0 | p_dec->obj.no_interact = no_interact; |
1274 | 0 | p_dec->obj.logger = logger; |
1275 | 0 | es_format_Clean( &es_in ); |
1276 | 0 | video_format_Clean( &fmt_out ); |
1277 | |
|
1278 | 0 | return p_pic; |
1279 | 0 | } |
1280 | | |
1281 | | static void TTMLRegionsToSpuBitmapRegions( decoder_t *p_dec, subpicture_t *p_spu, |
1282 | | ttml_region_t *p_regions ) |
1283 | 0 | { |
1284 | | /* Create region update info from each ttml region */ |
1285 | 0 | for( ttml_region_t *p_region = p_regions; |
1286 | 0 | p_region; p_region = (ttml_region_t *) p_region->updt.p_next ) |
1287 | 0 | { |
1288 | 0 | picture_t *p_pic = picture_CreateFromPNG( p_dec, p_region->bgbitmap.p_bytes, |
1289 | 0 | p_region->bgbitmap.i_bytes, |
1290 | 0 | p_region->updt.flags, |
1291 | 0 | p_region->updt.extent.x, |
1292 | 0 | p_region->updt.extent.y ); |
1293 | 0 | if( p_pic ) |
1294 | 0 | { |
1295 | 0 | ttml_image_updater_region_t *r = TTML_ImageUpdaterRegionNew( p_pic ); |
1296 | 0 | if( !r ) |
1297 | 0 | { |
1298 | 0 | picture_Release( p_pic ); |
1299 | 0 | continue; |
1300 | 0 | } |
1301 | | /* use text updt values/flags for ease */ |
1302 | 0 | static_assert((int)UPDT_REGION_ORIGIN_X_IS_RATIO == (int)ORIGIN_X_IS_RATIO, |
1303 | 0 | "flag enums values differs"); |
1304 | 0 | static_assert((int)UPDT_REGION_EXTENT_Y_IS_RATIO == (int)EXTENT_Y_IS_RATIO, |
1305 | 0 | "flag enums values differs"); |
1306 | 0 | r->i_flags = p_region->updt.flags; |
1307 | 0 | r->origin.x = p_region->updt.origin.x; |
1308 | 0 | r->origin.y = p_region->updt.origin.y; |
1309 | 0 | r->extent.x = p_region->updt.extent.x; |
1310 | 0 | r->extent.y = p_region->updt.extent.y; |
1311 | 0 | TTML_ImageSpuAppendRegion( p_spu->updater.sys, r ); |
1312 | 0 | } |
1313 | 0 | } |
1314 | 0 | } |
1315 | | |
1316 | | static int ParseBlock( decoder_t *p_dec, const block_t *p_block ) |
1317 | 24.7k | { |
1318 | 24.7k | decoder_sys_t *p_sys = p_dec->p_sys; |
1319 | | |
1320 | 24.7k | tt_time_t *p_timings_array = NULL; |
1321 | 24.7k | size_t i_timings_count = 0; |
1322 | | |
1323 | | /* We Only support absolute timings */ |
1324 | 24.7k | tt_timings_t temporal_extent; |
1325 | 24.7k | temporal_extent.i_type = TT_TIMINGS_PARALLEL; |
1326 | 24.7k | tt_time_Init( &temporal_extent.begin ); |
1327 | 24.7k | tt_time_Init( &temporal_extent.end ); |
1328 | 24.7k | tt_time_Init( &temporal_extent.dur ); |
1329 | 24.7k | temporal_extent.begin.base = 0; |
1330 | | |
1331 | 24.7k | if( p_block->i_flags & BLOCK_FLAG_CORRUPTED ) |
1332 | 0 | return VLCDEC_SUCCESS; |
1333 | | |
1334 | | /* We cannot display a subpicture with no date */ |
1335 | 24.7k | if( p_block->i_pts == VLC_TICK_INVALID ) |
1336 | 0 | { |
1337 | 0 | msg_Warn( p_dec, "subtitle without a date" ); |
1338 | 0 | return VLCDEC_SUCCESS; |
1339 | 0 | } |
1340 | | |
1341 | 24.7k | tt_namespaces_t namespaces; |
1342 | 24.7k | tt_namespaces_Init( &namespaces ); |
1343 | 24.7k | tt_node_t *p_rootnode = ParseTTML( p_dec, &namespaces, p_block->p_buffer, p_block->i_buffer ); |
1344 | 24.7k | if( !p_rootnode ) |
1345 | 9.62k | { |
1346 | 9.62k | tt_namespaces_Clean( &namespaces ); |
1347 | 9.62k | return VLCDEC_SUCCESS; |
1348 | 9.62k | } |
1349 | | |
1350 | 15.0k | tt_timings_Resolve( (tt_basenode_t *) p_rootnode, &temporal_extent, |
1351 | 15.0k | &p_timings_array, &i_timings_count ); |
1352 | | |
1353 | | #ifdef TTML_DEBUG |
1354 | | for( size_t i=0; i<i_timings_count; i++ ) |
1355 | | printf("%ld ", tt_time_Convert( &p_timings_array[i] ) ); |
1356 | | printf("\n"); |
1357 | | #endif |
1358 | 15.0k | vlc_tick_t i_block_start_time = p_block->i_dts - p_sys->pes.i_offset; |
1359 | | |
1360 | 15.0k | if(TTML_in_PES(p_dec) && i_block_start_time < p_sys->pes.i_prev_segment_start_time ) |
1361 | 0 | i_block_start_time = p_sys->pes.i_prev_segment_start_time; |
1362 | | |
1363 | 160k | for( size_t i=0; i+1 < i_timings_count; i++ ) |
1364 | 151k | { |
1365 | | /* We Only support absolute timings (2) */ |
1366 | 151k | if( tt_time_Convert( &p_timings_array[i] ) + VLC_TICK_0 < i_block_start_time ) |
1367 | 135k | continue; |
1368 | | |
1369 | 16.7k | if( !TTML_in_PES(p_dec) && |
1370 | 16.7k | tt_time_Convert( &p_timings_array[i] ) + VLC_TICK_0 > i_block_start_time + p_block->i_length ) |
1371 | 6.09k | break; |
1372 | | |
1373 | 10.6k | if( TTML_in_PES(p_dec) && p_sys->pes.i_prev_segment_start_time < tt_time_Convert( &p_timings_array[i] ) ) |
1374 | 0 | p_sys->pes.i_prev_segment_start_time = tt_time_Convert( &p_timings_array[i] ); |
1375 | | |
1376 | 10.6k | bool b_bitmap_regions = false; |
1377 | 10.6k | subpicture_t *p_spu = NULL; |
1378 | 10.6k | ttml_region_t *p_regions = GenerateRegions( &namespaces, p_rootnode, p_timings_array[i] ); |
1379 | 10.6k | if( p_regions ) |
1380 | 9.33k | { |
1381 | 9.33k | if( p_regions->bgbitmap.i_bytes > 0 && p_regions->updt.p_segments == NULL ) |
1382 | 0 | { |
1383 | 0 | b_bitmap_regions = true; |
1384 | 0 | p_spu = decoder_NewTTML_ImageSpu( p_dec ); |
1385 | 0 | } |
1386 | 9.33k | else |
1387 | 9.33k | { |
1388 | 9.33k | p_spu = decoder_NewSubpictureText( p_dec ); |
1389 | 9.33k | } |
1390 | 9.33k | } |
1391 | | |
1392 | 10.6k | if( p_regions && p_spu ) |
1393 | 9.33k | { |
1394 | 9.33k | p_spu->i_start = p_sys->pes.i_offset + |
1395 | 9.33k | VLC_TICK_0 + tt_time_Convert( &p_timings_array[i] ); |
1396 | 9.33k | p_spu->i_stop = p_sys->pes.i_offset + |
1397 | 9.33k | VLC_TICK_0 + tt_time_Convert( &p_timings_array[i+1] ) - 1; |
1398 | 9.33k | p_spu->b_ephemer = true; |
1399 | | |
1400 | 9.33k | if( !b_bitmap_regions ) /* TEXT regions */ |
1401 | 9.33k | TTMLRegionsToSpuTextRegions( p_dec, p_spu, p_regions ); |
1402 | 0 | else /* BITMAP regions */ |
1403 | 0 | TTMLRegionsToSpuBitmapRegions( p_dec, p_spu, p_regions ); |
1404 | 9.33k | } |
1405 | | |
1406 | | /* cleanup */ |
1407 | 30.8k | while( p_regions ) |
1408 | 20.2k | { |
1409 | 20.2k | ttml_region_t *p_nextregion = (ttml_region_t *) p_regions->updt.p_next; |
1410 | 20.2k | ttml_region_Delete( p_regions ); |
1411 | 20.2k | p_regions = p_nextregion; |
1412 | 20.2k | } |
1413 | | |
1414 | 10.6k | if( p_spu ) |
1415 | 9.33k | decoder_QueueSub( p_dec, p_spu ); |
1416 | 10.6k | } |
1417 | | |
1418 | 15.0k | tt_node_RecursiveDelete( p_rootnode ); |
1419 | 15.0k | tt_namespaces_Clean( &namespaces ); |
1420 | | |
1421 | 15.0k | free( p_timings_array ); |
1422 | | |
1423 | 15.0k | return VLCDEC_SUCCESS; |
1424 | 24.7k | } |
1425 | | |
1426 | | |
1427 | | /**************************************************************************** |
1428 | | * DecodeBlock: the whole thing |
1429 | | ****************************************************************************/ |
1430 | | static int DecodeBlock( decoder_t *p_dec, block_t *p_block ) |
1431 | 52.7k | { |
1432 | 52.7k | if( p_block == NULL ) /* No Drain */ |
1433 | 28.0k | return VLCDEC_SUCCESS; |
1434 | | |
1435 | 24.7k | int ret = ParseBlock( p_dec, p_block ); |
1436 | | #ifdef TTML_DEBUG |
1437 | | if( p_block->i_buffer ) |
1438 | | { |
1439 | | p_block->p_buffer[p_block->i_buffer - 1] = 0; |
1440 | | msg_Dbg(p_dec,"time %ld %s", p_block->i_dts, p_block->p_buffer); |
1441 | | } |
1442 | | #endif |
1443 | 24.7k | block_Release( p_block ); |
1444 | 24.7k | return ret; |
1445 | 52.7k | } |
1446 | | |
1447 | | static int DecodePESBlock( decoder_t *p_dec, block_t *p_block ) |
1448 | 0 | { |
1449 | 0 | decoder_sys_t *p_sys = p_dec->p_sys; |
1450 | 0 | return ParsePESEncap( p_dec, &p_sys->pes, DecodeBlock, p_block ); |
1451 | 0 | } |
1452 | | |
1453 | | /***************************************************************************** |
1454 | | * Flush state between seeks |
1455 | | *****************************************************************************/ |
1456 | | static void Flush( decoder_t *p_dec ) |
1457 | 0 | { |
1458 | 0 | decoder_sys_t *p_sys = p_dec->p_sys; |
1459 | 0 | ttml_in_pes_Init( &p_sys->pes ); |
1460 | 0 | } |
1461 | | |
1462 | | /***************************************************************************** |
1463 | | * tt_OpenDecoder: probe the decoder and return score |
1464 | | *****************************************************************************/ |
1465 | | int tt_OpenDecoder( vlc_object_t *p_this ) |
1466 | 11.7k | { |
1467 | 11.7k | decoder_t *p_dec = (decoder_t*)p_this; |
1468 | 11.7k | decoder_sys_t *p_sys; |
1469 | | |
1470 | 11.7k | if( p_dec->fmt_in->i_codec != VLC_CODEC_TTML && |
1471 | 8.39k | !TTML_in_PES(p_dec) ) |
1472 | 8.39k | return VLC_EGENERIC; |
1473 | | |
1474 | | /* Allocate the memory needed to store the decoder's structure */ |
1475 | 3.35k | p_dec->p_sys = p_sys = vlc_obj_calloc( p_this, 1, sizeof( *p_sys ) ); |
1476 | 3.35k | if( unlikely( p_sys == NULL ) ) |
1477 | 0 | return VLC_ENOMEM; |
1478 | | |
1479 | 3.35k | if( !TTML_in_PES( p_dec ) ) |
1480 | 3.35k | p_dec->pf_decode = DecodeBlock; |
1481 | 0 | else |
1482 | 0 | p_dec->pf_decode = DecodePESBlock; |
1483 | 3.35k | p_dec->pf_flush = Flush; |
1484 | 3.35k | p_sys->i_align = var_InheritInteger( p_dec, "ttml-align" ); |
1485 | 3.35k | ttml_in_pes_Init( &p_sys->pes ); |
1486 | | |
1487 | 3.35k | return VLC_SUCCESS; |
1488 | 3.35k | } |