/*****************************************************************************
* ttml.c : TTML subtitles demux
*****************************************************************************
* Copyright (C) 2015-2017 VLC authors and VideoLAN
*
* Authors: Hugo Beauzée-Luyssen <hugo@beauzee.fr>
* Sushma Reddy <sushma.reddy@research.iiit.ac.in>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <vlc_common.h>
#include <vlc_demux.h>
#include <vlc_xml.h>
#include <vlc_strings.h>
#include <vlc_memstream.h>
#include <vlc_es_out.h>
#include <vlc_charset.h> /* FromCharset */
#include <assert.h>
#include <stdlib.h>
#include <ctype.h>
#include "../codec/ttml/ttml.h"
//#define TTML_DEMUX_DEBUG
typedef struct
{
xml_t* p_xml;
xml_reader_t* p_reader;
es_out_id_t* p_es;
vlc_tick_t i_next_demux_time;
bool b_slave;
bool b_first_time;
tt_node_t *p_rootnode;
tt_namespaces_t namespaces;
tt_timings_t temporal_extent;
/*
* All timings are stored unique and ordered.
* Being begin or end times of sub sequence,
* we use them as 'point of change' for output filtering.
*/
struct
{
tt_time_t *p_array;
size_t i_count;
size_t i_current;
} times;
} demux_sys_t;
static int Control( demux_t* p_demux, int i_query, va_list args )
{
demux_sys_t *p_sys = p_demux->p_sys;
vlc_tick_t i64;
double *pf, f;
bool b;
switch( i_query )
{
case DEMUX_CAN_SEEK:
*va_arg( args, bool * ) = true;
return VLC_SUCCESS;
case DEMUX_GET_TIME:
*va_arg( args, vlc_tick_t * ) = p_sys->i_next_demux_time;
return VLC_SUCCESS;
case DEMUX_SET_TIME:
if( p_sys->times.i_count )
{
tt_time_t t = tt_time_Create( va_arg( args, vlc_tick_t ) - VLC_TICK_0 );
size_t i_index = tt_timings_FindLowerIndex( p_sys->times.p_array,
p_sys->times.i_count, t, &b );
p_sys->times.i_current = i_index;
p_sys->b_first_time = true;
return VLC_SUCCESS;
}
break;
case DEMUX_SET_NEXT_DEMUX_TIME:
p_sys->i_next_demux_time = va_arg( args, vlc_tick_t );
p_sys->b_slave = true;
return VLC_SUCCESS;
case DEMUX_GET_LENGTH:
if( p_sys->times.i_count )
{
tt_time_t t = tt_time_Sub( p_sys->times.p_array[p_sys->times.i_count - 1],
p_sys->temporal_extent.begin );
*va_arg( args, vlc_tick_t * ) = tt_time_Convert( &t );
return VLC_SUCCESS;
}
break;
case DEMUX_GET_POSITION:
pf = va_arg( args, double * );
if( p_sys->times.i_current >= p_sys->times.i_count )
{
*pf = 1.0;
}
else if( p_sys->times.i_count > 0 )
{
i64 = tt_time_Convert( &p_sys->times.p_array[p_sys->times.i_count - 1] );
*pf = (double) p_sys->i_next_demux_time / (i64 + VLC_TICK_FROM_MS(500));
}
else
{
*pf = 0.0;
}
return VLC_SUCCESS;
case DEMUX_SET_POSITION:
f = va_arg( args, double );
if( p_sys->times.i_count )
{
i64 = f * tt_time_Convert( &p_sys->times.p_array[p_sys->times.i_count - 1] );
tt_time_t t = tt_time_Create( i64 );
size_t i_index = tt_timings_FindLowerIndex( p_sys->times.p_array,
p_sys->times.i_count, t, &b );
p_sys->times.i_current = i_index;
p_sys->b_first_time = true;
return VLC_SUCCESS;
}
break;
case DEMUX_CAN_PAUSE:
case DEMUX_SET_PAUSE_STATE:
case DEMUX_CAN_CONTROL_PACE:
return demux_vaControlHelper( p_demux->s, 0, -1, 0, 1, i_query, args );
case DEMUX_GET_PTS_DELAY:
case DEMUX_GET_FPS:
case DEMUX_GET_META:
case DEMUX_GET_ATTACHMENTS:
case DEMUX_GET_TITLE_INFO:
case DEMUX_HAS_UNSUPPORTED_META:
case DEMUX_CAN_RECORD:
default:
break;
}
return VLC_EGENERIC;
}
static int ReadTTML( demux_t* p_demux )
{
demux_sys_t* p_sys = p_demux->p_sys;
const char* psz_node_name, *psz_node_namespace;
do
{
int i_type = xml_ReaderNextNodeNS( p_sys->p_reader, &psz_node_name, &psz_node_namespace );
bool b_empty = xml_ReaderIsEmptyElement( p_sys->p_reader );
if( i_type <= XML_READER_NONE )
break;
switch(i_type)
{
default:
break;
case XML_READER_STARTELEM:
if( (psz_node_namespace && strcmp( psz_node_namespace, TT_NS )) ||
strcmp( tt_LocalName( psz_node_name ), "tt" ) ||
p_sys->p_rootnode != NULL )
return VLC_EGENERIC;
p_sys->p_rootnode = tt_node_NewRead( p_sys->p_reader, &p_sys->namespaces, NULL,
psz_node_name,
psz_node_namespace );
if( b_empty )
break;
if( !p_sys->p_rootnode ||
tt_nodes_Read( p_sys->p_reader,
&p_sys->namespaces, p_sys->p_rootnode ) != VLC_SUCCESS )
return VLC_EGENERIC;
break;
case XML_READER_ENDELEM:
if( !p_sys->p_rootnode ||
strcmp( psz_node_name, p_sys->p_rootnode->psz_node_name ) )
return VLC_EGENERIC;
break;
}
} while( 1 );
if( p_sys->p_rootnode == NULL )
return VLC_EGENERIC;
return VLC_SUCCESS;
}
static int Demux( demux_t* p_demux )
{
demux_sys_t* p_sys = p_demux->p_sys;
/* Last one must be an end time */
while( p_sys->times.i_current + 1 < p_sys->times.i_count &&
tt_time_Convert( &p_sys->times.p_array[p_sys->times.i_current] ) <= p_sys->i_next_demux_time )
{
const vlc_tick_t i_playbacktime =
tt_time_Convert( &p_sys->times.p_array[p_sys->times.i_current] );
const vlc_tick_t i_playbackendtime =
tt_time_Convert( &p_sys->times.p_array[p_sys->times.i_current + 1] ) - 1;
if ( !p_sys->b_slave && p_sys->b_first_time )
{
es_out_SetPCR( p_demux->out, VLC_TICK_0 + i_playbacktime );
p_sys->b_first_time = false;
}
struct vlc_memstream stream;
if( vlc_memstream_open( &stream ) )
return VLC_DEMUXER_EGENERIC;
tt_node_ToText( &stream, (tt_basenode_t *) p_sys->p_rootnode,
&p_sys->times.p_array[p_sys->times.i_current] );
if( vlc_memstream_close( &stream ) == 0 )
{
block_t* p_block = block_heap_Alloc( stream.ptr, stream.length );
if( p_block )
{
p_block->i_dts =
p_block->i_pts = VLC_TICK_0 + i_playbacktime;
p_block->i_length = i_playbackendtime - i_playbacktime;
es_out_Send( p_demux->out, p_sys->p_es, p_block );
}
}
p_sys->times.i_current++;
}
if ( !p_sys->b_slave )
{
es_out_SetPCR( p_demux->out, VLC_TICK_0 + p_sys->i_next_demux_time );
p_sys->i_next_demux_time += VLC_TICK_FROM_MS(125);
}
if( p_sys->times.i_current + 1 >= p_sys->times.i_count )
return VLC_DEMUXER_EOF;
return VLC_DEMUXER_SUCCESS;
}
int tt_OpenDemux( vlc_object_t* p_this )
{
demux_t *p_demux = (demux_t*)p_this;
demux_sys_t *p_sys;
const uint8_t *p_peek;
ssize_t i_peek = vlc_stream_Peek( p_demux->s, &p_peek, 2048 );
if( unlikely( i_peek <= 32 ) )
return VLC_EGENERIC;
const char *psz_xml = (const char *) p_peek;
size_t i_xml = i_peek;
/* Try to probe without xml module/loading the full document */
char *psz_alloc = NULL;
switch( GetQWBE(p_peek) )
{
/* See RFC 3023 Part 4 */
case UINT64_C(0xFFFE3C003F007800): /* UTF16 BOM<? */
case UINT64_C(0xFFFE3C003F007400): /* UTF16 BOM<t */
case UINT64_C(0xFEFF003C003F0078): /* UTF16 BOM<? */
case UINT64_C(0xFEFF003C003F0074): /* UTF16 BOM<t */
psz_alloc = FromCharset( "UTF-16", p_peek, i_peek );
break;
case UINT64_C(0x3C003F0078006D00): /* UTF16-LE <?xm */
case UINT64_C(0x3C003F0074007400): /* UTF16-LE <tt */
psz_alloc = FromCharset( "UTF-16LE", p_peek, i_peek );
break;
case UINT64_C(0x003C003F0078006D): /* UTF16-BE <?xm */
case UINT64_C(0x003C003F00740074): /* UTF16-BE <tt */
psz_alloc = FromCharset( "UTF-16BE", p_peek, i_peek );
break;
case UINT64_C(0xEFBBBF3C3F786D6C): /* UTF8 BOM<?xml */
case UINT64_C(0x3C3F786D6C207665): /* UTF8 <?xml ve */
case UINT64_C(0xEFBBBF3C74742078): /* UTF8 BOM<tt x*/
break;
default:
if(GetDWBE(p_peek) != UINT32_C(0x3C747420)) /* tt node without xml document marker */
return VLC_EGENERIC;
}
if( psz_alloc )
{
psz_xml = psz_alloc;
i_xml = strlen( psz_alloc );
}
/* Simplified probing. Valid TTML must have a namespace declaration */
const char *psz_tt = strnstr( psz_xml, "tt", i_xml );
if( !psz_tt || psz_tt == psz_xml ||
((size_t)(&psz_tt[2] - (const char*)p_peek)) == i_xml || isalpha(psz_tt[2]) ||
(psz_tt[-1] != ':' && psz_tt[-1] != '<') )
{
free( psz_alloc );
return VLC_EGENERIC;
}
else
{
const char * const rgsz[] =
{
"=\"http://www.w3.org/ns/ttml\"",
"=\"http://www.w3.org/2004/11/ttaf1\"",
"=\"http://www.w3.org/2006/04/ttaf1\"",
"=\"http://www.w3.org/2006/10/ttaf1\"",
};
const char *psz_ns = NULL;
for( size_t i=0; i<ARRAY_SIZE(rgsz) && !psz_ns; i++ )
{
psz_ns = strnstr( psz_xml, rgsz[i],
i_xml - (psz_tt - psz_xml) );
}
free( psz_alloc );
if( !psz_ns )
return VLC_EGENERIC;
}
p_demux->p_sys = p_sys = calloc( 1, sizeof( *p_sys ) );
if( unlikely( p_sys == NULL ) )
return VLC_ENOMEM;
p_sys->b_first_time = true;
p_sys->temporal_extent.i_type = TT_TIMINGS_PARALLEL;
tt_time_Init( &p_sys->temporal_extent.begin );
tt_time_Init( &p_sys->temporal_extent.end );
tt_time_Init( &p_sys->temporal_extent.dur );
p_sys->temporal_extent.begin.base = 0;
tt_namespaces_Init( &p_sys->namespaces );
p_sys->p_xml = xml_Create( p_demux );
if( !p_sys->p_xml )
goto error;
p_sys->p_reader = xml_ReaderCreate( p_sys->p_xml, p_demux->s );
if( !p_sys->p_reader )
goto error;
#ifndef TTML_DEMUX_DEBUG
p_sys->p_reader->obj.logger = NULL;
#endif
if( ReadTTML( p_demux ) != VLC_SUCCESS )
goto error;
tt_timings_Resolve( (tt_basenode_t *) p_sys->p_rootnode, &p_sys->temporal_extent,
&p_sys->times.p_array, &p_sys->times.i_count );
#ifdef TTML_DEMUX_DEBUG
{
struct vlc_memstream stream;
if( vlc_memstream_open( &stream ) )
goto error;
tt_time_t t;
tt_time_Init( &t );
tt_node_ToText( &stream, (tt_basenode_t*)p_sys->p_rootnode, &t /* invalid */ );
vlc_memstream_putc( &stream, '\0' );
if( vlc_memstream_close( &stream ) == 0 )
{
msg_Dbg( p_demux, "%s", stream.ptr );
free( stream.ptr );
}
}
#endif
p_demux->pf_demux = Demux;
p_demux->pf_control = Control;
es_format_t fmt;
es_format_Init( &fmt, SPU_ES, VLC_CODEC_TTML );
fmt.i_id = 0;
p_sys->p_es = es_out_Add( p_demux->out, &fmt );
if( !p_sys->p_es )
goto error;
es_format_Clean( &fmt );
return VLC_SUCCESS;
error:
tt_CloseDemux( p_this );
return VLC_EGENERIC;
}
void tt_CloseDemux( vlc_object_t* p_this )
{
demux_t *p_demux = (demux_t *)p_this;
demux_sys_t* p_sys = p_demux->p_sys;
if( p_sys->p_rootnode )
tt_node_RecursiveDelete( p_sys->p_rootnode );
if( p_sys->p_es )
es_out_Del( p_demux->out, p_sys->p_es );
if( p_sys->p_reader )
xml_ReaderDelete( p_sys->p_reader );
if( p_sys->p_xml )
xml_Delete( p_sys->p_xml );
tt_namespaces_Clean( &p_sys->namespaces );
free( p_sys->times.p_array );
free( p_sys );
}
¤ Dauer der Verarbeitung: 0.5 Sekunden
¤
*© Formatika GbR, Deutschland
|
|