Commit 7d19767c authored by Sushma Reddy's avatar Sushma Reddy Committed by Hugo Beauzée-Luyssen

ttml: Basic styling support

Signed-off-by: default avatarHugo Beauzée-Luyssen <hugo@beauzee.fr>
parent 5972f560
......@@ -20,6 +20,7 @@
* 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
......@@ -28,18 +29,197 @@
#include <vlc_plugin.h>
#include <vlc_modules.h>
#include <vlc_codec.h>
#include <vlc_xml.h>
#include <vlc_stream.h>
#include <vlc_text_style.h>
#include "substext.h"
#include <ctype.h>
#define ALIGN_TEXT N_("Subtitle justification")
#define ALIGN_LONGTEXT N_("Set the justification of subtitles")
static const struct {
const char *psz_name;
uint32_t i_value;
} p_html_colors[] = {
{ "Aqua", 0x00FFFF },
{ "Black", 0x000000 },
{ "Blue", 0x0000FF },
{ "Fuchsia", 0xFF00FF },
{ "Gray", 0x808080 },
{ "Green", 0x008000 },
{ "Lime", 0x00FF00 },
{ "Maroon", 0x800000 },
{ "Navy", 0x000080 },
{ "Olive", 0x808000 },
{ "Purple", 0x800080 },
{ "Red", 0xFF0000 },
{ "Silver", 0xC0C0C0 },
{ "Teal", 0x008080 },
{ "White", 0xFFFFFF },
{ "Yellow", 0xFFFF00 },
{ "AliceBlue", 0xF0F8FF },
{ "AntiqueWhite", 0xFAEBD7 },
{ "Aqua", 0x00FFFF },
{ "Aquamarine", 0x7FFFD4 },
{ "Azure", 0xF0FFFF },
{ "Beige", 0xF5F5DC },
{ "Bisque", 0xFFE4C4 },
{ "Black", 0x000000 },
{ "BlanchedAlmond", 0xFFEBCD },
{ "Blue", 0x0000FF },
{ "BlueViolet", 0x8A2BE2 },
{ "Brown", 0xA52A2A },
{ "BurlyWood", 0xDEB887 },
{ "CadetBlue", 0x5F9EA0 },
{ "Chartreuse", 0x7FFF00 },
{ "Chocolate", 0xD2691E },
{ "Coral", 0xFF7F50 },
{ "CornflowerBlue", 0x6495ED },
{ "Cornsilk", 0xFFF8DC },
{ "Crimson", 0xDC143C },
{ "Cyan", 0x00FFFF },
{ "DarkBlue", 0x00008B },
{ "DarkCyan", 0x008B8B },
{ "DarkGoldenRod", 0xB8860B },
{ "DarkGray", 0xA9A9A9 },
{ "DarkGrey", 0xA9A9A9 },
{ "DarkGreen", 0x006400 },
{ "DarkKhaki", 0xBDB76B },
{ "DarkMagenta", 0x8B008B },
{ "DarkOliveGreen", 0x556B2F },
{ "Darkorange", 0xFF8C00 },
{ "DarkOrchid", 0x9932CC },
{ "DarkRed", 0x8B0000 },
{ "DarkSalmon", 0xE9967A },
{ "DarkSeaGreen", 0x8FBC8F },
{ "DarkSlateBlue", 0x483D8B },
{ "DarkSlateGray", 0x2F4F4F },
{ "DarkSlateGrey", 0x2F4F4F },
{ "DarkTurquoise", 0x00CED1 },
{ "DarkViolet", 0x9400D3 },
{ "DeepPink", 0xFF1493 },
{ "DeepSkyBlue", 0x00BFFF },
{ "DimGray", 0x696969 },
{ "DimGrey", 0x696969 },
{ "DodgerBlue", 0x1E90FF },
{ "FireBrick", 0xB22222 },
{ "FloralWhite", 0xFFFAF0 },
{ "ForestGreen", 0x228B22 },
{ "Fuchsia", 0xFF00FF },
{ "Gainsboro", 0xDCDCDC },
{ "GhostWhite", 0xF8F8FF },
{ "Gold", 0xFFD700 },
{ "GoldenRod", 0xDAA520 },
{ "Gray", 0x808080 },
{ "Grey", 0x808080 },
{ "Green", 0x008000 },
{ "GreenYellow", 0xADFF2F },
{ "HoneyDew", 0xF0FFF0 },
{ "HotPink", 0xFF69B4 },
{ "IndianRed", 0xCD5C5C },
{ "Indigo", 0x4B0082 },
{ "Ivory", 0xFFFFF0 },
{ "Khaki", 0xF0E68C },
{ "Lavender", 0xE6E6FA },
{ "LavenderBlush", 0xFFF0F5 },
{ "LawnGreen", 0x7CFC00 },
{ "LemonChiffon", 0xFFFACD },
{ "LightBlue", 0xADD8E6 },
{ "LightCoral", 0xF08080 },
{ "LightCyan", 0xE0FFFF },
{ "LightGoldenRodYellow", 0xFAFAD2 },
{ "LightGray", 0xD3D3D3 },
{ "LightGrey", 0xD3D3D3 },
{ "LightGreen", 0x90EE90 },
{ "LightPink", 0xFFB6C1 },
{ "LightSalmon", 0xFFA07A },
{ "LightSeaGreen", 0x20B2AA },
{ "LightSkyBlue", 0x87CEFA },
{ "LightSlateGray", 0x778899 },
{ "LightSlateGrey", 0x778899 },
{ "LightSteelBlue", 0xB0C4DE },
{ "LightYellow", 0xFFFFE0 },
{ "Lime", 0x00FF00 },
{ "LimeGreen", 0x32CD32 },
{ "Linen", 0xFAF0E6 },
{ "Magenta", 0xFF00FF },
{ "Maroon", 0x800000 },
{ "MediumAquaMarine", 0x66CDAA },
{ "MediumBlue", 0x0000CD },
{ "MediumOrchid", 0xBA55D3 },
{ "MediumPurple", 0x9370D8 },
{ "MediumSeaGreen", 0x3CB371 },
{ "MediumSlateBlue", 0x7B68EE },
{ "MediumSpringGreen", 0x00FA9A },
{ "MediumTurquoise", 0x48D1CC },
{ "MediumVioletRed", 0xC71585 },
{ "MidnightBlue", 0x191970 },
{ "MintCream", 0xF5FFFA },
{ "MistyRose", 0xFFE4E1 },
{ "Moccasin", 0xFFE4B5 },
{ "NavajoWhite", 0xFFDEAD },
{ "Navy", 0x000080 },
{ "OldLace", 0xFDF5E6 },
{ "Olive", 0x808000 },
{ "OliveDrab", 0x6B8E23 },
{ "Orange", 0xFFA500 },
{ "OrangeRed", 0xFF4500 },
{ "Orchid", 0xDA70D6 },
{ "PaleGoldenRod", 0xEEE8AA },
{ "PaleGreen", 0x98FB98 },
{ "PaleTurquoise", 0xAFEEEE },
{ "PaleVioletRed", 0xD87093 },
{ "PapayaWhip", 0xFFEFD5 },
{ "PeachPuff", 0xFFDAB9 },
{ "Peru", 0xCD853F },
{ "Pink", 0xFFC0CB },
{ "Plum", 0xDDA0DD },
{ "PowderBlue", 0xB0E0E6 },
{ "Purple", 0x800080 },
{ "Red", 0xFF0000 },
{ "RosyBrown", 0xBC8F8F },
{ "RoyalBlue", 0x4169E1 },
{ "SaddleBrown", 0x8B4513 },
{ "Salmon", 0xFA8072 },
{ "SandyBrown", 0xF4A460 },
{ "SeaGreen", 0x2E8B57 },
{ "SeaShell", 0xFFF5EE },
{ "Sienna", 0xA0522D },
{ "Silver", 0xC0C0C0 },
{ "SkyBlue", 0x87CEEB },
{ "SlateBlue", 0x6A5ACD },
{ "SlateGray", 0x708090 },
{ "SlateGrey", 0x708090 },
{ "Snow", 0xFFFAFA },
{ "SpringGreen", 0x00FF7F },
{ "SteelBlue", 0x4682B4 },
{ "Tan", 0xD2B48C },
{ "Teal", 0x008080 },
{ "Thistle", 0xD8BFD8 },
{ "Tomato", 0xFF6347 },
{ "Turquoise", 0x40E0D0 },
{ "Violet", 0xEE82EE },
{ "Wheat", 0xF5DEB3 },
{ "White", 0xFFFFFF },
{ "WhiteSmoke", 0xF5F5F5 },
{ "Yellow", 0xFFFF00 },
{ "YellowGreen", 0x9ACD32 },
{ NULL, 0 }
};
/*****************************************************************************
* Module descriptor.
*****************************************************************************/
static int OpenDecoder ( vlc_object_t * );
static void CloseDecoder ( vlc_object_t * );
static text_segment_t *ParseTTMLSubtitles( decoder_t *, subpicture_updater_sys_t *, char * );
vlc_module_begin ()
set_capability( "decoder", 10 )
set_shortname( N_("TTML decoder"))
......@@ -50,16 +230,479 @@ vlc_module_begin ()
add_integer( "ttml-align", 0, ALIGN_TEXT, ALIGN_LONGTEXT, false )
vlc_module_end ();
/*****************************************************************************
* Local prototypes
*****************************************************************************/
typedef struct
{
char* psz_styleid;
text_style_t* font_style;
int i_align;
int i_margin_h;
int i_margin_v;
int i_margin_percent_h;
int i_margin_percent_v;
} ttml_style_t;
struct decoder_sys_t
{
int i_align;
ttml_style_t** pp_styles;
size_t i_styles;
};
static ttml_style_t *FindTextStyle( decoder_t *p_dec, const char *psz_style )
{
decoder_sys_t *p_sys = p_dec->p_sys;
for( size_t i = 0; i < p_sys->i_styles; i++ )
{
if( !strcmp( p_sys->pp_styles[i]->psz_styleid, psz_style ) )
{
return p_sys->pp_styles[i];
}
}
return NULL;
}
typedef struct style_stack style_stack_t;
struct style_stack
{
ttml_style_t* p_style;
style_stack_t* p_next;
};
static bool PushStyle( style_stack_t **pp_stack, ttml_style_t* p_style )
{
style_stack_t* p_entry = malloc( sizeof( *p_entry) );
if ( unlikely( p_entry == NULL ) )
return false;
p_entry->p_style = p_style;
p_entry->p_next = *pp_stack;
*pp_stack = p_entry;
return true;
}
static void PopStyle( style_stack_t** pp_stack )
{
if ( *pp_stack == NULL )
return;
style_stack_t* p_next = (*pp_stack)->p_next;
free( *pp_stack );
*pp_stack = p_next;
}
static void ClearStack( style_stack_t* p_stack )
{
while ( p_stack != NULL )
{
style_stack_t* p_next = p_stack->p_next;
free( p_stack );
p_stack = p_next;
}
}
static text_style_t* CurrentStyle( style_stack_t* p_stack )
{
if ( p_stack == NULL )
return text_style_Create( STYLE_NO_DEFAULTS );
return text_style_Duplicate( p_stack->p_style->font_style );
}
static int GetColor( const char *psz_value, bool* ok )
{
unsigned int color = 0xFFFFFF;
char* psz_end;
if ( ok != NULL )
*ok = false;
if( *psz_value == '#' )
{
color = strtol( psz_value + 1, &psz_end, 16 );
if ( ok != NULL && ( *psz_end == '\0' || *psz_end == ' ' ) )
*ok = true;
}
else
{
uint32_t i_value = strtol( psz_value, &psz_end, 16 );
if( *psz_end == '\0' || *psz_end == ' ' )
{
color = i_value;
if ( ok != NULL )
*ok = true;
}
else
{
for( int i = 0; p_html_colors[i].psz_name != NULL; i++ )
{
if( !strncasecmp( psz_value, p_html_colors[i].psz_name, strlen(p_html_colors[i].psz_name) ) )
{
// Assume opaque text since no alpha is specified.
color = p_html_colors[i].i_value | 0xFF000000;
if ( ok != NULL )
*ok = true;
}
}
}
}
return color;
}
static void ParseTTMLStyle( decoder_t *p_dec, xml_reader_t* p_reader )
{
decoder_sys_t* p_sys = p_dec->p_sys;
ttml_style_t *p_ttml_style = NULL;
ttml_style_t *p_base_style = NULL;
p_ttml_style = calloc( 1, sizeof(ttml_style_t) );
if ( unlikely( !p_ttml_style ) )
return ;
p_ttml_style->font_style = text_style_Create( STYLE_NO_DEFAULTS );
if( unlikely( !p_ttml_style->font_style ) )
{
free( p_ttml_style );
return ;
}
const char *attr, *val;
while( (attr = xml_ReaderNextAttr( p_reader, &val ) ) )
{
if ( !strcasecmp( attr, "style" ) )
{
for( size_t i = 0; i < p_sys->i_styles; i++ )
{
if( !strcasecmp( p_sys->pp_styles[i]->psz_styleid, val ) )
{
p_base_style = p_sys->pp_styles[i];
break;
}
}
}
else if ( !strcasecmp( "xml:id", attr ) )
{
free( p_ttml_style->psz_styleid );
p_ttml_style->psz_styleid = strdup( val );
}
else if ( !strcasecmp ( "tts:fontFamily", attr ) )
{
free( p_ttml_style->font_style->psz_fontname );
p_ttml_style->font_style->psz_fontname = strdup( val );
}
else if ( !strcasecmp( "tts:fontSize", attr ) )
{
p_ttml_style->font_style->i_font_size = atoi( val );
}
else if ( !strcasecmp( "tts:color", attr ) )
{
unsigned int i_color = GetColor( val, NULL );
p_ttml_style->font_style->i_font_color = (i_color & 0xffffff);
p_ttml_style->font_style->i_font_alpha = (i_color & 0xFF000000) >> 24;
p_ttml_style->font_style->i_features |= STYLE_HAS_FONT_COLOR | STYLE_HAS_FONT_ALPHA;
}
else if ( !strcasecmp( "tts:backgroundColor", attr ) )
{
unsigned int i_color = GetColor( val, NULL );
p_ttml_style->font_style->i_background_color = i_color & 0xFFFFFF;
p_ttml_style->font_style->i_background_alpha = (i_color & 0xFF000000) >> 24;
p_ttml_style->font_style->i_features |= STYLE_HAS_BACKGROUND_COLOR
| STYLE_HAS_BACKGROUND_ALPHA;
}
else if ( !strcasecmp( "tts:textAlign", attr ) )
{
if ( !strcasecmp ( "left", val ) )
p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_LEFT;
else if ( !strcasecmp ( "right", val ) )
p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_RIGHT;
else if ( !strcasecmp ( "center", val ) )
p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM;
else if ( !strcasecmp ( "start", val ) )
p_ttml_style->i_align = SUBPICTURE_ALIGN_TOP | SUBPICTURE_ALIGN_LEFT;
else if ( !strcasecmp ( "end", val ) )
p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_RIGHT;
}
else if ( !strcasecmp( "tts:fontStyle", attr ) )
{
if ( !strcasecmp ( "italic", val ) || !strcasecmp ( "oblique", val ) )
p_ttml_style->font_style->i_style_flags |= STYLE_ITALIC;
else
p_ttml_style->font_style->i_style_flags &= ~STYLE_ITALIC;
p_ttml_style->font_style->i_features |= STYLE_HAS_FLAGS;
}
else if ( !strcasecmp ( "tts:fontWeight", attr ) )
{
if ( !strcasecmp ( "bold", val ) )
p_ttml_style->font_style->i_style_flags |= STYLE_BOLD;
else
p_ttml_style->font_style->i_style_flags &= ~STYLE_BOLD;
p_ttml_style->font_style->i_features |= STYLE_HAS_FLAGS;
}
else if ( !strcasecmp ( "tts:textDecoration", attr ) )
{
if ( !strcasecmp ( "underline", val ) )
p_ttml_style->font_style->i_style_flags |= STYLE_UNDERLINE;
else if ( !strcasecmp ( "noUnderline", val ) )
p_ttml_style->font_style->i_style_flags &= ~STYLE_UNDERLINE;
if ( !strcasecmp ( "lineThrough", val ) )
p_ttml_style->font_style->i_style_flags |= STYLE_STRIKEOUT;
else if ( !strcasecmp ( "noLineThrough", val ) )
p_ttml_style->font_style->i_style_flags &= ~STYLE_STRIKEOUT;
p_ttml_style->font_style->i_features |= STYLE_HAS_FLAGS;
}
else if ( !strcasecmp ( "tts:origin", attr ) )
{
const char *psz_token = val;
while ( isspace( *psz_token ) )
psz_token++;
const char *psz_separator = strchr( psz_token, ' ' );
if ( psz_separator == NULL )
{
msg_Warn( p_dec, "Invalid origin attribute: \"%s\"", val );
continue;
}
const char *psz_percent_sign = strchr( psz_token, '%' );
if( psz_percent_sign != NULL && psz_percent_sign < psz_separator )
{
p_ttml_style->i_margin_h = 0;
p_ttml_style->i_margin_percent_h = atoi( psz_token );
}
else
{
p_ttml_style->i_margin_h = atoi( psz_token );
p_ttml_style->i_margin_percent_h = 0;
}
while ( isspace( *psz_separator ) )
psz_separator++;
psz_token = psz_separator;
psz_percent_sign = strchr( psz_token, '%' );
if( psz_percent_sign != NULL )
{
p_ttml_style->i_margin_v = 0;
p_ttml_style->i_margin_percent_v = atoi( val );
}
else
{
p_ttml_style->i_margin_v = atoi( val );
p_ttml_style->i_margin_percent_v = 0;
}
}
else if ( !strcasecmp( "tts:textOutline", attr ) )
{
char *value = strdup( val );
char* psz_saveptr = NULL;
char* token = strtok_r( value, " ", &psz_saveptr );
// <color>? <length> <length>?
bool b_ok = false;
unsigned int color = GetColor( token, &b_ok );
if ( b_ok )
{
p_ttml_style->font_style->i_outline_color = color & 0xFFFFFF;
p_ttml_style->font_style->i_outline_alpha = (color & 0xFF000000) >> 24;
token = strtok_r( NULL, " ", &psz_saveptr );
}
char* psz_end = NULL;
int i_outline_width = strtol( token, &psz_end, 10 );
if ( psz_end != token )
{
// Assume unit is pixel, and ignore border radius
p_ttml_style->font_style->i_outline_width = i_outline_width;
}
free( value );
}
}
if ( p_base_style != NULL )
{
text_style_Merge( p_ttml_style->font_style, p_base_style->font_style, false );
}
if ( p_ttml_style->psz_styleid == NULL )
{
free( p_ttml_style );
free( p_ttml_style->font_style->psz_fontname );
return ;
}
TAB_APPEND( p_sys->i_styles, p_sys->pp_styles, p_ttml_style );
return ;
}
static void ParseTTMLStyles( decoder_t* p_dec )
{
stream_t* p_stream = stream_MemoryNew( p_dec, (uint8_t*)p_dec->fmt_in.p_extra, p_dec->fmt_in.i_extra, true );
if( unlikely( p_stream == NULL ) )
return ;
xml_reader_t* p_reader = xml_ReaderCreate( p_dec, p_stream );
if( unlikely( p_reader == NULL ) )
{
stream_Delete( p_stream );
return ;
}
const char* psz_name;
int i_type = xml_ReaderNextNode( p_reader, &psz_name );
if ( i_type == XML_READER_STARTELEM && ( !strcasecmp( psz_name, "head" ) || !strcasecmp( psz_name, "tt:head" ) ) )
{
do
{
i_type = xml_ReaderNextNode( p_reader, &psz_name );
if ( i_type == XML_READER_STARTELEM && ( !strcasecmp( "styling", psz_name ) ||
!strcasecmp( "tt:styling", psz_name ) ) )
{
i_type = xml_ReaderNextNode( p_reader, &psz_name );
while ( i_type != XML_READER_ENDELEM || ( strcasecmp( psz_name, "styling" ) && strcasecmp( psz_name, "tt:styling" ) ) )
{
ParseTTMLStyle( p_dec, p_reader );
i_type = xml_ReaderNextNode( p_reader, &psz_name );
}
}
} while ( i_type != XML_READER_ENDELEM || ( strcasecmp( psz_name, "head" ) && strcasecmp( psz_name, "tt:head" ) ) );
}
xml_ReaderDelete( p_reader );
stream_Delete( p_stream );
}
static text_segment_t *ParseTTMLSubtitles( decoder_t *p_dec, subpicture_updater_sys_t *p_update_sys, char *psz_subtitle )
{
stream_t* p_sub = NULL;
xml_reader_t* p_xml_reader = NULL;
text_segment_t* p_first_segment = NULL;
text_segment_t* p_current_segment = NULL;
style_stack_t* p_style_stack = NULL;
p_sub = stream_MemoryNew( p_dec, (uint8_t*)psz_subtitle, strlen( psz_subtitle ), true );
if( unlikely( p_sub == NULL ) )
return NULL;
p_xml_reader = xml_ReaderCreate( p_dec, p_sub );
if( unlikely( p_xml_reader == NULL ) )
{
stream_Delete( p_sub );
return NULL;
}
const char *node;
int i_type;
i_type = xml_ReaderNextNode( p_xml_reader, &node );
while ( i_type != XML_READER_NONE && i_type > 0 )
{
if( i_type == XML_READER_STARTELEM && ( !strcasecmp( node, "p" ) || !strcasecmp( node, "tt:p" ) ) )
{
text_segment_t* p_segment = text_segment_New( NULL );
if ( unlikely( p_segment == NULL ) )
goto fail;
const char* psz_attr_name;
const char* psz_attr_value;
while ( ( psz_attr_name = xml_ReaderNextAttr( p_xml_reader, &psz_attr_value ) ) != NULL )
{
if ( !strcasecmp( psz_attr_name, "style" ) )
{
ttml_style_t* p_style = FindTextStyle( p_dec, psz_attr_value );
if ( p_style == NULL )
{
msg_Warn( p_dec, "Style \"%s\" not found", psz_attr_value );
break;
}
if( p_style->i_margin_h )
p_update_sys->x = p_style->i_margin_h;
else
p_update_sys->x = p_style->i_margin_percent_h;
if( p_style->i_margin_v )
p_update_sys->y = p_style->i_margin_v;
else
p_update_sys->y = p_style->i_margin_percent_v;
p_update_sys->align = p_style->i_align;
if ( PushStyle( &p_style_stack, p_style ) == false )
{
goto fail;
}
p_segment->style = CurrentStyle( p_style_stack );
break;
}
}
if ( p_segment->style == NULL )
p_segment->style = text_style_Create( STYLE_NO_DEFAULTS );
if ( p_first_segment == NULL )
{
p_first_segment = p_segment;
p_current_segment = p_segment;
}
else
{
p_current_segment->p_next = p_segment;
p_current_segment = p_segment;
}
}
else if ( i_type == XML_READER_ENDELEM && ( !strcasecmp( node, "p" ) || !strcasecmp( node, "tt:p" ) ) )
{
PopStyle( &p_style_stack );
p_current_segment = NULL;
}
else if ( i_type == XML_READER_STARTELEM && !strcasecmp( node, "br" ) )
{
if ( p_current_segment != NULL && p_current_segment->psz_text != NULL )
{
char* psz_text = NULL;
if ( asprintf( &psz_text, "%s\n", p_current_segment->psz_text ) != -1 )
{
free( p_current_segment->psz_text );
p_current_segment->psz_text = psz_text;
}
}
}
else if ( i_type == XML_READER_TEXT )
{
if ( p_current_segment == NULL )
{
p_current_segment = text_segment_New( node );
p_current_segment->style = CurrentStyle( p_style_stack );
if ( p_first_segment == NULL )
p_first_segment = p_current_segment;
else
p_first_segment->p_next = p_current_segment;
}
else if ( p_current_segment->psz_text == NULL )
{
p_current_segment->psz_text = strdup( node );
resolve_xml_special_chars( p_current_segment->psz_text );
}
else
{
size_t i_previous_len = strlen( p_current_segment->psz_text );
char* psz_text = NULL;
if ( asprintf( &psz_text, "%s%s", p_current_segment->psz_text, node ) != -1 )
{
free( p_current_segment->psz_text );
p_current_segment->psz_text = psz_text;
// Don't process text multiple time, just check for the appended section
resolve_xml_special_chars( p_current_segment->psz_text + i_previous_len );
}
}
}
i_type = xml_ReaderNextNode( p_xml_reader, &node );
}
ClearStack( p_style_stack );
xml_ReaderDelete( p_xml_reader );
stream_Delete( p_sub );
return p_first_segment;
fail:
text_segment_ChainDelete( p_first_segment );
ClearStack( p_style_stack );
xml_ReaderDelete( p_xml_reader );
stream_Delete( p_sub );
return NULL;
}
static subpicture_t *ParseText( decoder_t *p_dec, block_t *p_block )
{
decoder_sys_t *p_sys = p_dec->p_sys;
......@@ -99,15 +742,18 @@ static subpicture_t *ParseText( decoder_t *p_dec, block_t *p_block )
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->p_segments = text_segment_New( psz_subtitle );
p_spu_sys->p_segments = ParseTTMLSubtitles( p_dec, p_spu_sys, psz_subtitle );
free( psz_subtitle );
return p_spu;
}
/****************************************************************************
* DecodeBlock: the whole thing
****************************************************************************/
......@@ -134,20 +780,20 @@ static int OpenDecoder( vlc_object_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;
}
if ( p_dec->fmt_in.p_extra != NULL && p_dec->fmt_in.i_extra > 0 )
ParseTTMLStyles( p_dec );
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;
}
......@@ -156,10 +802,16 @@ static int OpenDecoder( vlc_object_t *p_this )
*****************************************************************************/
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;
for ( size_t i = 0; i < p_sys->i_styles; ++i )
{
free( p_sys->pp_styles[i]->psz_styleid );
text_style_Delete( p_sys->pp_styles[i]->font_style );
free( p_sys->pp_styles[i] );
}
TAB_CLEAN( p_sys->i_styles, p_sys->pp_styles );
free( p_sys );
}
......@@ -31,6 +31,7 @@
#include <vlc_xml.h>
#include <vlc_strings.h>
#include <vlc_memory.h>
#include <vlc_es_out.h>
static int Open( vlc_object_t* p_this );
static void Close( demux_t* p_demux );
......@@ -55,6 +56,7 @@ typedef struct
struct demux_sys_t
{
xml_t* p_xml;
xml_reader_t* p_reader;
subtitle_t* subtitle;
es_out_id_t* p_es;
......@@ -62,6 +64,9 @@ struct demux_sys_t
int64_t i_next_demux_time;
int i_subtitle;
int i_subtitles;
char* psz_head;
size_t i_head_len;
bool b_has_head;
};
static int Control( demux_t* p_demux, int i_query, va_list args )
......@@ -152,6 +157,7 @@ static int Control( demux_t* p_demux, int i_query, va_list args )
static int Convert_time( int64_t *timing_value, const char *s )
{
int h1, m1, s1, d1 = 0;
//char *sec = "";
if ( sscanf( s, "%d:%d:%d,%d",
&h1, &m1, &s1, &d1 ) == 4 ||
......@@ -171,32 +177,73 @@ static int Convert_time( int64_t *timing_value, const char *s )
return VLC_EGENERIC;
}
static char* Append( char* psz_old, const char* psz_format, ... )
{
va_list ap;
char* psz_new;
va_start (ap, psz_format);
int ret = vasprintf( &psz_new, psz_format, ap );
va_end (ap);
if ( ret < 0 )
{
free( psz_old );
return NULL;
}
char* psz_concat;
ret = asprintf( &psz_concat, "%s%s", psz_old, psz_new );
free( psz_old );
free( psz_new );
if ( ret < 0 )
return NULL;
return psz_concat;
}
static int ReadSubtitles( demux_sys_t* p_sys )
static int ReadTTML( demux_t* p_demux )
{
demux_sys_t* p_sys = p_demux->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" ) )
if ( i_type == XML_READER_STARTELEM && ( !strcasecmp( psz_name, "head" ) || !strcasecmp( psz_name, "tt:head" ) ) )
{
p_sys->b_has_head = true;
}
else if ( i_type == XML_READER_STARTELEM && ( !strcasecmp( psz_name, "p" ) || !strcasecmp( psz_name, "tt:p" ) ) )
{
char* psz_text = NULL;
char* psz_begin = NULL;
char* psz_end = NULL;
while ( !psz_begin || !psz_end )
{
if( asprintf ( &psz_text, "<%s", psz_name ) < 0 )
return VLC_ENOMEM;
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;
while ( psz_attr_name && psz_attr_value )
{
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 );
else if ( !strcasecmp( psz_attr_name, psz_attr_name ) )
{
psz_text = Append( psz_text, " %s = \"%s\"", psz_attr_name, psz_attr_value );
if ( unlikely( psz_text == NULL ) )
return VLC_ENOMEM;
}
psz_attr_name = xml_ReaderNextAttr( p_sys->p_reader, &psz_attr_value );
}
psz_text = Append( psz_text, ">" );
if ( unlikely( psz_text == NULL ) )
{
free( psz_begin );
free( psz_end );
return VLC_ENOMEM;
}
if ( psz_begin && psz_end )
......@@ -208,43 +255,83 @@ static int ReadSubtitles( demux_sys_t* p_sys )
sizeof( *p_sys->subtitle ) * i_max_sub );
if ( unlikely( p_sys->subtitle == NULL ) )
{
free( psz_text );
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 );
free( psz_begin );
free( psz_end );
i_type = xml_ReaderNextNode( p_sys->p_reader, &psz_text );
if ( i_type == XML_READER_TEXT && psz_text != NULL )
i_type = xml_ReaderNextNode( p_sys->p_reader, &psz_name );
while ( i_type != XML_READER_ENDELEM || ( strcmp( psz_name, "p" ) && strcmp( psz_name, "tt:p" ) ) )
{
if ( i_type == XML_READER_TEXT && psz_name != NULL )
{
if( *psz_text != 0 )
psz_text = Append( psz_text, "%s", psz_name );
if ( unlikely( psz_text == NULL ) )
return VLC_ENOMEM;
}
else if ( i_type == XML_READER_STARTELEM )
{
p_subtitle->psz_text = strdup( psz_text );
if ( likely( p_subtitle->psz_text != NULL ) )
psz_text = Append( psz_text, " <%s", psz_name );
if ( unlikely( psz_text == NULL ) )
return VLC_ENOMEM;
psz_attr_name = xml_ReaderNextAttr( p_sys->p_reader, &psz_attr_value );
while ( psz_attr_name && psz_attr_value )
{
resolve_xml_special_chars( p_subtitle->psz_text );
p_sys->i_subtitles++;
psz_text = Append( psz_text, " %s=\"%s\"", psz_attr_name, psz_attr_value );
if ( unlikely( psz_text == NULL ) )
return VLC_ENOMEM;
psz_attr_name = xml_ReaderNextAttr( p_sys->p_reader, &psz_attr_value );
}
if ( !strcasecmp( psz_name, "tt:br" ) || !strcasecmp( psz_name, "br" ) )
{
psz_text = Append( psz_text, "/>" );
if ( unlikely( psz_text == NULL ) )
return VLC_ENOMEM;
}
else
{
psz_text = Append( psz_text, ">" );
if ( unlikely( psz_text == NULL ) )
return VLC_ENOMEM;
}
}
else if ( i_type == XML_READER_ENDELEM )
{
psz_text = Append( psz_text, " </%s>", psz_name );
if ( unlikely( psz_text == NULL ) )
return VLC_ENOMEM;
}
i_type = xml_ReaderNextNode( p_sys->p_reader, &psz_name );
}
psz_text = Append( psz_text, "</p>" );
if ( unlikely( psz_text == NULL ) )
return VLC_ENOMEM;
p_subtitle->psz_text = psz_text;
p_sys->i_subtitles++;
}
else
{
free( psz_text );
free( psz_begin );
free( psz_end );
}
} while ( i_type != XML_READER_ENDELEM || strcasecmp( psz_name, "tt" ) );
}
} while ( i_type != XML_READER_ENDELEM || ( strcasecmp( psz_name, "tt" ) && strcasecmp( psz_name, "tt: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;
......@@ -273,16 +360,109 @@ static int Demux( demux_t* p_demux )
return 1;
}
static void ParseHead( demux_t* p_demux )
{
demux_sys_t* p_sys = p_demux->p_sys;
char buff[1025];
char* psz_head = NULL;
size_t i_head_len = 0; // head tags size, in bytes
size_t i_size; // allocated buffer size
size_t i_read;
// Rewind since the XML parser will have consumed the entire file.
stream_Seek( p_demux->s, 0 );
while ( ( i_read = stream_Read( p_demux->s, (void*)buff, 1024 ) ) > 0 )
{
ssize_t i_offset = -1;
// Ensure we can use strstr
buff[i_read] = 0;
if ( psz_head == NULL )
{
// Seek to the opening <head> tag if we haven't seen it already
const char* psz_head_begin = strstr( buff, "<head>" );
if ( psz_head_begin == NULL )
psz_head_begin = strstr( buff, "<tt:head>" );
if ( psz_head_begin == NULL )
continue;
i_head_len = i_read - ( psz_head_begin - buff );
i_size = i_head_len;
psz_head = malloc( i_size * sizeof( *psz_head ) );
if ( unlikely( psz_head == NULL ) )
return;
memcpy( psz_head, psz_head_begin, i_head_len );
// Avoid copying the head tag again once we search for the end tag.
i_offset = psz_head_begin - buff;
}
if ( psz_head != NULL )
{
size_t i_end_tag_len = strlen( "</head>" );
// Or copy until the end of the head tag once we've seen the opening one
const char* psz_end_head = strstr( buff, "</head>" );
if ( psz_end_head == NULL )
{
psz_end_head = strstr( buff, "</tt:head>" );
i_end_tag_len = strlen( "</tt:head>" );
}
// Check if we need to extend the buffer first
size_t i_to_copy = i_read;
if ( psz_end_head != NULL )
i_to_copy = psz_end_head - buff + i_end_tag_len;
if ( i_size < i_head_len + i_to_copy )
{
i_size = __MAX(i_size * 2, i_head_len + i_to_copy);
psz_head = realloc_or_free( psz_head, i_size );
if ( unlikely( psz_head == NULL ) )
return;
}
if ( psz_end_head == NULL )
{
// If we already copied the begin tag, we don't need to append this again.
if ( i_offset != -1 )
continue;
// Otherwise, simply append the entire buffer
memcpy( psz_head + i_head_len, buff, i_to_copy );
i_head_len += i_to_copy;
}
else
{
if ( i_offset == -1 )
{
memcpy( psz_head + i_head_len, buff, i_to_copy );
i_head_len += i_to_copy;
}
else
{
// If the buffer we originally copied already contains the end tag, no need to copy again
// Though if we already have the </head> in our buffer, we need to adjust the total size
i_head_len = psz_end_head - buff + i_end_tag_len + i_offset;
}
p_sys->psz_head = psz_head;
p_sys->i_head_len = i_head_len;
break;
}
}
}
}
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_reader = xml_ReaderCreate( p_this, p_demux->s );
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 );
......@@ -291,22 +471,36 @@ static int Open( vlc_object_t* p_this )
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" ) )
if ( i_type != XML_READER_STARTELEM || ( strcmp( psz_name, "tt" ) && strcmp( psz_name, "tt:tt" ) ) )
{
Close( p_demux );
return VLC_EGENERIC;
}
if ( ReadTTML( p_demux ) != VLC_SUCCESS )
{
Close( p_demux );
return VLC_EGENERIC;
}
if ( p_sys->b_has_head )
ParseHead( p_demux );
p_demux->pf_demux = Demux;
p_demux->pf_control = Control;
es_format_t fmt;
es_format_Init( &fmt, SPU_ES, VLC_CODEC_TTML );
if ( p_sys->i_head_len > 0 )
{
fmt.p_extra = p_sys->psz_head;
fmt.i_extra = p_sys->i_head_len;
}
p_sys->p_es = es_out_Add( p_demux->out, &fmt );
es_format_Clean( &fmt );
if ( ReadSubtitles( p_sys ) != VLC_SUCCESS )
return VLC_EGENERIC;
if ( p_sys->i_subtitles > 0 )
p_sys->i_length = p_sys->subtitle[ p_sys->i_subtitles - 1 ].i_stop;
else
p_sys->i_length = 0;
return VLC_SUCCESS;
}
......@@ -318,12 +512,12 @@ static void Close( demux_t* p_demux )
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 );
stream_Seek( p_demux->s, 0 );
}
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