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_memory.h>
33 #include <vlc_memstream.h>
34 #include <vlc_es_out.h>
35 #include <vlc_charset.h>          /* FromCharset */
36 
37 #include <assert.h>
38 #include <stdlib.h>
39 #include <ctype.h>
40 
41 #include "../codec/ttml/ttml.h"
42 
43 //#define TTML_DEMUX_DEBUG
44 
45 struct demux_sys_t
46 {
47     xml_t*          p_xml;
48     xml_reader_t*   p_reader;
49     es_out_id_t*    p_es;
50     int64_t         i_next_demux_time;
51     bool            b_slave;
52     bool            b_first_time;
53 
54     tt_node_t         *p_rootnode;
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 };
70 
tt_genTiming(tt_time_t t)71 static char *tt_genTiming( tt_time_t t )
72 {
73     if( !tt_time_Valid( &t ) )
74         t.base = 0;
75     unsigned f = t.base % CLOCK_FREQ;
76     t.base /= CLOCK_FREQ;
77     unsigned h = t.base / 3600;
78     unsigned m = t.base % 3600 / 60;
79     unsigned s = t.base % 60;
80 
81     int i_ret;
82     char *psz;
83     if( f )
84     {
85         const char *lz = "000000";
86         const char *psz_lz = &lz[6];
87         /* add leading zeroes */
88         for( unsigned i=10*f; i<CLOCK_FREQ; i *= 10 )
89             psz_lz--;
90         /* strip trailing zeroes */
91         for( ; f > 0 && (f % 10) == 0; f /= 10 );
92         i_ret = asprintf( &psz, "%02u:%02u:%02u.%s%u",
93                                  h, m, s, psz_lz, f );
94     }
95     else if( t.frames )
96     {
97         i_ret = asprintf( &psz, "%02u:%02u:%02u:%s%u",
98                                  h, m, s, t.frames < 10 ? "0" : "", t.frames );
99     }
100     else
101     {
102         i_ret = asprintf( &psz, "%02u:%02u:%02u",
103                                  h, m, s );
104     }
105 
106     return i_ret < 0 ? NULL : psz;
107 }
108 
tt_MemstreamPutEntities(struct vlc_memstream * p_stream,const char * psz)109 static void tt_MemstreamPutEntities( struct vlc_memstream *p_stream, const char *psz )
110 {
111     char *psz_entities = vlc_xml_encode( psz );
112     if( psz_entities )
113     {
114         vlc_memstream_puts( p_stream, psz_entities );
115         free( psz_entities );
116     }
117 }
118 
tt_node_AttributesToText(struct vlc_memstream * p_stream,const tt_node_t * p_node)119 static void tt_node_AttributesToText( struct vlc_memstream *p_stream, const tt_node_t* p_node )
120 {
121     bool b_timed_node = false;
122     const vlc_dictionary_t* p_attr_dict = &p_node->attr_dict;
123     for( int i = 0; i < p_attr_dict->i_size; ++i )
124     {
125         for ( vlc_dictionary_entry_t* p_entry = p_attr_dict->p_entries[i];
126                                       p_entry != NULL; p_entry = p_entry->p_next )
127         {
128             const char *psz_value = NULL;
129 
130             if( !strcmp(p_entry->psz_key, "begin") ||
131                 !strcmp(p_entry->psz_key, "end") ||
132                 !strcmp(p_entry->psz_key, "dur") )
133             {
134                 b_timed_node = true;
135                 /* will remove duration */
136                 continue;
137             }
138             else if( !strcmp(p_entry->psz_key, "timeContainer") )
139             {
140                 /* also remove sequential timings info (all abs now) */
141                 continue;
142             }
143             else
144             {
145                 psz_value = p_entry->p_value;
146             }
147 
148             if( psz_value == NULL )
149                 continue;
150 
151             vlc_memstream_printf( p_stream, " %s=\"", p_entry->psz_key );
152             tt_MemstreamPutEntities( p_stream, psz_value );
153             vlc_memstream_putc( p_stream, '"' );
154         }
155     }
156 
157     if( b_timed_node )
158     {
159         if( tt_time_Valid( &p_node->timings.begin ) )
160         {
161             char *psz = tt_genTiming( p_node->timings.begin );
162             vlc_memstream_printf( p_stream, " begin=\"%s\"", psz );
163             free( psz );
164         }
165 
166         if( tt_time_Valid( &p_node->timings.end ) )
167         {
168             char *psz = tt_genTiming( p_node->timings.end );
169             vlc_memstream_printf( p_stream, " end=\"%s\"", psz );
170             free( psz );
171         }
172     }
173 }
174 
tt_node_ToText(struct vlc_memstream * p_stream,const tt_basenode_t * p_basenode,const tt_time_t * playbacktime)175 static void tt_node_ToText( struct vlc_memstream *p_stream, const tt_basenode_t *p_basenode,
176                             const tt_time_t *playbacktime )
177 {
178     if( p_basenode->i_type == TT_NODE_TYPE_ELEMENT )
179     {
180         const tt_node_t *p_node = (const tt_node_t *) p_basenode;
181 
182         if( tt_time_Valid( playbacktime ) &&
183            !tt_timings_Contains( &p_node->timings, playbacktime ) )
184             return;
185 
186         vlc_memstream_putc( p_stream, '<' );
187         tt_MemstreamPutEntities( p_stream, p_node->psz_node_name );
188 
189         tt_node_AttributesToText( p_stream, p_node );
190 
191         if( tt_node_HasChild( p_node ) )
192         {
193             vlc_memstream_putc( p_stream, '>' );
194 
195 #ifdef TTML_DEMUX_DEBUG
196             vlc_memstream_printf( p_stream, "<!-- starts %ld ends %ld -->",
197                                   tt_time_Convert( &p_node->timings.begin ),
198                                   tt_time_Convert( &p_node->timings.end ) );
199 #endif
200 
201             for( const tt_basenode_t *p_child = p_node->p_child;
202                                    p_child; p_child = p_child->p_next )
203             {
204                 tt_node_ToText( p_stream, p_child, playbacktime );
205             }
206 
207             vlc_memstream_puts( p_stream, "</" );
208             tt_MemstreamPutEntities( p_stream, p_node->psz_node_name );
209             vlc_memstream_putc( p_stream, '>' );
210         }
211         else
212             vlc_memstream_puts( p_stream, "/>" );
213     }
214     else
215     {
216         const tt_textnode_t *p_textnode = (const tt_textnode_t *) p_basenode;
217         tt_MemstreamPutEntities( p_stream, p_textnode->psz_text );
218     }
219 }
220 
Control(demux_t * p_demux,int i_query,va_list args)221 static int Control( demux_t* p_demux, int i_query, va_list args )
222 {
223     demux_sys_t *p_sys = p_demux->p_sys;
224     int64_t *pi64, i64;
225     double *pf, f;
226     bool b;
227 
228     switch( i_query )
229     {
230         case DEMUX_CAN_SEEK:
231             *va_arg( args, bool * ) = true;
232             return VLC_SUCCESS;
233         case DEMUX_GET_TIME:
234             pi64 = va_arg( args, int64_t * );
235             *pi64 = p_sys->i_next_demux_time;
236             return VLC_SUCCESS;
237         case DEMUX_SET_TIME:
238             i64 = va_arg( args, int64_t );
239             if( p_sys->times.i_count )
240             {
241                 tt_time_t t = tt_time_Create( i64 - VLC_TS_0 );
242                 size_t i_index = tt_timings_FindLowerIndex( p_sys->times.p_array,
243                                                             p_sys->times.i_count, t, &b );
244                 p_sys->times.i_current = i_index;
245                 p_sys->b_first_time = true;
246                 return VLC_SUCCESS;
247             }
248             break;
249         case DEMUX_SET_NEXT_DEMUX_TIME:
250             i64 = va_arg( args, int64_t );
251             p_sys->i_next_demux_time = i64;
252             p_sys->b_slave = true;
253             return VLC_SUCCESS;
254         case DEMUX_GET_LENGTH:
255             pi64 = va_arg( args, int64_t * );
256             if( p_sys->times.i_count )
257             {
258                 tt_time_t t = tt_time_Sub( p_sys->times.p_array[p_sys->times.i_count - 1],
259                                            p_sys->temporal_extent.begin );
260                 *pi64 = tt_time_Convert( &t );
261                 return VLC_SUCCESS;
262             }
263             break;
264         case DEMUX_GET_POSITION:
265             pf = va_arg( args, double * );
266             if( p_sys->times.i_current >= p_sys->times.i_count )
267             {
268                 *pf = 1.0;
269             }
270             else if( p_sys->times.i_count > 0 )
271             {
272                 i64 = tt_time_Convert( &p_sys->times.p_array[p_sys->times.i_count - 1] );
273                 *pf = (double) p_sys->i_next_demux_time / (i64 + 0.5);
274             }
275             else
276             {
277                 *pf = 0.0;
278             }
279             return VLC_SUCCESS;
280         case DEMUX_SET_POSITION:
281             f = va_arg( args, double );
282             if( p_sys->times.i_count )
283             {
284                 i64 = f * tt_time_Convert( &p_sys->times.p_array[p_sys->times.i_count - 1] );
285                 tt_time_t t = tt_time_Create( i64 );
286                 size_t i_index = tt_timings_FindLowerIndex( p_sys->times.p_array,
287                                                             p_sys->times.i_count, t, &b );
288                 p_sys->times.i_current = i_index;
289                 p_sys->b_first_time = true;
290                 return VLC_SUCCESS;
291             }
292             break;
293         case DEMUX_GET_PTS_DELAY:
294         case DEMUX_GET_FPS:
295         case DEMUX_GET_META:
296         case DEMUX_GET_ATTACHMENTS:
297         case DEMUX_GET_TITLE_INFO:
298         case DEMUX_HAS_UNSUPPORTED_META:
299         case DEMUX_CAN_RECORD:
300         default:
301             break;
302     }
303 
304     return VLC_EGENERIC;
305 }
306 
ReadTTML(demux_t * p_demux)307 static int ReadTTML( demux_t* p_demux )
308 {
309     demux_sys_t* p_sys = p_demux->p_sys;
310     const char* psz_node_name;
311 
312     do
313     {
314         int i_type = xml_ReaderNextNode( p_sys->p_reader, &psz_node_name );
315         bool b_empty = xml_ReaderIsEmptyElement( p_sys->p_reader );
316 
317         if( i_type <= XML_READER_NONE )
318             break;
319 
320         switch(i_type)
321         {
322             default:
323                 break;
324 
325             case XML_READER_STARTELEM:
326                 if( tt_node_NameCompare( psz_node_name, "tt" ) ||
327                     p_sys->p_rootnode != NULL )
328                     return VLC_EGENERIC;
329 
330                 p_sys->p_rootnode = tt_node_New( p_sys->p_reader, NULL, psz_node_name );
331                 if( b_empty )
332                     break;
333                 if( !p_sys->p_rootnode ||
334                     tt_nodes_Read( p_sys->p_reader, p_sys->p_rootnode ) != VLC_SUCCESS )
335                     return VLC_EGENERIC;
336                 break;
337 
338             case XML_READER_ENDELEM:
339                 if( !p_sys->p_rootnode ||
340                     tt_node_NameCompare( psz_node_name, p_sys->p_rootnode->psz_node_name ) )
341                     return VLC_EGENERIC;
342                 break;
343         }
344 
345     } while( 1 );
346 
347     if( p_sys->p_rootnode == NULL )
348         return VLC_EGENERIC;
349 
350     return VLC_SUCCESS;
351 }
352 
Demux(demux_t * p_demux)353 static int Demux( demux_t* p_demux )
354 {
355     demux_sys_t* p_sys = p_demux->p_sys;
356 
357     /* Last one must be an end time */
358     while( p_sys->times.i_current + 1 < p_sys->times.i_count &&
359            tt_time_Convert( &p_sys->times.p_array[p_sys->times.i_current] ) <= p_sys->i_next_demux_time )
360     {
361         const int64_t i_playbacktime =
362                 tt_time_Convert( &p_sys->times.p_array[p_sys->times.i_current] );
363         const int64_t i_playbackendtime =
364                 tt_time_Convert( &p_sys->times.p_array[p_sys->times.i_current + 1] ) - 1;
365 
366         if ( !p_sys->b_slave && p_sys->b_first_time )
367         {
368             es_out_SetPCR( p_demux->out, VLC_TS_0 + i_playbacktime );
369             p_sys->b_first_time = false;
370         }
371 
372         struct vlc_memstream stream;
373 
374         if( vlc_memstream_open( &stream ) )
375             return VLC_DEMUXER_EGENERIC;
376 
377         tt_node_ToText( &stream, (tt_basenode_t *) p_sys->p_rootnode,
378                         &p_sys->times.p_array[p_sys->times.i_current] );
379 
380         if( vlc_memstream_close( &stream ) == VLC_SUCCESS )
381         {
382             block_t* p_block = block_heap_Alloc( stream.ptr, stream.length );
383             if( p_block )
384             {
385                 p_block->i_dts =
386                     p_block->i_pts = VLC_TS_0 + i_playbacktime;
387                 p_block->i_length = i_playbackendtime - i_playbacktime;
388 
389                 es_out_Send( p_demux->out, p_sys->p_es, p_block );
390             }
391         }
392 
393         p_sys->times.i_current++;
394     }
395 
396     if ( !p_sys->b_slave )
397     {
398         es_out_SetPCR( p_demux->out, VLC_TS_0 + p_sys->i_next_demux_time );
399         p_sys->i_next_demux_time += CLOCK_FREQ / 8;
400     }
401 
402     if( p_sys->times.i_current + 1 >= p_sys->times.i_count )
403         return VLC_DEMUXER_EOF;
404 
405     return VLC_DEMUXER_SUCCESS;
406 }
407 
tt_OpenDemux(vlc_object_t * p_this)408 int tt_OpenDemux( vlc_object_t* p_this )
409 {
410     demux_t     *p_demux = (demux_t*)p_this;
411     demux_sys_t *p_sys;
412 
413     const uint8_t *p_peek;
414     ssize_t i_peek = vlc_stream_Peek( p_demux->s, &p_peek, 2048 );
415     if( unlikely( i_peek <= 32 ) )
416         return VLC_EGENERIC;
417 
418     const char *psz_xml = (const char *) p_peek;
419     size_t i_xml  = i_peek;
420 
421     /* Try to probe without xml module/loading the full document */
422     char *psz_alloc = NULL;
423     switch( GetQWBE(p_peek) )
424     {
425         /* See RFC 3023 Part 4 */
426         case UINT64_C(0xFFFE3C003F007800): /* UTF16 BOM<? */
427         case UINT64_C(0xFFFE3C003F007400): /* UTF16 BOM<t */
428         case UINT64_C(0xFEFF003C003F0078): /* UTF16 BOM<? */
429         case UINT64_C(0xFEFF003C003F0074): /* UTF16 BOM<t */
430             psz_alloc = FromCharset( "UTF-16", p_peek, i_peek );
431             break;
432         case UINT64_C(0x3C003F0078006D00): /* UTF16-LE <?xm */
433         case UINT64_C(0x3C003F0074007400): /* UTF16-LE <tt */
434             psz_alloc = FromCharset( "UTF-16LE", p_peek, i_peek );
435             break;
436         case UINT64_C(0x003C003F0078006D): /* UTF16-BE <?xm */
437         case UINT64_C(0x003C003F00740074): /* UTF16-BE <tt */
438             psz_alloc = FromCharset( "UTF-16BE", p_peek, i_peek );
439             break;
440         case UINT64_C(0xEFBBBF3C3F786D6C): /* UTF8 BOM<?xml */
441         case UINT64_C(0x3C3F786D6C207665): /* UTF8 <?xml ve */
442         case UINT64_C(0xEFBBBF3C74742078): /* UTF8 BOM<tt x*/
443             break;
444         default:
445             if(GetDWBE(p_peek) != UINT32_C(0x3C747420)) /* tt node without xml document marker */
446                 return VLC_EGENERIC;
447     }
448 
449     if( psz_alloc )
450     {
451         psz_xml = psz_alloc;
452         i_xml = strlen( psz_alloc );
453     }
454 
455     /* Simplified probing. Valid TTML must have a namespace declaration */
456     const char *psz_tt = strnstr( psz_xml, "tt", i_xml );
457     if( !psz_tt || psz_tt == psz_xml ||
458         ((size_t)(&psz_tt[2] - (const char*)p_peek)) == i_xml || isalpha(psz_tt[2]) ||
459         (psz_tt[-1] != ':' && psz_tt[-1] != '<') )
460     {
461         free( psz_alloc );
462         return VLC_EGENERIC;
463     }
464     else
465     {
466         const char * const rgsz[] =
467         {
468             "=\"http://www.w3.org/ns/ttml\"",
469             "=\"http://www.w3.org/2004/11/ttaf1\"",
470             "=\"http://www.w3.org/2006/04/ttaf1\"",
471             "=\"http://www.w3.org/2006/10/ttaf1\"",
472         };
473         const char *psz_ns = NULL;
474         for( size_t i=0; i<ARRAY_SIZE(rgsz) && !psz_ns; i++ )
475         {
476             psz_ns = strnstr( psz_xml, rgsz[i],
477                               i_xml - (psz_tt - psz_xml) );
478         }
479         free( psz_alloc );
480         if( !psz_ns )
481             return VLC_EGENERIC;
482     }
483 
484     p_demux->p_sys = p_sys = calloc( 1, sizeof( *p_sys ) );
485     if( unlikely( p_sys == NULL ) )
486         return VLC_ENOMEM;
487 
488     p_sys->b_first_time = true;
489     p_sys->temporal_extent.i_type = TT_TIMINGS_PARALLEL;
490     tt_time_Init( &p_sys->temporal_extent.begin );
491     tt_time_Init( &p_sys->temporal_extent.end );
492     tt_time_Init( &p_sys->temporal_extent.dur );
493     p_sys->temporal_extent.begin.base = 0;
494 
495     p_sys->p_xml = xml_Create( p_demux );
496     if( !p_sys->p_xml )
497         goto error;
498 
499     p_sys->p_reader = xml_ReaderCreate( p_sys->p_xml, p_demux->s );
500     if( !p_sys->p_reader )
501         goto error;
502 
503 #ifndef TTML_DEMUX_DEBUG
504     p_sys->p_reader->obj.flags |= OBJECT_FLAGS_QUIET;
505 #endif
506 
507     if( ReadTTML( p_demux ) != VLC_SUCCESS )
508         goto error;
509 
510     tt_timings_Resolve( (tt_basenode_t *) p_sys->p_rootnode, &p_sys->temporal_extent,
511                         &p_sys->times.p_array, &p_sys->times.i_count );
512 
513 #ifdef TTML_DEMUX_DEBUG
514     {
515         struct vlc_memstream stream;
516 
517         if( vlc_memstream_open( &stream ) )
518             goto error;
519 
520         tt_time_t t;
521         tt_time_Init( &t );
522         tt_node_ToText( &stream, (tt_basenode_t*)p_sys->p_rootnode, &t /* invalid */ );
523 
524         vlc_memstream_putc( &stream, '\0' );
525 
526         if( vlc_memstream_close( &stream ) == VLC_SUCCESS )
527         {
528             msg_Dbg( p_demux, "%s", stream.ptr );
529             free( stream.ptr );
530         }
531     }
532 #endif
533 
534     p_demux->pf_demux = Demux;
535     p_demux->pf_control = Control;
536 
537     es_format_t fmt;
538     es_format_Init( &fmt, SPU_ES, VLC_CODEC_TTML );
539     p_sys->p_es = es_out_Add( p_demux->out, &fmt );
540     if( !p_sys->p_es )
541         goto error;
542 
543     es_format_Clean( &fmt );
544 
545     return VLC_SUCCESS;
546 
547 error:
548     tt_CloseDemux( p_demux );
549 
550     return VLC_EGENERIC;
551 }
552 
tt_CloseDemux(demux_t * p_demux)553 void tt_CloseDemux( demux_t* p_demux )
554 {
555     demux_sys_t* p_sys = p_demux->p_sys;
556 
557     if( p_sys->p_rootnode )
558         tt_node_RecursiveDelete( p_sys->p_rootnode );
559 
560     if( p_sys->p_es )
561         es_out_Del( p_demux->out, p_sys->p_es );
562 
563     if( p_sys->p_reader )
564         xml_ReaderDelete( p_sys->p_reader );
565 
566     if( p_sys->p_xml )
567         xml_Delete( p_sys->p_xml );
568 
569     free( p_sys->times.p_array );
570 
571     free( p_sys );
572 }
573