diff --git a/NEWS b/NEWS index 1ccdec538ac39226cb60dfacb049588b2c53c6c1..6d93061618d9a0a1b08f11c607e28c4760d85cbe 100644 --- a/NEWS +++ b/NEWS @@ -35,6 +35,7 @@ Decoder: * Rewrite WPL playlists and add ZPL playlists support (Zune) * Support TDSC, Canopus HQX * Support HEVC hardware decoding on Windows, using DxVA2 + * Basic TTML subtitles support Demuxers: * Support HD-DVD .evo (H.264, VC-1, MPEG-2, PCM, AC-3, E-AC3, MLP, DTS) diff --git a/modules/MODULES_LIST b/modules/MODULES_LIST index 10400ef1dec21d997f41cd11bf3eae5b1289c94c..0d794e0d21a39496b91f088b241129e8c953ab0b 100644 --- a/modules/MODULES_LIST +++ b/modules/MODULES_LIST @@ -368,6 +368,7 @@ $Id$ * stream_out_transcode: audio & video transcoder * subsdec: a codec to output textual subtitles * subsdelay: subtitles delay filter + * substtml: TTML subtitles decoder * substx3g: tx3g styled subtitles decoder * subsusf: a demuxer for USF subtitles * subtitle: a demuxer for subtitle files @@ -388,6 +389,7 @@ $Id$ * trivial_channel_mixer: Simple channel mixer plugin * ts: MPEG-TS demuxer * tta: Lossless True Audio parser + * ttml: a TTML subtitles demuxer * twolame: a mp1 mp2 audio encoder based on twolame * ty: TY demuxer * udev: udev probing module diff --git a/modules/codec/Makefile.am b/modules/codec/Makefile.am index bd893261f39ce5f2d664a12fab847ea5bd831087..f173c0e29b23568dfbf9e3dd1b30039a6439d366 100644 --- a/modules/codec/Makefile.am +++ b/modules/codec/Makefile.am @@ -198,6 +198,9 @@ codec_LTLIBRARIES += libsubsdec_plugin.la libsubsusf_plugin_la_SOURCES = codec/subsusf.c codec_LTLIBRARIES += libsubsusf_plugin.la +libsubsttml_plugin_la_SOURCES = codec/substtml.c +codec_LTLIBRARIES += libsubsttml_plugin.la + libsvcdsub_plugin_la_SOURCES = codec/svcdsub.c codec_LTLIBRARIES += libsvcdsub_plugin.la diff --git a/modules/codec/substtml.c b/modules/codec/substtml.c new file mode 100644 index 0000000000000000000000000000000000000000..e4c80a6614be7720a0478b8fd247def51f8e6b5d --- /dev/null +++ b/modules/codec/substtml.c @@ -0,0 +1,164 @@ +/***************************************************************************** + * substtml.c : TTML subtitles decoder + ***************************************************************************** + * Copyright (C) 2015 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_plugin.h> +#include <vlc_modules.h> +#include <vlc_codec.h> + +#include "substext.h" + +#define ALIGN_TEXT N_("Subtitle justification") +#define ALIGN_LONGTEXT N_("Set the justification of subtitles") + +/***************************************************************************** + * Module descriptor. + *****************************************************************************/ +static int OpenDecoder ( vlc_object_t * ); +static void CloseDecoder ( vlc_object_t * ); + +vlc_module_begin () + set_capability( "decoder", 10 ) + set_shortname( N_("TTML decoder")) + set_description( N_("TTML subtitles decoder") ) + set_callbacks( OpenDecoder, CloseDecoder ) + set_category( CAT_INPUT ) + set_subcategory( SUBCAT_INPUT_SCODEC ) + add_integer( "ttml-align", 0, ALIGN_TEXT, ALIGN_LONGTEXT, false ) +vlc_module_end (); + + +/***************************************************************************** + * Local prototypes + *****************************************************************************/ + +struct decoder_sys_t +{ + int i_align; +}; + +static subpicture_t *ParseText( decoder_t *p_dec, block_t *p_block ) +{ + decoder_sys_t *p_sys = p_dec->p_sys; + subpicture_t *p_spu = NULL; + char *psz_subtitle = NULL; + + /* We cannot display a subpicture with no date */ + if( p_block->i_pts <= VLC_TS_INVALID ) + { + msg_Warn( p_dec, "subtitle without a date" ); + return NULL; + } + + /* Check validity of packet data */ + /* An "empty" line containing only \0 can be used to force + and ephemer picture from the screen */ + + if( p_block->i_buffer < 1 ) + { + msg_Warn( p_dec, "no subtitle data" ); + return NULL; + } + + psz_subtitle = malloc( p_block->i_buffer ); + if ( unlikely( psz_subtitle == NULL ) ) + return NULL; + memcpy( psz_subtitle, p_block->p_buffer, p_block->i_buffer ); + + /* Create the subpicture unit */ + p_spu = decoder_NewSubpictureText( p_dec ); + if( !p_spu ) + { + free( psz_subtitle ); + return NULL; + } + p_spu->i_start = p_block->i_pts; + p_spu->i_stop = p_block->i_pts + p_block->i_length; + p_spu->b_ephemer = (p_block->i_length == 0); + p_spu->b_absolute = false; + subpicture_updater_sys_t *p_spu_sys = p_spu->updater.p_sys; + + p_spu_sys->align = SUBPICTURE_ALIGN_BOTTOM | p_sys->i_align; + p_spu_sys->text = psz_subtitle; + + return p_spu; +} + +/**************************************************************************** + * DecodeBlock: the whole thing + ****************************************************************************/ +static subpicture_t *DecodeBlock( decoder_t *p_dec, block_t **pp_block ) +{ + if( !pp_block || *pp_block == NULL ) + return NULL; + + block_t* p_block = *pp_block; + subpicture_t *p_spu = ParseText( p_dec, p_block ); + + block_Release( p_block ); + *pp_block = NULL; + + return p_spu; +} + +/***************************************************************************** + * OpenDecoder: probe the decoder and return score + *****************************************************************************/ +static int OpenDecoder( vlc_object_t *p_this ) +{ + decoder_t *p_dec = (decoder_t*)p_this; + decoder_sys_t *p_sys; + + if ( p_dec->fmt_in.i_codec != VLC_CODEC_TTML ) + { + return VLC_EGENERIC; + } + + /* Allocate the memory needed to store the decoder's structure */ + p_dec->p_sys = p_sys = calloc( 1, sizeof( *p_sys ) ); + if( unlikely( p_sys == NULL ) ) + { + return VLC_ENOMEM; + } + + p_dec->pf_decode_sub = DecodeBlock; + p_dec->fmt_out.i_cat = SPU_ES; + p_sys->i_align = var_InheritInteger( p_dec, "ttml-align" ); + return VLC_SUCCESS; +} + +/***************************************************************************** + * CloseDecoder: clean up the decoder + *****************************************************************************/ +static void CloseDecoder( vlc_object_t *p_this ) +{ + /* Cleanup here */ + decoder_t *p_dec = (decoder_t *)p_this; + decoder_sys_t *p_sys = p_dec->p_sys; + + free( p_sys ); +} + diff --git a/modules/demux/Makefile.am b/modules/demux/Makefile.am index f3eb976b8eb30262a8d151b7b8b3c495d6d0bac0..ec0b03d60165433b2477795160a9382efbff732e 100644 --- a/modules/demux/Makefile.am +++ b/modules/demux/Makefile.am @@ -340,4 +340,6 @@ libdash_plugin_la_LIBADD += -lz endif demux_LTLIBRARIES += libdash_plugin.la +libttml_plugin_la_SOURCES = demux/ttml.c +demux_LTLIBRARIES += libttml_plugin.la diff --git a/modules/demux/ttml.c b/modules/demux/ttml.c new file mode 100644 index 0000000000000000000000000000000000000000..139854e31477b937f7f643da6f8d0aa7673b419f --- /dev/null +++ b/modules/demux/ttml.c @@ -0,0 +1,337 @@ +/***************************************************************************** + * ttml.c : TTML subtitles demux + ***************************************************************************** + * Copyright (C) 2015 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_plugin.h> +#include <vlc_demux.h> +#include <vlc_xml.h> +#include <vlc_strings.h> +#include <vlc_memory.h> + +static int Open( vlc_object_t* p_this ); +static void Close( demux_t* p_demux ); + +vlc_module_begin () + set_shortname( N_("TTML") ) + set_description( N_("TTML demuxer") ) + set_capability( "demux", 10 ) + set_category( CAT_INPUT ) + set_subcategory( SUBCAT_INPUT_DEMUX ) + set_callbacks( Open, Close ) + add_shortcut( "ttml", "subtitle" ) +vlc_module_end (); + + +typedef struct +{ + int64_t i_start; + int64_t i_stop; + char *psz_text; +} subtitle_t; + +struct demux_sys_t +{ + xml_t* p_xml; + xml_reader_t* p_reader; + subtitle_t* subtitle; + es_out_id_t* p_es; + int64_t i_length; + int64_t i_next_demux_time; + int i_subtitle; + int i_subtitles; +}; + +static int Control( demux_t* p_demux, int i_query, va_list args ) +{ + demux_sys_t *p_sys = p_demux->p_sys; + int64_t *pi64, i64; + double *pf, f; + + switch( i_query ) + { + case DEMUX_GET_TIME: + pi64 = (int64_t*)va_arg( args, int64_t * ); + if( p_sys->i_subtitle < p_sys->i_subtitles ) + { + *pi64 = p_sys->subtitle[p_sys->i_subtitle].i_start; + return VLC_SUCCESS; + } + case DEMUX_SET_TIME: + i64 = (int64_t)va_arg( args, int64_t ); + p_sys->i_subtitle = 0; + while( p_sys->i_subtitle < p_sys->i_subtitles ) + { + const subtitle_t *p_subtitle = &p_sys->subtitle[p_sys->i_subtitle]; + + if( p_subtitle->i_start > i64 ) + break; + if( p_subtitle->i_stop > p_subtitle->i_start && p_subtitle->i_stop > i64 ) + break; + + p_sys->i_subtitle++; + } + + if( p_sys->i_subtitle >= p_sys->i_subtitles ) + return VLC_EGENERIC; + return VLC_SUCCESS; + case DEMUX_SET_NEXT_DEMUX_TIME: + i64 = (int64_t)va_arg( args, int64_t ); + p_sys->i_next_demux_time = i64; + return VLC_SUCCESS; + case DEMUX_GET_LENGTH: + pi64 = (int64_t*)va_arg( args, int64_t * ); + *pi64 = p_sys->i_length; + return VLC_SUCCESS; + case DEMUX_GET_POSITION: + pf = (double*)va_arg( args, double * ); + if( p_sys->i_subtitle >= p_sys->i_subtitles ) + { + *pf = 1.0; + } + else if( p_sys->i_subtitles > 0 ) + { + *pf = (double)p_sys->subtitle[p_sys->i_subtitle].i_start / + (double)p_sys->i_length; + } + else + { + *pf = 0.0; + } + return VLC_SUCCESS; + case DEMUX_SET_POSITION: + f = (double)va_arg( args, double ); + i64 = f * p_sys->i_length; + + p_sys->i_subtitle = 0; + while( p_sys->i_subtitle < p_sys->i_subtitles && + p_sys->subtitle[p_sys->i_subtitle].i_start < i64 ) + { + p_sys->i_subtitle++; + } + if( p_sys->i_subtitle >= p_sys->i_subtitles ) + return VLC_EGENERIC; + return VLC_SUCCESS; + 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: + return VLC_EGENERIC; + default: + msg_Err( p_demux, "unknown query %d in subtitle control", i_query ); + return VLC_EGENERIC; + } + return VLC_EGENERIC; +} + +static int Convert_time( int64_t *timing_value, const char *s ) +{ + int h1, m1, s1, d1 = 0; + + if ( sscanf( s, "%d:%d:%d,%d", + &h1, &m1, &s1, &d1 ) == 4 || + sscanf( s, "%d:%d:%d.%d", + &h1, &m1, &s1, &d1 ) == 4 || + sscanf( s, "%d:%d:%d", + &h1, &m1, &s1) == 3 ) + { + (*timing_value) = ( (int64_t)h1 * 3600 * 1000 + + (int64_t)m1 * 60 * 1000 + + (int64_t)s1 * 1000 + + (int64_t)d1 ) * 1000; + + return VLC_SUCCESS; + } + + return VLC_EGENERIC; +} + + + +static int ReadSubtitles( demux_sys_t* p_sys ) +{ + const char* psz_name; + int i_max_sub = 0; + int i_type; + do + { + i_type = xml_ReaderNextNode( p_sys->p_reader, &psz_name ); + + if ( i_type == XML_READER_STARTELEM && !strcasecmp( psz_name, "p" ) ) + { + char* psz_begin = NULL; + char* psz_end = NULL; + + while ( !psz_begin || !psz_end ) + { + const char* psz_attr_value = NULL; + const char* psz_attr_name = xml_ReaderNextAttr( p_sys->p_reader, &psz_attr_value ); + if ( !psz_attr_name || !psz_attr_value ) + break; + if ( !strcasecmp( psz_attr_name, "begin" ) ) + psz_begin = strdup( psz_attr_value ); + else if ( !strcasecmp( psz_attr_name, "end" ) ) + psz_end = strdup( psz_attr_value ); + } + + if ( psz_begin && psz_end ) + { + if ( p_sys->i_subtitles >= i_max_sub ) + { + i_max_sub += 500; + p_sys->subtitle = realloc_or_free( p_sys->subtitle, + sizeof( *p_sys->subtitle ) * i_max_sub ); + if ( unlikely( p_sys->subtitle == NULL ) ) + { + free( psz_begin ); + free( psz_end ); + return VLC_ENOMEM; + } + } + const char* psz_text = NULL; + subtitle_t *p_subtitle = &p_sys->subtitle[p_sys->i_subtitles]; + + Convert_time( &p_subtitle->i_start, psz_begin ); + Convert_time( &p_subtitle->i_stop, psz_end ); + + i_type = xml_ReaderNextNode( p_sys->p_reader, &psz_text ); + if ( i_type == XML_READER_TEXT && psz_text != NULL ) + { + if( *psz_text != 0 ) + { + p_subtitle->psz_text = strdup( psz_text ); + if ( likely( p_subtitle->psz_text != NULL ) ) + { + resolve_xml_special_chars( p_subtitle->psz_text ); + p_sys->i_subtitles++; + } + } + } + } + free( psz_begin ); + free( psz_end ); + } + } while ( i_type != XML_READER_ENDELEM || strcasecmp( psz_name, "tt" ) ); + + return VLC_SUCCESS; +} + +static int Demux( demux_t* p_demux ) +{ + demux_sys_t* p_sys = p_demux->p_sys; + + if( p_sys->i_subtitle >= p_sys->i_subtitles ) + return 0; + + while ( p_sys->i_subtitle < p_sys->i_subtitles && + p_sys->subtitle[p_sys->i_subtitle].i_start < p_sys->i_next_demux_time ) + { + const subtitle_t* p_subtitle = &p_sys->subtitle[p_sys->i_subtitle]; + + block_t* p_block = block_Alloc( strlen( p_subtitle->psz_text ) + 1 ); + if ( unlikely( p_block == NULL ) ) + return -1; + + p_block->i_dts = + p_block->i_pts = VLC_TS_0 + p_subtitle->i_start; + + if( p_subtitle->i_stop >= 0 && p_subtitle->i_stop >= p_subtitle->i_start ) + p_block->i_length = p_subtitle->i_stop - p_subtitle->i_start; + + memcpy( p_block->p_buffer, p_subtitle->psz_text, p_block->i_buffer ); + + es_out_Send( p_demux->out, p_sys->p_es, p_block ); + + p_sys->i_subtitle++; + } + p_sys->i_next_demux_time = 0; + return 1; +} + +static int Open( vlc_object_t* p_this ) +{ + demux_t *p_demux = (demux_t*)p_this; + demux_sys_t *p_sys; + + p_demux->p_sys = p_sys = calloc( 1, sizeof( *p_sys ) ); + if ( unlikely( p_sys == NULL ) ) + return VLC_ENOMEM; + + p_sys->p_xml = xml_Create( p_demux ); + if ( !p_sys->p_xml ) + { + Close( p_demux ); + return VLC_EGENERIC; + } + + p_sys->p_reader = xml_ReaderCreate( p_sys->p_xml, p_demux->s ); + if ( !p_sys->p_reader ) + { + Close( p_demux ); + return VLC_EGENERIC; + } + + const char* psz_name; + int i_type = xml_ReaderNextNode( p_sys->p_reader, &psz_name ); + if ( i_type != XML_READER_STARTELEM || strcmp( psz_name, "tt" ) ) + { + Close( p_demux ); + return VLC_EGENERIC; + } + p_demux->pf_demux = Demux; + p_demux->pf_control = Control; + + es_format_t fmt; + es_format_Init( &fmt, SPU_ES, VLC_CODEC_TTML ); + p_sys->p_es = es_out_Add( p_demux->out, &fmt ); + es_format_Clean( &fmt ); + + if ( ReadSubtitles( p_sys ) != VLC_SUCCESS ) + return VLC_EGENERIC; + p_sys->i_length = p_sys->subtitle[ p_sys->i_subtitles - 1 ].i_stop; + + return VLC_SUCCESS; +} + +static void Close( demux_t* p_demux ) +{ + demux_sys_t* p_sys = p_demux->p_sys; + 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 ); + for ( int i = 0; i < p_sys->i_subtitles; ++i ) + { + free( p_sys->subtitle[i].psz_text ); + } + free( p_sys->subtitle ); + free( p_sys ); +} diff --git a/po/POTFILES.in b/po/POTFILES.in index 04338208292815c991cfd62ec0bdfafc0f626c7a..773a7281a706469402f184812f2b51c12275d3bc 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -411,6 +411,7 @@ modules/codec/svcdsub.c modules/codec/t140.c modules/codec/telx.c modules/codec/theora.c +modules/codec/substtml.c modules/codec/twolame.c modules/codec/uleaddvaudio.c modules/codec/vorbis.c @@ -524,6 +525,7 @@ modules/demux/smf.c modules/demux/stl.c modules/demux/subtitle.c modules/demux/tta.c +modules/demux/ttml.c modules/demux/ty.c modules/demux/vc1.c modules/demux/vobsub.c