Commit 44dd0225 authored by Hugo Beauzée-Luyssen's avatar Hugo Beauzée-Luyssen

Add basic TTML support

This is meant to serve as a base for our Outreachy student's project:
https://wiki.gnome.org/Outreachy/2015/MayAugust#VideoLAN
refs #9396
parent 3b3bd7e4
......@@ -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)
......
......@@ -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
......
......@@ -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
......
/*****************************************************************************
* 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 );
}
......@@ -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
/*****************************************************************************
* 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 );
}
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment