/src/vlc/modules/demux/ttml.c
Line | Count | Source |
1 | | /***************************************************************************** |
2 | | * ttml.c : TTML subtitles demux |
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 | | |
24 | | #ifdef HAVE_CONFIG_H |
25 | | # include "config.h" |
26 | | #endif |
27 | | |
28 | | #include <vlc_common.h> |
29 | | #include <vlc_demux.h> |
30 | | #include <vlc_xml.h> |
31 | | #include <vlc_strings.h> |
32 | | #include <vlc_memstream.h> |
33 | | #include <vlc_es_out.h> |
34 | | #include <vlc_charset.h> /* FromCharset */ |
35 | | |
36 | | #include <assert.h> |
37 | | #include <stdlib.h> |
38 | | #include <ctype.h> |
39 | | |
40 | | #include "../codec/ttml/ttml.h" |
41 | | |
42 | | //#define TTML_DEMUX_DEBUG |
43 | | |
44 | | typedef struct |
45 | | { |
46 | | xml_t* p_xml; |
47 | | xml_reader_t* p_reader; |
48 | | es_out_id_t* p_es; |
49 | | vlc_tick_t i_next_demux_time; |
50 | | bool b_slave; |
51 | | bool b_first_time; |
52 | | |
53 | | tt_node_t *p_rootnode; |
54 | | tt_namespaces_t namespaces; |
55 | | |
56 | | tt_timings_t temporal_extent; |
57 | | |
58 | | /* |
59 | | * All timings are stored unique and ordered. |
60 | | * Being begin or end times of sub sequence, |
61 | | * we use them as 'point of change' for output filtering. |
62 | | */ |
63 | | struct |
64 | | { |
65 | | tt_time_t *p_array; |
66 | | size_t i_count; |
67 | | size_t i_current; |
68 | | } times; |
69 | | } demux_sys_t; |
70 | | |
71 | | static int Control( demux_t* p_demux, int i_query, va_list args ) |
72 | 0 | { |
73 | 0 | demux_sys_t *p_sys = p_demux->p_sys; |
74 | 0 | vlc_tick_t i64; |
75 | 0 | double *pf, f; |
76 | 0 | bool b; |
77 | |
|
78 | 0 | switch( i_query ) |
79 | 0 | { |
80 | 0 | case DEMUX_CAN_SEEK: |
81 | 0 | *va_arg( args, bool * ) = true; |
82 | 0 | return VLC_SUCCESS; |
83 | 0 | case DEMUX_GET_TIME: |
84 | 0 | *va_arg( args, vlc_tick_t * ) = p_sys->i_next_demux_time; |
85 | 0 | return VLC_SUCCESS; |
86 | 0 | case DEMUX_SET_TIME: |
87 | 0 | if( p_sys->times.i_count ) |
88 | 0 | { |
89 | 0 | tt_time_t t = tt_time_Create( va_arg( args, vlc_tick_t ) - VLC_TICK_0 ); |
90 | 0 | size_t i_index = tt_timings_FindLowerIndex( p_sys->times.p_array, |
91 | 0 | p_sys->times.i_count, t, &b ); |
92 | 0 | p_sys->times.i_current = i_index; |
93 | 0 | p_sys->b_first_time = true; |
94 | 0 | return VLC_SUCCESS; |
95 | 0 | } |
96 | 0 | break; |
97 | 0 | case DEMUX_SET_NEXT_DEMUX_TIME: |
98 | 0 | p_sys->i_next_demux_time = va_arg( args, vlc_tick_t ); |
99 | 0 | p_sys->b_slave = true; |
100 | 0 | return VLC_SUCCESS; |
101 | 0 | case DEMUX_GET_LENGTH: |
102 | 0 | if( p_sys->times.i_count ) |
103 | 0 | { |
104 | 0 | tt_time_t t = tt_time_Sub( p_sys->times.p_array[p_sys->times.i_count - 1], |
105 | 0 | p_sys->temporal_extent.begin ); |
106 | 0 | *va_arg( args, vlc_tick_t * ) = tt_time_Convert( &t ); |
107 | 0 | return VLC_SUCCESS; |
108 | 0 | } |
109 | 0 | break; |
110 | 0 | case DEMUX_GET_POSITION: |
111 | 0 | pf = va_arg( args, double * ); |
112 | 0 | if( p_sys->times.i_current >= p_sys->times.i_count ) |
113 | 0 | { |
114 | 0 | *pf = 1.0; |
115 | 0 | } |
116 | 0 | else if( p_sys->times.i_count > 0 ) |
117 | 0 | { |
118 | 0 | i64 = tt_time_Convert( &p_sys->times.p_array[p_sys->times.i_count - 1] ); |
119 | 0 | *pf = (double) p_sys->i_next_demux_time / (i64 + VLC_TICK_FROM_MS(500)); |
120 | 0 | } |
121 | 0 | else |
122 | 0 | { |
123 | 0 | *pf = 0.0; |
124 | 0 | } |
125 | 0 | return VLC_SUCCESS; |
126 | 0 | case DEMUX_SET_POSITION: |
127 | 0 | f = va_arg( args, double ); |
128 | 0 | if( p_sys->times.i_count ) |
129 | 0 | { |
130 | 0 | i64 = f * tt_time_Convert( &p_sys->times.p_array[p_sys->times.i_count - 1] ); |
131 | 0 | tt_time_t t = tt_time_Create( i64 ); |
132 | 0 | size_t i_index = tt_timings_FindLowerIndex( p_sys->times.p_array, |
133 | 0 | p_sys->times.i_count, t, &b ); |
134 | 0 | p_sys->times.i_current = i_index; |
135 | 0 | p_sys->b_first_time = true; |
136 | 0 | return VLC_SUCCESS; |
137 | 0 | } |
138 | 0 | break; |
139 | 0 | case DEMUX_CAN_PAUSE: |
140 | 0 | case DEMUX_SET_PAUSE_STATE: |
141 | 0 | case DEMUX_CAN_CONTROL_PACE: |
142 | 0 | return demux_vaControlHelper( p_demux->s, 0, -1, 0, 1, i_query, args ); |
143 | | |
144 | 0 | case DEMUX_GET_PTS_DELAY: |
145 | 0 | case DEMUX_GET_FPS: |
146 | 0 | case DEMUX_GET_META: |
147 | 0 | case DEMUX_GET_ATTACHMENTS: |
148 | 0 | case DEMUX_GET_TITLE_INFO: |
149 | 0 | case DEMUX_HAS_UNSUPPORTED_META: |
150 | 0 | case DEMUX_CAN_RECORD: |
151 | 0 | default: |
152 | 0 | break; |
153 | 0 | } |
154 | | |
155 | 0 | return VLC_EGENERIC; |
156 | 0 | } |
157 | | |
158 | | static int ReadTTML( demux_t* p_demux ) |
159 | 1.95k | { |
160 | 1.95k | demux_sys_t* p_sys = p_demux->p_sys; |
161 | 1.95k | const char* psz_node_name, *psz_node_namespace; |
162 | | |
163 | 1.95k | do |
164 | 3.89k | { |
165 | 3.89k | int i_type = xml_ReaderNextNodeNS( p_sys->p_reader, &psz_node_name, &psz_node_namespace ); |
166 | 3.89k | bool b_empty = xml_ReaderIsEmptyElement( p_sys->p_reader ); |
167 | | |
168 | 3.89k | if( i_type <= XML_READER_NONE ) |
169 | 1.94k | break; |
170 | | |
171 | 1.94k | switch(i_type) |
172 | 1.94k | { |
173 | 0 | default: |
174 | 0 | break; |
175 | | |
176 | 1.94k | case XML_READER_STARTELEM: |
177 | 1.94k | if( (psz_node_namespace && strcmp( psz_node_namespace, TT_NS )) || |
178 | 1.94k | strcmp( tt_LocalName( psz_node_name ), "tt" ) || |
179 | 1.94k | p_sys->p_rootnode != NULL ) |
180 | 8 | return VLC_EGENERIC; |
181 | | |
182 | 1.94k | p_sys->p_rootnode = tt_node_NewRead( p_sys->p_reader, &p_sys->namespaces, NULL, |
183 | 1.94k | psz_node_name, |
184 | 1.94k | psz_node_namespace ); |
185 | 1.94k | if( b_empty ) |
186 | 0 | break; |
187 | 1.94k | if( !p_sys->p_rootnode || |
188 | 1.94k | tt_nodes_Read( p_sys->p_reader, |
189 | 1.94k | &p_sys->namespaces, p_sys->p_rootnode ) != VLC_SUCCESS ) |
190 | 0 | return VLC_EGENERIC; |
191 | 1.94k | break; |
192 | | |
193 | 1.94k | case XML_READER_ENDELEM: |
194 | 0 | if( !p_sys->p_rootnode || |
195 | 0 | strcmp( psz_node_name, p_sys->p_rootnode->psz_node_name ) ) |
196 | 0 | return VLC_EGENERIC; |
197 | 0 | break; |
198 | 1.94k | } |
199 | | |
200 | 1.94k | } while( 1 ); |
201 | | |
202 | 1.94k | if( p_sys->p_rootnode == NULL ) |
203 | 1 | return VLC_EGENERIC; |
204 | | |
205 | 1.94k | return VLC_SUCCESS; |
206 | 1.94k | } |
207 | | |
208 | | static int Demux( demux_t* p_demux ) |
209 | 245M | { |
210 | 245M | demux_sys_t* p_sys = p_demux->p_sys; |
211 | | |
212 | | /* Last one must be an end time */ |
213 | 245M | while( p_sys->times.i_current + 1 < p_sys->times.i_count && |
214 | 245M | tt_time_Convert( &p_sys->times.p_array[p_sys->times.i_current] ) <= p_sys->i_next_demux_time ) |
215 | 21.2k | { |
216 | 21.2k | const vlc_tick_t i_playbacktime = |
217 | 21.2k | tt_time_Convert( &p_sys->times.p_array[p_sys->times.i_current] ); |
218 | 21.2k | const vlc_tick_t i_playbackendtime = |
219 | 21.2k | tt_time_Convert( &p_sys->times.p_array[p_sys->times.i_current + 1] ) - 1; |
220 | | |
221 | 21.2k | if ( !p_sys->b_slave && p_sys->b_first_time ) |
222 | 1.92k | { |
223 | 1.92k | es_out_SetPCR( p_demux->out, VLC_TICK_0 + i_playbacktime ); |
224 | 1.92k | p_sys->b_first_time = false; |
225 | 1.92k | } |
226 | | |
227 | 21.2k | struct vlc_memstream stream; |
228 | | |
229 | 21.2k | if( vlc_memstream_open( &stream ) ) |
230 | 0 | return VLC_DEMUXER_EGENERIC; |
231 | | |
232 | 21.2k | tt_node_ToText( &stream, (tt_basenode_t *) p_sys->p_rootnode, |
233 | 21.2k | &p_sys->times.p_array[p_sys->times.i_current] ); |
234 | | |
235 | 21.2k | if( vlc_memstream_close( &stream ) == 0 ) |
236 | 21.2k | { |
237 | 21.2k | block_t* p_block = block_heap_Alloc( stream.ptr, stream.length ); |
238 | 21.2k | if( p_block ) |
239 | 21.2k | { |
240 | 21.2k | p_block->i_dts = |
241 | 21.2k | p_block->i_pts = VLC_TICK_0 + i_playbacktime; |
242 | 21.2k | p_block->i_length = i_playbackendtime - i_playbacktime; |
243 | | |
244 | 21.2k | es_out_Send( p_demux->out, p_sys->p_es, p_block ); |
245 | 21.2k | } |
246 | 21.2k | } |
247 | | |
248 | 21.2k | p_sys->times.i_current++; |
249 | 21.2k | } |
250 | | |
251 | 245M | if ( !p_sys->b_slave ) |
252 | 245M | { |
253 | 245M | es_out_SetPCR( p_demux->out, VLC_TICK_0 + p_sys->i_next_demux_time ); |
254 | 245M | p_sys->i_next_demux_time += VLC_TICK_FROM_MS(125); |
255 | 245M | } |
256 | | |
257 | 245M | if( p_sys->times.i_current + 1 >= p_sys->times.i_count ) |
258 | 1.94k | return VLC_DEMUXER_EOF; |
259 | | |
260 | 245M | return VLC_DEMUXER_SUCCESS; |
261 | 245M | } |
262 | | |
263 | | int tt_OpenDemux( vlc_object_t* p_this ) |
264 | 2.43k | { |
265 | 2.43k | demux_t *p_demux = (demux_t*)p_this; |
266 | 2.43k | demux_sys_t *p_sys; |
267 | | |
268 | 2.43k | const uint8_t *p_peek; |
269 | 2.43k | ssize_t i_peek = vlc_stream_Peek( p_demux->s, &p_peek, 2048 ); |
270 | 2.43k | if( unlikely( i_peek <= 32 ) ) |
271 | 0 | return VLC_EGENERIC; |
272 | | |
273 | 2.43k | const char *psz_xml = (const char *) p_peek; |
274 | 2.43k | size_t i_xml = i_peek; |
275 | | |
276 | | /* Try to probe without xml module/loading the full document */ |
277 | 2.43k | char *psz_alloc = NULL; |
278 | 2.43k | switch( GetQWBE(p_peek) ) |
279 | 2.43k | { |
280 | | /* See RFC 3023 Part 4 */ |
281 | 0 | case UINT64_C(0xFFFE3C003F007800): /* UTF16 BOM<? */ |
282 | 0 | case UINT64_C(0xFFFE3C003F007400): /* UTF16 BOM<t */ |
283 | 0 | case UINT64_C(0xFEFF003C003F0078): /* UTF16 BOM<? */ |
284 | 0 | case UINT64_C(0xFEFF003C003F0074): /* UTF16 BOM<t */ |
285 | 0 | psz_alloc = FromCharset( "UTF-16", p_peek, i_peek ); |
286 | 0 | break; |
287 | 0 | case UINT64_C(0x3C003F0078006D00): /* UTF16-LE <?xm */ |
288 | 8 | case UINT64_C(0x3C003F0074007400): /* UTF16-LE <tt */ |
289 | 8 | psz_alloc = FromCharset( "UTF-16LE", p_peek, i_peek ); |
290 | 8 | break; |
291 | 0 | case UINT64_C(0x003C003F0078006D): /* UTF16-BE <?xm */ |
292 | 2 | case UINT64_C(0x003C003F00740074): /* UTF16-BE <tt */ |
293 | 2 | psz_alloc = FromCharset( "UTF-16BE", p_peek, i_peek ); |
294 | 2 | break; |
295 | 0 | case UINT64_C(0xEFBBBF3C3F786D6C): /* UTF8 BOM<?xml */ |
296 | 1.41k | case UINT64_C(0x3C3F786D6C207665): /* UTF8 <?xml ve */ |
297 | 1.41k | case UINT64_C(0xEFBBBF3C74742078): /* UTF8 BOM<tt x*/ |
298 | 1.41k | break; |
299 | 1.00k | default: |
300 | 1.00k | if(GetDWBE(p_peek) != UINT32_C(0x3C747420)) /* tt node without xml document marker */ |
301 | 472 | return VLC_EGENERIC; |
302 | 2.43k | } |
303 | | |
304 | 1.96k | if( psz_alloc ) |
305 | 10 | { |
306 | 10 | psz_xml = psz_alloc; |
307 | 10 | i_xml = strlen( psz_alloc ); |
308 | 10 | } |
309 | | |
310 | | /* Simplified probing. Valid TTML must have a namespace declaration */ |
311 | 1.96k | const char *psz_tt = strnstr( psz_xml, "tt", i_xml ); |
312 | 1.96k | if( !psz_tt || psz_tt == psz_xml || |
313 | 1.96k | ((size_t)(&psz_tt[2] - (const char*)p_peek)) == i_xml || isalpha(psz_tt[2]) || |
314 | 1.96k | (psz_tt[-1] != ':' && psz_tt[-1] != '<') ) |
315 | 10 | { |
316 | 10 | free( psz_alloc ); |
317 | 10 | return VLC_EGENERIC; |
318 | 10 | } |
319 | 1.95k | else |
320 | 1.95k | { |
321 | 1.95k | const char * const rgsz[] = |
322 | 1.95k | { |
323 | 1.95k | "=\"http://www.w3.org/ns/ttml\"", |
324 | 1.95k | "=\"http://www.w3.org/2004/11/ttaf1\"", |
325 | 1.95k | "=\"http://www.w3.org/2006/04/ttaf1\"", |
326 | 1.95k | "=\"http://www.w3.org/2006/10/ttaf1\"", |
327 | 1.95k | }; |
328 | 1.95k | const char *psz_ns = NULL; |
329 | 3.93k | for( size_t i=0; i<ARRAY_SIZE(rgsz) && !psz_ns; i++ ) |
330 | 1.98k | { |
331 | 1.98k | psz_ns = strnstr( psz_xml, rgsz[i], |
332 | 1.98k | i_xml - (psz_tt - psz_xml) ); |
333 | 1.98k | } |
334 | 1.95k | free( psz_alloc ); |
335 | 1.95k | if( !psz_ns ) |
336 | 1 | return VLC_EGENERIC; |
337 | 1.95k | } |
338 | | |
339 | 1.95k | p_demux->p_sys = p_sys = calloc( 1, sizeof( *p_sys ) ); |
340 | 1.95k | if( unlikely( p_sys == NULL ) ) |
341 | 0 | return VLC_ENOMEM; |
342 | | |
343 | 1.95k | p_sys->b_first_time = true; |
344 | 1.95k | p_sys->temporal_extent.i_type = TT_TIMINGS_PARALLEL; |
345 | 1.95k | tt_time_Init( &p_sys->temporal_extent.begin ); |
346 | 1.95k | tt_time_Init( &p_sys->temporal_extent.end ); |
347 | 1.95k | tt_time_Init( &p_sys->temporal_extent.dur ); |
348 | 1.95k | p_sys->temporal_extent.begin.base = 0; |
349 | 1.95k | tt_namespaces_Init( &p_sys->namespaces ); |
350 | | |
351 | 1.95k | p_sys->p_xml = xml_Create( p_demux ); |
352 | 1.95k | if( !p_sys->p_xml ) |
353 | 0 | goto error; |
354 | | |
355 | 1.95k | p_sys->p_reader = xml_ReaderCreate( p_sys->p_xml, p_demux->s ); |
356 | 1.95k | if( !p_sys->p_reader ) |
357 | 0 | goto error; |
358 | | |
359 | 1.95k | #ifndef TTML_DEMUX_DEBUG |
360 | 1.95k | p_sys->p_reader->obj.logger = NULL; |
361 | 1.95k | #endif |
362 | | |
363 | 1.95k | if( ReadTTML( p_demux ) != VLC_SUCCESS ) |
364 | 9 | goto error; |
365 | | |
366 | 1.94k | tt_timings_Resolve( (tt_basenode_t *) p_sys->p_rootnode, &p_sys->temporal_extent, |
367 | 1.94k | &p_sys->times.p_array, &p_sys->times.i_count ); |
368 | | |
369 | | #ifdef TTML_DEMUX_DEBUG |
370 | | { |
371 | | struct vlc_memstream stream; |
372 | | |
373 | | if( vlc_memstream_open( &stream ) ) |
374 | | goto error; |
375 | | |
376 | | tt_time_t t; |
377 | | tt_time_Init( &t ); |
378 | | tt_node_ToText( &stream, (tt_basenode_t*)p_sys->p_rootnode, &t /* invalid */ ); |
379 | | |
380 | | vlc_memstream_putc( &stream, '\0' ); |
381 | | |
382 | | if( vlc_memstream_close( &stream ) == 0 ) |
383 | | { |
384 | | msg_Dbg( p_demux, "%s", stream.ptr ); |
385 | | free( stream.ptr ); |
386 | | } |
387 | | } |
388 | | #endif |
389 | | |
390 | 1.94k | p_demux->pf_demux = Demux; |
391 | 1.94k | p_demux->pf_control = Control; |
392 | | |
393 | 1.94k | es_format_t fmt; |
394 | 1.94k | es_format_Init( &fmt, SPU_ES, VLC_CODEC_TTML ); |
395 | 1.94k | fmt.i_id = 0; |
396 | 1.94k | p_sys->p_es = es_out_Add( p_demux->out, &fmt ); |
397 | 1.94k | if( !p_sys->p_es ) |
398 | 0 | goto error; |
399 | | |
400 | 1.94k | es_format_Clean( &fmt ); |
401 | | |
402 | 1.94k | return VLC_SUCCESS; |
403 | | |
404 | 9 | error: |
405 | 9 | tt_CloseDemux( p_this ); |
406 | | |
407 | 9 | return VLC_EGENERIC; |
408 | 1.94k | } |
409 | | |
410 | | void tt_CloseDemux( vlc_object_t* p_this ) |
411 | 1.95k | { |
412 | 1.95k | demux_t *p_demux = (demux_t *)p_this; |
413 | 1.95k | demux_sys_t* p_sys = p_demux->p_sys; |
414 | | |
415 | 1.95k | if( p_sys->p_rootnode ) |
416 | 1.94k | tt_node_RecursiveDelete( p_sys->p_rootnode ); |
417 | | |
418 | 1.95k | if( p_sys->p_es ) |
419 | 1.94k | es_out_Del( p_demux->out, p_sys->p_es ); |
420 | | |
421 | 1.95k | if( p_sys->p_reader ) |
422 | 1.95k | xml_ReaderDelete( p_sys->p_reader ); |
423 | | |
424 | 1.95k | if( p_sys->p_xml ) |
425 | 1.95k | xml_Delete( p_sys->p_xml ); |
426 | | |
427 | 1.95k | tt_namespaces_Clean( &p_sys->namespaces ); |
428 | | |
429 | 1.95k | free( p_sys->times.p_array ); |
430 | | |
431 | 1.95k | free( p_sys ); |
432 | 1.95k | } |