1 /*****************************************************************************
2 * substx3gsub.c : MP4 tx3g subtitles decoder
3 *****************************************************************************
4 * Copyright (C) 2014 VLC authors and VideoLAN
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation, Inc.,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19 *****************************************************************************/
25 #include <vlc_common.h>
26 #include <vlc_plugin.h>
27 #include <vlc_codec.h>
29 #include <vlc_charset.h>
33 /*****************************************************************************
35 *****************************************************************************/
36 static int Open ( vlc_object_t
* );
37 static int Decode( decoder_t
*, block_t
* );
40 set_description( N_("tx3g subtitles decoder") )
41 set_shortname( N_("tx3g subtitles") )
42 set_capability( "spu decoder", 100 )
43 set_category( CAT_INPUT
)
44 set_subcategory( SUBCAT_INPUT_SCODEC
)
45 set_callbacks( Open
, NULL
)
48 /****************************************************************************
50 ****************************************************************************/
52 /*****************************************************************************
53 * Open: probe the decoder and return score
54 *****************************************************************************/
55 static int Open( vlc_object_t
*p_this
)
57 decoder_t
*p_dec
= (decoder_t
*) p_this
;
59 if( p_dec
->fmt_in
.i_codec
!= VLC_CODEC_TX3G
)
62 p_dec
->pf_decode
= Decode
;
64 p_dec
->fmt_out
.i_codec
= 0;
65 if( p_dec
->fmt_out
.subs
.p_style
)
67 p_dec
->fmt_out
.subs
.p_style
->i_font_size
= 0;
68 p_dec
->fmt_out
.subs
.p_style
->f_font_relsize
= 5.0;
74 /*****************************************************************************
76 *****************************************************************************/
78 #define FONT_FACE_BOLD 0x1
79 #define FONT_FACE_ITALIC 0x2
80 #define FONT_FACE_UNDERLINE 0x4
82 static int ConvertFlags( int i_atomflags
)
84 int i_vlcstyles_flags
= 0;
85 if ( i_atomflags
& FONT_FACE_BOLD
)
86 i_vlcstyles_flags
|= STYLE_BOLD
;
87 if ( i_atomflags
& FONT_FACE_ITALIC
)
88 i_vlcstyles_flags
|= STYLE_ITALIC
;
89 if ( i_atomflags
& FONT_FACE_UNDERLINE
)
90 i_vlcstyles_flags
|= STYLE_UNDERLINE
;
91 return i_vlcstyles_flags
;
94 static size_t str8len( const char *psz_string
)
96 const char *psz_tmp
= psz_string
;
100 if ( (*psz_tmp
& 0xC0) != 0x80 ) i
++;
106 static char * str8indup( const char *psz_string
, size_t i_skip
, size_t n
)
108 while( i_skip
&& *psz_string
)
110 if ( (*psz_string
& 0xC0) != 0x80 ) i_skip
--;
113 if ( ! *psz_string
|| i_skip
) return NULL
;
115 const char *psz_tmp
= psz_string
;
116 while( n
&& *psz_tmp
)
118 if ( (*psz_tmp
& 0xC0) != 0x80 ) n
--;
121 return strndup( psz_string
, psz_tmp
- psz_string
);
124 typedef struct tx3g_segment_t tx3g_segment_t
;
126 struct tx3g_segment_t
130 tx3g_segment_t
*p_next3g
;
133 static tx3g_segment_t
* tx3g_segment_New( const char *psz_string
)
135 tx3g_segment_t
*p_seg
= malloc( sizeof(tx3g_segment_t
) );
139 p_seg
->p_next3g
= NULL
;
140 p_seg
->s
= text_segment_New( psz_string
);
150 static void SegmentDoSplit( tx3g_segment_t
*p_segment
, uint16_t i_start
, uint16_t i_end
,
151 tx3g_segment_t
**pp_segment_left
,
152 tx3g_segment_t
**pp_segment_middle
,
153 tx3g_segment_t
**pp_segment_right
)
155 tx3g_segment_t
*p_segment_left
= NULL
, *p_segment_right
= NULL
, *p_segment_middle
= NULL
;
157 if ( (p_segment
->i_size
- i_start
< 1) || (p_segment
->i_size
- i_end
< 1) )
162 char* psz_text
= str8indup( p_segment
->s
->psz_text
, 0, i_start
);
163 p_segment_left
= tx3g_segment_New( psz_text
);
165 if ( !p_segment_left
) goto error
;
166 p_segment_left
->s
->style
= text_style_Duplicate( p_segment
->s
->style
);
167 p_segment_left
->i_size
= str8len( p_segment_left
->s
->psz_text
);
170 char* psz_text
= str8indup( p_segment
->s
->psz_text
, i_start
, i_end
- i_start
+ 1 );
171 p_segment_middle
= tx3g_segment_New( psz_text
);
173 if ( !p_segment_middle
) goto error
;
174 p_segment_middle
->s
->style
= text_style_Duplicate( p_segment
->s
->style
);
175 p_segment_middle
->i_size
= str8len( p_segment_middle
->s
->psz_text
);
177 if ( i_end
< (p_segment
->i_size
- 1) )
179 char* psz_text
= str8indup( p_segment
->s
->psz_text
, i_end
+ 1, p_segment
->i_size
- i_end
- 1 );
180 p_segment_right
= tx3g_segment_New( psz_text
);
182 if ( !p_segment_right
) goto error
;
183 p_segment_right
->s
->style
= text_style_Duplicate( p_segment
->s
->style
);
184 p_segment_right
->i_size
= str8len( p_segment_right
->s
->psz_text
);
187 if ( p_segment_left
) p_segment_left
->p_next3g
= p_segment_middle
;
188 if ( p_segment_right
) p_segment_middle
->p_next3g
= p_segment_right
;
190 *pp_segment_left
= p_segment_left
;
191 *pp_segment_middle
= p_segment_middle
;
192 *pp_segment_right
= p_segment_right
;
197 if( p_segment_middle
)
199 text_segment_Delete( p_segment_middle
->s
);
200 free( p_segment_middle
);
204 text_segment_Delete( p_segment_left
->s
);
205 free( p_segment_left
);
207 *pp_segment_left
= *pp_segment_middle
= *pp_segment_right
= NULL
;
210 static bool SegmentSplit( tx3g_segment_t
*p_prev
, tx3g_segment_t
**pp_segment
,
211 const uint16_t i_start
, const uint16_t i_end
,
212 const text_style_t
*p_styles
)
214 tx3g_segment_t
*p_segment_left
= NULL
, *p_segment_middle
= NULL
, *p_segment_right
= NULL
;
216 if ( (*pp_segment
)->i_size
== 0 ) return false;
217 if ( i_start
> i_end
) return false;
218 if ( (size_t)(i_end
- i_start
) > (*pp_segment
)->i_size
- 1 ) return false;
219 if ( i_end
> (*pp_segment
)->i_size
- 1 ) return false;
221 SegmentDoSplit( *pp_segment
, i_start
, i_end
, &p_segment_left
, &p_segment_middle
, &p_segment_right
);
222 if ( !p_segment_middle
)
225 text_segment_Delete( p_segment_left
->s
);
226 free( p_segment_left
);
227 text_segment_Delete( p_segment_right
->s
);
228 free( p_segment_right
);
232 tx3g_segment_t
*p_next3g
= (*pp_segment
)->p_next3g
;
233 text_segment_Delete( (*pp_segment
)->s
);
235 *pp_segment
= ( p_segment_left
) ? p_segment_left
: p_segment_middle
;
236 if ( p_prev
) p_prev
->p_next3g
= *pp_segment
;
238 if ( p_segment_right
)
239 p_segment_right
->p_next3g
= p_next3g
;
241 p_segment_middle
->p_next3g
= p_next3g
;
243 text_style_Delete( p_segment_middle
->s
->style
);
244 p_segment_middle
->s
->style
= text_style_Duplicate( p_styles
);
249 /* Creates a new segment using the given style and split existing ones according
250 to the start & end offsets */
251 static void ApplySegmentStyle( tx3g_segment_t
**pp_segment
, const uint16_t i_absstart
,
252 const uint16_t i_absend
, const text_style_t
*p_styles
)
254 /* find the matching segment */
255 uint16_t i_curstart
= 0;
256 tx3g_segment_t
*p_prev
= NULL
;
257 tx3g_segment_t
*p_cur
= *pp_segment
;
260 uint16_t i_curend
= i_curstart
+ p_cur
->i_size
- 1;
261 if ( (i_absstart
>= i_curstart
) && (i_absend
<= i_curend
) )
264 if ( !SegmentSplit( p_prev
, &p_cur
, i_absstart
- i_curstart
,
265 i_absend
- i_curstart
, p_styles
) )
267 if ( !p_prev
) *pp_segment
= p_cur
;
272 i_curstart
+= p_cur
->i_size
;
274 p_cur
= p_cur
->p_next3g
;
279 /* Do relative size conversion using default style size (from stsd),
280 as the line should always be 5%. Apply to each segment specific text size */
281 static void FontSizeConvert( const text_style_t
*p_default_style
, text_style_t
*p_style
)
283 if( unlikely(!p_style
) )
287 else if( unlikely(!p_default_style
) || p_default_style
->i_font_size
== 0 )
289 p_style
->i_font_size
= 0;
290 p_style
->f_font_relsize
= 5.0;
294 p_style
->f_font_relsize
= 5.0 * (float) p_style
->i_font_size
/ p_default_style
->i_font_size
;
295 p_style
->i_font_size
= 0;
299 /*****************************************************************************
301 *****************************************************************************/
302 static int Decode( decoder_t
*p_dec
, block_t
*p_block
)
304 subpicture_t
*p_spu
= NULL
;
306 if( p_block
== NULL
) /* No Drain */
307 return VLCDEC_SUCCESS
;
309 if( ( p_block
->i_flags
& (BLOCK_FLAG_CORRUPTED
) ) ||
310 p_block
->i_buffer
< sizeof(uint16_t) )
312 block_Release( p_block
);
313 return VLCDEC_SUCCESS
;
316 uint8_t *p_buf
= p_block
->p_buffer
;
318 /* Read our raw string and create the styled segment for HTML */
319 uint16_t i_psz_bytelength
= GetWBE( p_buf
);
320 const uint8_t *p_pszstart
= p_block
->p_buffer
+ sizeof(uint16_t);
322 if ( i_psz_bytelength
> 2 &&
323 ( !memcmp( p_pszstart
, "\xFE\xFF", 2 ) || !memcmp( p_pszstart
, "\xFF\xFE", 2 ) )
326 psz_subtitle
= FromCharset( "UTF-16", p_pszstart
, i_psz_bytelength
);
328 return VLCDEC_SUCCESS
;
332 psz_subtitle
= malloc( i_psz_bytelength
+ 1 );
334 return VLCDEC_SUCCESS
;
335 memcpy( psz_subtitle
, p_pszstart
, i_psz_bytelength
);
336 psz_subtitle
[ i_psz_bytelength
] = '\0';
338 p_buf
+= i_psz_bytelength
+ sizeof(uint16_t);
340 for( uint16_t i
=0; i
< i_psz_bytelength
; i
++ )
341 if ( psz_subtitle
[i
] == '\r' ) psz_subtitle
[i
] = '\n';
343 tx3g_segment_t
*p_segment3g
= tx3g_segment_New( psz_subtitle
);
344 p_segment3g
->i_size
= str8len( psz_subtitle
);
345 if ( p_dec
->fmt_in
.subs
.p_style
)
346 p_segment3g
->s
->style
= text_style_Duplicate( p_dec
->fmt_in
.subs
.p_style
);
348 free( psz_subtitle
);
350 if ( !p_segment3g
->s
->psz_text
)
352 text_segment_Delete( p_segment3g
->s
);
354 return VLCDEC_SUCCESS
;
357 /* Create the subpicture unit */
358 p_spu
= decoder_NewSubpictureText( p_dec
);
361 text_segment_Delete( p_segment3g
->s
);
363 return VLCDEC_SUCCESS
;
365 subpicture_updater_sys_t
*p_spu_sys
= p_spu
->updater
.p_sys
;
367 /* Parse our styles */
368 while( (size_t)(p_buf
- p_block
->p_buffer
) + 8 < p_block
->i_buffer
)
370 uint32_t i_atomsize
= GetDWBE( p_buf
);
371 vlc_fourcc_t i_atomtype
= VLC_FOURCC(p_buf
[4],p_buf
[5],p_buf
[6],p_buf
[7]);
376 case VLC_FOURCC('s','t','y','l'):
378 if ( (size_t)(p_buf
- p_block
->p_buffer
) < 14 ) break;
379 uint16_t i_nbrecords
= GetWBE(p_buf
);
380 uint16_t i_cur_record
= 0;
382 while( i_cur_record
++ < i_nbrecords
)
384 if ( (size_t)(p_buf
- p_block
->p_buffer
) < 12 ) break;
385 uint16_t i_start
= __MIN( GetWBE(p_buf
), i_psz_bytelength
- 1 );
386 uint16_t i_end
= __MIN( GetWBE(p_buf
+ 2), i_psz_bytelength
- 1 );
389 memset( &style
, 0, sizeof(text_style_t
) );
390 style
.i_style_flags
= ConvertFlags( p_buf
[6] );
391 style
.i_font_size
= p_buf
[7];
392 style
.i_font_color
= GetDWBE(p_buf
+8) >> 8;// RGBA -> RGB
393 style
.i_font_alpha
= GetDWBE(p_buf
+8) & 0xFF;
394 style
.i_features
= STYLE_HAS_FONT_COLOR
| STYLE_HAS_FONT_ALPHA
;
395 ApplySegmentStyle( &p_segment3g
, i_start
, i_end
, &style
);
397 if ( i_nbrecords
== 1 )
401 if( (p_spu_sys
->p_default_style
->i_style_flags
= ConvertFlags( p_buf
[6] )) )
402 p_spu_sys
->p_default_style
->i_features
|= STYLE_HAS_FLAGS
;
404 p_spu_sys
->p_default_style
->i_font_size
= p_buf
[7];
405 p_spu_sys
->p_default_style
->i_font_color
= GetDWBE(p_buf
+8) >> 8;// RGBA -> ARGB
406 p_spu_sys
->p_default_style
->i_font_alpha
= (GetDWBE(p_buf
+8) & 0xFF) << 24;
407 p_spu_sys
->p_default_style
->i_features
|= (STYLE_HAS_FONT_COLOR
| STYLE_HAS_FONT_ALPHA
);
414 case VLC_FOURCC('d','r','p','o'):
415 if ( (size_t)(p_buf
- p_block
->p_buffer
) < 4 ) break;
416 p_spu_sys
->p_default_style
->i_shadow_width
= __MAX( GetWBE(p_buf
), GetWBE(p_buf
+2) );
419 case VLC_FOURCC('d','r','p','t'):
420 if ( (size_t)(p_buf
- p_block
->p_buffer
) < 2 ) break;
421 p_spu_sys
->p_default_style
->i_shadow_alpha
= GetWBE(p_buf
);
422 p_spu_sys
->p_default_style
->i_features
|= STYLE_HAS_SHADOW_ALPHA
;
432 p_spu
->i_start
= p_block
->i_pts
;
433 p_spu
->i_stop
= p_block
->i_pts
+ p_block
->i_length
;
434 p_spu
->b_ephemer
= (p_block
->i_length
== 0);
435 p_spu
->b_absolute
= false;
437 p_spu_sys
->region
.inner_align
= SUBPICTURE_ALIGN_BOTTOM
;
439 FontSizeConvert( p_dec
->fmt_in
.subs
.p_style
, p_spu_sys
->p_default_style
);
442 text_segment_t
*p_text_segments
= p_segment3g
->s
;
443 text_segment_t
*p_cur
= p_text_segments
;
446 FontSizeConvert( p_dec
->fmt_in
.subs
.p_style
, p_segment3g
->s
->style
);
448 tx3g_segment_t
* p_old
= p_segment3g
;
449 p_segment3g
= p_segment3g
->p_next3g
;
452 p_cur
->p_next
= p_segment3g
->s
;
453 p_cur
= p_cur
->p_next
;
456 p_spu_sys
->region
.p_segments
= p_text_segments
;
458 block_Release( p_block
);
460 decoder_QueueSub( p_dec
, p_spu
);
461 return VLCDEC_SUCCESS
;