From 6a63bcae635b35ae291a47e45a1d5ca75e079075 Mon Sep 17 00:00:00 2001 From: Francois Cartegnie <fcvlcdev@free.fr> Date: Fri, 21 Mar 2014 19:47:11 +0100 Subject: [PATCH] codec: add tx3g spu --- NEWS | 1 + include/vlc_fourcc.h | 1 + modules/MODULES_LIST | 1 + modules/codec/Makefile.am | 2 + modules/codec/substext.h | 192 ++++++++++++++++++++- modules/codec/substx3g.c | 346 ++++++++++++++++++++++++++++++++++++++ po/POTFILES.in | 1 + 7 files changed, 538 insertions(+), 6 deletions(-) create mode 100644 modules/codec/substx3g.c diff --git a/NEWS b/NEWS index f79d43682037..cb3ad9e4e543 100644 --- a/NEWS +++ b/NEWS @@ -25,6 +25,7 @@ Decoder: * OpenMax IL improvements, notably for RaspberryPi * Fix channel ordering of LPCM codec in m2ts files * New jpeg image decoder + * Add tx3g subtitles decoder Encoder: * Support for MPEG-2 encoding using x262 diff --git a/include/vlc_fourcc.h b/include/vlc_fourcc.h index 8af8d419790c..46903eabb5ae 100644 --- a/include/vlc_fourcc.h +++ b/include/vlc_fourcc.h @@ -416,6 +416,7 @@ #define VLC_CODEC_USF VLC_FOURCC('u','s','f',' ') #define VLC_CODEC_OGT VLC_FOURCC('o','g','t',' ') #define VLC_CODEC_CVD VLC_FOURCC('c','v','d',' ') +#define VLC_CODEC_TX3G VLC_FOURCC('t','x','3','g') /* Blu-ray Presentation Graphics */ #define VLC_CODEC_BD_PG VLC_FOURCC('b','d','p','g') /* EBU STL (TECH. 3264-E) */ diff --git a/modules/MODULES_LIST b/modules/MODULES_LIST index c6a8fba03780..c860098d7056 100644 --- a/modules/MODULES_LIST +++ b/modules/MODULES_LIST @@ -344,6 +344,7 @@ $Id$ * stream_out_transcode: audio & video transcoder * subsdec: a codec to output textual subtitles * subsdelay: subtitles delay filter + * substx3g: tx3g styled subtitles decoder * subsusf: a demuxer for USF subtitles * subtitle: a demuxer for subtitle files * svcdsub: SVCD subtitles decoder diff --git a/modules/codec/Makefile.am b/modules/codec/Makefile.am index d1d7332f06d6..14a53fd96766 100644 --- a/modules/codec/Makefile.am +++ b/modules/codec/Makefile.am @@ -183,6 +183,8 @@ endif EXTRA_LTLIBRARIES += libzvbi_plugin.la codec_LTLIBRARIES += $(LTLIBzvbi) +libsubstx3g_plugin_la_SOURCES = codec/substx3g.c codec/substext.h +codec_LTLIBRARIES += libsubstx3g_plugin.la ### Xiph ### diff --git a/modules/codec/substext.h b/modules/codec/substext.h index fe5df95350e3..05129e3e7f04 100644 --- a/modules/codec/substext.h +++ b/modules/codec/substext.h @@ -1,18 +1,164 @@ +#include <vlc_strings.h> + +typedef struct +{ + bool b_set; + unsigned int i_value; +} subpicture_updater_sys_option_t; + +typedef struct segment_t segment_t; + +typedef struct +{ + uint8_t i_fontsize; + uint32_t i_color; //ARGB + uint8_t i_flags; +} segment_style_t; + +struct segment_t +{ + char *psz_string; + unsigned int i_size; + segment_t *p_next; + /* styles applied to that segment */ + segment_style_t styles; +}; + struct subpicture_updater_sys_t { char *text; char *html; + segment_t *p_htmlsegments; int align; int x; int y; int i_font_height_percent; + int i_font_height_abs_to_src; bool is_fixed; int fixed_width; int fixed_height; bool renderbg; + + /* styling */ + subpicture_updater_sys_option_t style_flags; + subpicture_updater_sys_option_t font_color; + subpicture_updater_sys_option_t background_color; + int16_t i_alpha; + int16_t i_drop_shadow; + int16_t i_drop_shadow_alpha; }; +static void SegmentFree( segment_t *p_segment ) +{ + if ( p_segment ) + { + free( p_segment->psz_string ); + free( p_segment ); + } +} + +static void MakeHtmlNewLines( char **ppsz_src ) +{ + unsigned int i_nlcount = 0; + unsigned i_len = strlen( *ppsz_src ); + if ( i_len == 0 ) return; + for ( unsigned i=0; i<i_len; i++ ) + if ( (*ppsz_src)[i] == '\n' ) + i_nlcount++; + if ( !i_nlcount ) return; + + char *psz_dst = malloc( i_len + 1 + (i_nlcount * 4) ); + char *ptr = psz_dst; + for ( unsigned i=0; i<i_len; i++ ) + { + if ( (*ppsz_src)[i] == '\n' ) + { + strcpy( ptr, "<br/>" ); + ptr += 5; + } else { + *ptr++ = (*ppsz_src)[i]; + } + } + *ptr = 0; + free( *ppsz_src ); + *ppsz_src = psz_dst; +} + +static void HtmlAppend( char **ppsz_dst, const char *psz_src, + const segment_style_t *p_styles, const float f_scale ) +{ + if ( !ppsz_dst ) return; + int i_ignore; VLC_UNUSED(i_ignore); + char *psz_subtext = NULL; + char *psz_text = NULL; + char *psz_fontsize = NULL; + char *psz_color = NULL; + char *psz_encoded = convert_xml_special_chars( psz_src ); + if ( !psz_encoded ) return; + + MakeHtmlNewLines( &psz_encoded ); + + if ( p_styles->i_color & 0xFF000000 ) //ARGB + i_ignore = asprintf( &psz_color, " color=\"#%6x\"", + p_styles->i_color & 0x00FFFFFF ); + + if ( p_styles->i_fontsize > 0 && f_scale > 0 ) + i_ignore = asprintf( &psz_fontsize, " size=\"%u\"", + (unsigned) (f_scale * p_styles->i_fontsize) ); + + i_ignore = asprintf( &psz_subtext, "%s%s%s%s%s%s%s", + ( p_styles->i_flags & STYLE_UNDERLINE ) ? "<u>" : "", + ( p_styles->i_flags & STYLE_BOLD ) ? "<b>" : "", + ( p_styles->i_flags & STYLE_ITALIC ) ? "<i>" : "", + psz_encoded, + ( p_styles->i_flags & STYLE_ITALIC ) ? "</i>" : "", + ( p_styles->i_flags & STYLE_BOLD ) ? "</b>" : "", + ( p_styles->i_flags & STYLE_UNDERLINE ) ? "</u>" : "" + ); + + if ( psz_color || psz_fontsize ) + { + i_ignore = asprintf( &psz_text, "<font%s%s>%s</font>", + psz_color ? psz_color : "", + psz_fontsize ? psz_fontsize : "", + psz_subtext ); + free( psz_subtext ); + } + else + { + psz_text = psz_subtext; + } + + free( psz_fontsize ); + free( psz_color ); + + if ( *ppsz_dst ) + { + char *psz_dst = *ppsz_dst; + i_ignore = asprintf( ppsz_dst, "%s%s", psz_dst, psz_text ); + free( psz_dst ); + free( psz_text ); + } + else + *ppsz_dst = psz_text; +} + +static char *SegmentsToHtml( segment_t *p_head, const float f_scale ) +{ + char *psz_dst = NULL; + char *psz_ret = NULL; + while( p_head ) + { + HtmlAppend( &psz_dst, p_head->psz_string, &p_head->styles, f_scale ); + p_head = p_head->p_next; + } + int i_ignore = asprintf( &psz_ret, "<text>%s</text>", psz_dst ); + VLC_UNUSED( i_ignore ); + free( psz_dst ); + return psz_ret; +} + static int SubpictureTextValidate(subpicture_t *subpic, bool has_src_changed, const video_format_t *fmt_src, bool has_dst_changed, const video_format_t *fmt_dst, @@ -59,7 +205,13 @@ static void SubpictureTextUpdate(subpicture_t *subpic, return; r->psz_text = sys->text ? strdup(sys->text) : NULL; - r->psz_html = sys->html ? strdup(sys->html) : NULL; + if ( sys->p_htmlsegments ) + r->psz_html = SegmentsToHtml( sys->p_htmlsegments, + (float) fmt_dst->i_height / fmt_src->i_height ); + else if ( sys->html ) + r->psz_html = strdup(sys->html); + else + r->psz_html = NULL; r->i_align = sys->align; r->b_renderbg = sys->renderbg; if (!sys->is_fixed) { @@ -84,16 +236,38 @@ static void SubpictureTextUpdate(subpicture_t *subpic, r->i_y = sys->y * fmt_dst->i_height / sys->fixed_height; } - if (sys->i_font_height_percent != 0) + if (sys->i_font_height_percent || sys->i_alpha || + sys->style_flags.b_set || + sys->font_color.b_set || + sys->background_color.b_set ) { r->p_style = text_style_New(); - if (r->p_style) + if (!r->p_style) return; + + if (sys->i_font_height_abs_to_src) + sys->i_font_height_percent = sys->i_font_height_abs_to_src * 100 / + fmt_src->i_visible_height; + + if (sys->i_font_height_percent) { - r->p_style->i_font_size = sys->i_font_height_percent * - subpic->i_original_picture_height / 100; + r->p_style->i_font_size = sys->i_font_height_percent * + subpic->i_original_picture_height / 100; r->p_style->i_font_color = 0xffffff; r->p_style->i_font_alpha = 0xff; - } + } + + if (sys->style_flags.b_set) + r->p_style->i_style_flags = sys->style_flags.i_value; + if (sys->font_color.b_set) + r->p_style->i_font_color = sys->font_color.i_value; + if (sys->background_color.b_set) + r->p_style->i_background_color = sys->background_color.i_value; + if (sys->i_alpha) + r->p_style->i_font_alpha = sys->i_alpha; + if (sys->i_drop_shadow) + r->p_style->i_shadow_width = sys->i_drop_shadow; + if (sys->i_drop_shadow_alpha) + r->p_style->i_shadow_alpha = sys->i_drop_shadow_alpha; } } static void SubpictureTextDestroy(subpicture_t *subpic) @@ -102,6 +276,12 @@ static void SubpictureTextDestroy(subpicture_t *subpic) free(sys->text); free(sys->html); + while( sys->p_htmlsegments ) + { + segment_t *p_segment = sys->p_htmlsegments; + sys->p_htmlsegments = sys->p_htmlsegments->p_next; + SegmentFree( p_segment ); + } free(sys); } diff --git a/modules/codec/substx3g.c b/modules/codec/substx3g.c new file mode 100644 index 000000000000..4464b896b00c --- /dev/null +++ b/modules/codec/substx3g.c @@ -0,0 +1,346 @@ +/***************************************************************************** + * substx3gsub.c : MP4 tx3g subtitles decoder + ***************************************************************************** + * Copyright (C) 2014 VLC authors and VideoLAN + * + * 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., + * 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_codec.h> +#include <vlc_sout.h> + +#include "substext.h" + +/***************************************************************************** + * Module descriptor. + *****************************************************************************/ +static int Open ( vlc_object_t * ); +static subpicture_t *Decode( decoder_t *, block_t ** ); + +vlc_module_begin () + set_description( N_("tx3g subtitles decoder") ) + set_shortname( N_("tx3g subtitles") ) + set_capability( "decoder", 100 ) + set_category( CAT_INPUT ) + set_subcategory( SUBCAT_INPUT_SCODEC ) + set_callbacks( Open, NULL ) +vlc_module_end () + +/**************************************************************************** + * Local structs + ****************************************************************************/ + +/***************************************************************************** + * Open: probe the decoder and return score + *****************************************************************************/ +static int Open( vlc_object_t *p_this ) +{ + decoder_t *p_dec = (decoder_t *) p_this; + + if( p_dec->fmt_in.i_codec != VLC_CODEC_TX3G ) + return VLC_EGENERIC; + + p_dec->pf_decode_sub = Decode; + + p_dec->fmt_out.i_cat = SPU_ES; + p_dec->fmt_out.i_codec = 0; + + return VLC_SUCCESS; +} + +/***************************************************************************** + * Local: + *****************************************************************************/ + +#define FONT_FACE_BOLD 0x1 +#define FONT_FACE_ITALIC 0x2 +#define FONT_FACE_UNDERLINE 0x4 + +static int ConvertFlags( int i_atomflags ) +{ + int i_vlcstyles_flags = 0; + if ( i_atomflags & FONT_FACE_BOLD ) + i_vlcstyles_flags |= STYLE_BOLD; + else if ( i_atomflags & FONT_FACE_ITALIC ) + i_vlcstyles_flags |= STYLE_ITALIC; + else if ( i_atomflags & FONT_FACE_UNDERLINE ) + i_vlcstyles_flags |= STYLE_UNDERLINE; + return i_vlcstyles_flags; +} + +static void SegmentDoSplit( segment_t *p_segment, uint16_t i_start, uint16_t i_end, + segment_t **pp_segment_left, + segment_t **pp_segment_middle, + segment_t **pp_segment_right ) +{ + segment_t *p_segment_left = *pp_segment_left; + segment_t *p_segment_right = *pp_segment_right; + segment_t *p_segment_middle = *pp_segment_middle; + p_segment_left = p_segment_middle = p_segment_right = NULL; + + if ( (p_segment->i_size - i_start < 1) || (p_segment->i_size - i_end < 1) ) + return; + + if ( i_start > 0 ) + { + p_segment_left = calloc( 1, sizeof(segment_t) ); + if ( !p_segment_left ) goto error; + memcpy( &p_segment_left->styles, &p_segment->styles, sizeof(segment_style_t) ); + p_segment_left->psz_string = strndup( p_segment->psz_string, i_start ); + p_segment_left->i_size = strlen( p_segment_left->psz_string ); + } + + p_segment_middle = calloc( 1, sizeof(segment_t) ); + if ( !p_segment_middle ) goto error; + memcpy( &p_segment_middle->styles, &p_segment->styles, sizeof(segment_style_t) ); + p_segment_middle->psz_string = strndup( p_segment->psz_string + i_start, i_end - i_start + 1 ); + p_segment_middle->i_size = strlen( p_segment_middle->psz_string ); + + if ( i_end < (p_segment->i_size - 1) ) + { + p_segment_right = calloc( 1, sizeof(segment_t) ); + if ( !p_segment_right ) goto error; + memcpy( &p_segment_right->styles, &p_segment->styles, sizeof(segment_style_t) ); + p_segment_right->psz_string = strndup( p_segment->psz_string + i_end + 1, p_segment->i_size - i_end - 1 ); + p_segment_right->i_size = strlen( p_segment_right->psz_string ); + } + + if ( p_segment_left ) p_segment_left->p_next = p_segment_middle; + if ( p_segment_right ) p_segment_middle->p_next = p_segment_right; + + *pp_segment_left = p_segment_left; + *pp_segment_middle = p_segment_middle; + *pp_segment_right = p_segment_right; + + return; + +error: + SegmentFree( p_segment_left ); + SegmentFree( p_segment_middle ); + SegmentFree( p_segment_right ); +} + +static bool SegmentSplit( segment_t *p_prev, segment_t **pp_segment, + const uint16_t i_start, const uint16_t i_end, + const segment_style_t *p_styles ) +{ + segment_t *p_segment_left = NULL, *p_segment_middle = NULL, *p_segment_right = NULL; + + if ( (*pp_segment)->i_size == 0 ) return false; + if ( i_start > i_end ) return false; + if ( (size_t)(i_end - i_start) > (*pp_segment)->i_size - 1 ) return false; + if ( i_end > (*pp_segment)->i_size - 1 ) return false; + + SegmentDoSplit( *pp_segment, i_start, i_end, &p_segment_left, &p_segment_middle, &p_segment_right ); + if ( !p_segment_middle ) + { + /* Failed */ + SegmentFree( p_segment_left ); + SegmentFree( p_segment_right ); + return false; + } + + segment_t *p_next = (*pp_segment)->p_next; + SegmentFree( *pp_segment ); + *pp_segment = ( p_segment_left ) ? p_segment_left : p_segment_middle ; + if ( p_prev ) p_prev->p_next = *pp_segment; + + if ( p_segment_right ) + p_segment_right->p_next = p_next; + else + p_segment_middle->p_next = p_next; + + p_segment_middle->styles = *p_styles; + + return true; +} + +/* Creates a new segment using the given style and split existing ones according + to the start & end offsets */ +static void ApplySegmentStyle( segment_t **pp_segment, const uint16_t i_absstart, + const uint16_t i_absend, const segment_style_t *p_styles ) +{ + /* find the matching segment */ + uint16_t i_curstart = 0; + segment_t *p_prev = NULL; + segment_t *p_cur = *pp_segment; + while ( p_cur ) + { + uint16_t i_curend = i_curstart + p_cur->i_size - 1; + if ( (i_absstart >= i_curstart) && (i_absend <= i_curend) ) + { + /* segment found */ + if ( !SegmentSplit( p_prev, &p_cur, i_absstart - i_curstart, + i_absend - i_curstart, p_styles ) ) + return; + if ( !p_prev ) *pp_segment = p_cur; + break; + } + else + { + i_curstart += p_cur->i_size; + p_prev = p_cur; + p_cur = p_cur->p_next; + } + } +} + +/***************************************************************************** + * Decode: + *****************************************************************************/ +static subpicture_t *Decode( decoder_t *p_dec, block_t **pp_block ) +{ + block_t *p_block; + subpicture_t *p_spu = NULL; + + if( ( pp_block == NULL ) || ( *pp_block == NULL ) ) return NULL; + p_block = *pp_block; + *pp_block = NULL; + + if( ( p_block->i_flags & (BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED) ) || + p_block->i_buffer < sizeof(uint16_t) ) + { + block_Release( p_block ); + return NULL; + } + + uint8_t *p_buf = p_block->p_buffer; + + /* Read our raw string and create the styled segment for HTML */ + uint16_t i_psz_length = GetWBE( p_buf ); + char *psz_subtitle = malloc( i_psz_length + 1 ); + if ( !psz_subtitle ) return NULL; + memcpy( psz_subtitle, p_block->p_buffer + sizeof(uint16_t), i_psz_length ); + psz_subtitle[ i_psz_length ] = '\0'; + p_buf += i_psz_length + sizeof(uint16_t); + + for( uint16_t i=0; i < i_psz_length; i++ ) + if ( psz_subtitle[i] == '\r' ) psz_subtitle[i] = '\n'; + + segment_t *p_segment = calloc( 1, sizeof(segment_t) ); + if ( !p_segment ) + { + free( psz_subtitle ); + return NULL; + } + p_segment->psz_string = strdup( psz_subtitle ); + p_segment->i_size = strlen( psz_subtitle ); + if ( p_dec->fmt_in.subs.p_style ) + { + p_segment->styles.i_color = p_dec->fmt_in.subs.p_style->i_font_color; + p_segment->styles.i_color |= p_dec->fmt_in.subs.p_style->i_font_alpha << 24; + if ( p_dec->fmt_in.subs.p_style->i_style_flags ) + p_segment->styles.i_flags = p_dec->fmt_in.subs.p_style->i_style_flags; + p_segment->styles.i_fontsize = p_dec->fmt_in.subs.p_style->i_font_size; + } + + if ( !p_segment->psz_string ) + { + SegmentFree( p_segment ); + free( psz_subtitle ); + return NULL; + } + + /* Create the subpicture unit */ + p_spu = decoder_NewSubpictureText( p_dec ); + if( !p_spu ) + { + free( psz_subtitle ); + SegmentFree( p_segment ); + return NULL; + } + subpicture_updater_sys_t *p_spu_sys = p_spu->updater.p_sys; + + /* Parse our styles */ + while( (size_t)(p_buf - p_block->p_buffer) + 8 < p_block->i_buffer ) + { + uint32_t i_atomsize = GetDWBE( p_buf ); + vlc_fourcc_t i_atomtype = VLC_FOURCC(p_buf[4],p_buf[5],p_buf[6],p_buf[7]); + p_buf += 8; + switch( i_atomtype ) + { + + case VLC_FOURCC('s','t','y','l'): + { + if ( (size_t)(p_buf - p_block->p_buffer) < 14 ) break; + uint16_t i_nbrecords = GetWBE(p_buf); + uint16_t i_cur_record = 0; + p_buf += 2; + while( i_cur_record++ < i_nbrecords ) + { + if ( (size_t)(p_buf - p_block->p_buffer) < 12 ) break; + uint16_t i_start = __MIN( GetWBE(p_buf), i_psz_length - 1 ); + uint16_t i_end = __MIN( GetWBE(p_buf + 2), i_psz_length - 1 ); + + segment_style_t style; + style.i_flags = ConvertFlags( p_buf[6] ); + style.i_fontsize = p_buf[7]; + style.i_color = GetDWBE(p_buf+8) >> 8;// RGBA -> ARGB + style.i_color |= (GetDWBE(p_buf+8) & 0xFF) << 24; + ApplySegmentStyle( &p_segment, i_start, i_end, &style ); + + if ( i_nbrecords == 1 ) + { + if ( p_buf[6] ) + { + p_spu_sys->style_flags.i_value = ConvertFlags( p_buf[6] ); + p_spu_sys->style_flags.b_set = true; + } + p_spu_sys->i_font_height_abs_to_src = p_buf[7]; + p_spu_sys->font_color.i_value = GetDWBE(p_buf+8) >> 8;// RGBA -> ARGB + p_spu_sys->font_color.i_value |= (GetDWBE(p_buf+8) & 0xFF) << 24; + p_spu_sys->font_color.b_set = true; + } + + p_buf += 12; + } + } break; + + case VLC_FOURCC('d','r','p','o'): + if ( (size_t)(p_buf - p_block->p_buffer) < 4 ) break; + p_spu_sys->i_drop_shadow = __MAX( GetWBE(p_buf), GetWBE(p_buf+2) ); + break; + + case VLC_FOURCC('d','r','p','t'): + if ( (size_t)(p_buf - p_block->p_buffer) < 2 ) break; + p_spu_sys->i_drop_shadow_alpha = GetWBE(p_buf); + break; + + default: + break; + + } + p_buf += i_atomsize; + } + + 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; + + p_spu_sys->align = SUBPICTURE_ALIGN_BOTTOM; + p_spu_sys->text = psz_subtitle; + p_spu_sys->p_htmlsegments = p_segment; + + block_Release( p_block ); + + return p_spu; +} diff --git a/po/POTFILES.in b/po/POTFILES.in index 6dadb88348fa..124056e50d08 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -399,6 +399,7 @@ modules/codec/spudec/spudec.h modules/codec/stl.c modules/codec/subsdec.c modules/codec/subsusf.c +modules/codec/substx3g.c modules/codec/svcdsub.c modules/codec/t140.c modules/codec/telx.c -- GitLab