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>
32 #include "../demux/mp4/minibox.h"
34 /*****************************************************************************
36 *****************************************************************************/
37 static int OpenDecoder ( vlc_object_t
* );
38 static void CloseDecoder ( vlc_object_t
* );
39 static int Decode( decoder_t
*, block_t
* );
41 static int OpenEncoder ( vlc_object_t
* );
42 static block_t
* Encode( encoder_t
*, subpicture_t
* );
46 set_description( N_("tx3g subtitles decoder") )
47 set_shortname( N_("tx3g subtitles") )
48 set_capability( "spu decoder", 100 )
49 set_category( CAT_INPUT
)
50 set_subcategory( SUBCAT_INPUT_SCODEC
)
51 set_callbacks( OpenDecoder
, CloseDecoder
)
54 set_description( N_("tx3g subtitles encoder") )
55 set_shortname( N_("tx3g subtitles encoder") )
56 set_capability( "encoder", 101 )
57 set_callbacks( OpenEncoder
, NULL
)
61 /****************************************************************************
63 ****************************************************************************/
65 /*****************************************************************************
67 *****************************************************************************/
69 #define FONT_FACE_BOLD 0x1
70 #define FONT_FACE_ITALIC 0x2
71 #define FONT_FACE_UNDERLINE 0x4
73 static int ConvertToVLCFlags( int i_atomflags
)
75 int i_vlcstyles_flags
= 0;
76 if ( i_atomflags
& FONT_FACE_BOLD
)
77 i_vlcstyles_flags
|= STYLE_BOLD
;
78 if ( i_atomflags
& FONT_FACE_ITALIC
)
79 i_vlcstyles_flags
|= STYLE_ITALIC
;
80 if ( i_atomflags
& FONT_FACE_UNDERLINE
)
81 i_vlcstyles_flags
|= STYLE_UNDERLINE
;
82 return i_vlcstyles_flags
;
85 static size_t str8len( const char *psz_string
)
87 const char *psz_tmp
= psz_string
;
91 if ( (*psz_tmp
& 0xC0) != 0x80 ) i
++;
97 static char * str8indup( const char *psz_string
, size_t i_skip
, size_t n
)
99 while( i_skip
&& *psz_string
)
101 if ( (*psz_string
& 0xC0) != 0x80 ) i_skip
--;
104 if ( ! *psz_string
|| i_skip
) return NULL
;
106 const char *psz_tmp
= psz_string
;
107 while( n
&& *psz_tmp
)
109 if ( (*psz_tmp
& 0xC0) != 0x80 ) n
--;
112 return strndup( psz_string
, psz_tmp
- psz_string
);
115 typedef struct tx3g_segment_t tx3g_segment_t
;
117 struct tx3g_segment_t
121 tx3g_segment_t
*p_next3g
;
124 static tx3g_segment_t
* tx3g_segment_New( const char *psz_string
)
126 tx3g_segment_t
*p_seg
= malloc( sizeof(tx3g_segment_t
) );
130 p_seg
->p_next3g
= NULL
;
131 p_seg
->s
= text_segment_New( psz_string
);
141 static void SegmentDoSplit( tx3g_segment_t
*p_segment
, uint16_t i_start
, uint16_t i_end
,
142 tx3g_segment_t
**pp_segment_left
,
143 tx3g_segment_t
**pp_segment_middle
,
144 tx3g_segment_t
**pp_segment_right
)
146 tx3g_segment_t
*p_segment_left
= NULL
, *p_segment_right
= NULL
, *p_segment_middle
= NULL
;
148 if ( (p_segment
->i_size
- i_start
< 1) || (p_segment
->i_size
- i_end
< 1) )
153 char* psz_text
= str8indup( p_segment
->s
->psz_text
, 0, i_start
);
154 p_segment_left
= tx3g_segment_New( psz_text
);
156 if ( !p_segment_left
) goto error
;
157 p_segment_left
->s
->style
= text_style_Duplicate( p_segment
->s
->style
);
158 p_segment_left
->i_size
= str8len( p_segment_left
->s
->psz_text
);
161 char* psz_midtext
= str8indup( p_segment
->s
->psz_text
, i_start
, i_end
- i_start
+ 1 );
162 p_segment_middle
= tx3g_segment_New( psz_midtext
);
164 if ( !p_segment_middle
) goto error
;
165 p_segment_middle
->s
->style
= text_style_Duplicate( p_segment
->s
->style
);
166 p_segment_middle
->i_size
= str8len( p_segment_middle
->s
->psz_text
);
168 if ( i_end
< (p_segment
->i_size
- 1) )
170 char* psz_text
= str8indup( p_segment
->s
->psz_text
, i_end
+ 1, p_segment
->i_size
- i_end
- 1 );
171 p_segment_right
= tx3g_segment_New( psz_text
);
173 if ( !p_segment_right
) goto error
;
174 p_segment_right
->s
->style
= text_style_Duplicate( p_segment
->s
->style
);
175 p_segment_right
->i_size
= str8len( p_segment_right
->s
->psz_text
);
178 if ( p_segment_left
) p_segment_left
->p_next3g
= p_segment_middle
;
179 if ( p_segment_right
) p_segment_middle
->p_next3g
= p_segment_right
;
181 *pp_segment_left
= p_segment_left
;
182 *pp_segment_middle
= p_segment_middle
;
183 *pp_segment_right
= p_segment_right
;
188 if( p_segment_middle
)
190 text_segment_Delete( p_segment_middle
->s
);
191 free( p_segment_middle
);
195 text_segment_Delete( p_segment_left
->s
);
196 free( p_segment_left
);
198 *pp_segment_left
= *pp_segment_middle
= *pp_segment_right
= NULL
;
201 static bool SegmentSplit( tx3g_segment_t
*p_prev
, tx3g_segment_t
**pp_segment
,
202 const uint16_t i_start
, const uint16_t i_end
,
203 const text_style_t
*p_styles
)
205 tx3g_segment_t
*p_segment_left
= NULL
, *p_segment_middle
= NULL
, *p_segment_right
= NULL
;
207 if ( (*pp_segment
)->i_size
== 0 ) return false;
208 if ( i_start
> i_end
) return false;
209 if ( (size_t)(i_end
- i_start
) > (*pp_segment
)->i_size
- 1 ) return false;
210 if ( i_end
> (*pp_segment
)->i_size
- 1 ) return false;
212 SegmentDoSplit( *pp_segment
, i_start
, i_end
, &p_segment_left
, &p_segment_middle
, &p_segment_right
);
213 if ( !p_segment_middle
)
216 text_segment_Delete( p_segment_left
->s
);
217 free( p_segment_left
);
218 text_segment_Delete( p_segment_right
->s
);
219 free( p_segment_right
);
223 tx3g_segment_t
*p_next3g
= (*pp_segment
)->p_next3g
;
224 text_segment_Delete( (*pp_segment
)->s
);
226 *pp_segment
= ( p_segment_left
) ? p_segment_left
: p_segment_middle
;
227 if ( p_prev
) p_prev
->p_next3g
= *pp_segment
;
229 if ( p_segment_right
)
230 p_segment_right
->p_next3g
= p_next3g
;
232 p_segment_middle
->p_next3g
= p_next3g
;
234 if( p_segment_middle
->s
->style
)
235 text_style_Merge( p_segment_middle
->s
->style
, p_styles
, true );
237 p_segment_middle
->s
->style
= text_style_Duplicate( p_styles
);
242 /* Creates a new segment using the given style and split existing ones according
243 to the start & end offsets */
244 static void ApplySegmentStyle( tx3g_segment_t
**pp_segment
, const uint16_t i_absstart
,
245 const uint16_t i_absend
, const text_style_t
*p_styles
)
247 /* find the matching segment */
248 uint16_t i_curstart
= 0;
249 tx3g_segment_t
*p_prev
= NULL
;
250 tx3g_segment_t
*p_cur
= *pp_segment
;
253 uint16_t i_curend
= i_curstart
+ p_cur
->i_size
- 1;
254 if ( (i_absstart
>= i_curstart
) && (i_absend
<= i_curend
) )
257 if ( !SegmentSplit( p_prev
, &p_cur
, i_absstart
- i_curstart
,
258 i_absend
- i_curstart
, p_styles
) )
260 if ( !p_prev
) *pp_segment
= p_cur
;
265 i_curstart
+= p_cur
->i_size
;
267 p_cur
= p_cur
->p_next3g
;
272 /* Do relative size conversion using default style size (from stsd),
273 as the line should always be 5%. Apply to each segment specific text size */
274 static void FontSizeConvert( const text_style_t
*p_reference
, text_style_t
*p_style
)
276 if( unlikely(!p_style
) )
280 else if( unlikely(!p_reference
) || p_reference
->i_font_size
== 0 )
282 p_style
->i_font_size
= 0;
283 p_style
->f_font_relsize
= 5.0;
287 p_style
->f_font_relsize
= 5.0 * (float) p_style
->i_font_size
/ p_reference
->i_font_size
;
288 p_style
->i_font_size
= 0;
292 /*****************************************************************************
294 *****************************************************************************/
295 static int Decode( decoder_t
*p_dec
, block_t
*p_block
)
297 subpicture_t
*p_spu
= NULL
;
299 if( p_block
== NULL
) /* No Drain */
300 return VLCDEC_SUCCESS
;
302 if( ( p_block
->i_flags
& (BLOCK_FLAG_CORRUPTED
) ) ||
303 p_block
->i_buffer
< sizeof(uint16_t) )
305 block_Release( p_block
);
306 return VLCDEC_SUCCESS
;
309 uint8_t *p_buf
= p_block
->p_buffer
;
311 /* Read our raw string and create the styled segment for HTML */
312 uint16_t i_psz_bytelength
= GetWBE( p_buf
);
313 if( p_block
->i_buffer
< i_psz_bytelength
+ 2U )
315 block_Release( p_block
);
316 return VLCDEC_SUCCESS
;
319 const uint8_t *p_pszstart
= p_block
->p_buffer
+ sizeof(uint16_t);
321 if ( i_psz_bytelength
> 2 &&
322 ( !memcmp( p_pszstart
, "\xFE\xFF", 2 ) || !memcmp( p_pszstart
, "\xFF\xFE", 2 ) )
325 psz_subtitle
= FromCharset( "UTF-16", p_pszstart
, i_psz_bytelength
);
327 return VLCDEC_SUCCESS
;
331 psz_subtitle
= strndup( (const char*) p_pszstart
, i_psz_bytelength
);
333 return VLCDEC_SUCCESS
;
335 p_buf
+= i_psz_bytelength
+ sizeof(uint16_t);
337 for( uint16_t i
=0; i
< i_psz_bytelength
; i
++ )
338 if ( psz_subtitle
[i
] == '\r' ) psz_subtitle
[i
] = '\n';
340 tx3g_segment_t
*p_segment3g
= tx3g_segment_New( psz_subtitle
);
341 p_segment3g
->i_size
= str8len( psz_subtitle
);
342 free( psz_subtitle
);
344 if ( !p_segment3g
->s
->psz_text
)
346 text_segment_Delete( p_segment3g
->s
);
348 return VLCDEC_SUCCESS
;
351 /* Create the subpicture unit */
352 p_spu
= decoder_NewSubpictureText( p_dec
);
355 text_segment_Delete( p_segment3g
->s
);
357 return VLCDEC_SUCCESS
;
360 subtext_updater_sys_t
*p_spu_sys
= p_spu
->updater
.p_sys
;
361 const text_style_t
*p_root_style
= (text_style_t
*) p_dec
->p_sys
;
363 mp4_box_iterator_t it
;
364 mp4_box_iterator_Init( &it
, p_buf
,
365 p_block
->i_buffer
- (p_buf
- p_block
->p_buffer
) );
366 /* Parse our styles */
367 if( p_dec
->fmt_in
.i_codec
!= VLC_CODEC_QTXT
)
368 while( mp4_box_iterator_Next( &it
) )
373 case VLC_FOURCC('s','t','y','l'):
375 if( it
.i_payload
< 14 )
378 uint16_t i_nbrecords
= GetWBE(it
.p_payload
);
379 uint16_t i_cur_record
= 0;
381 it
.p_payload
+= 2; it
.i_payload
-= 2;
382 while( i_cur_record
++ < i_nbrecords
&& it
.i_payload
>= 12 )
384 uint16_t i_start
= __MIN( GetWBE(it
.p_payload
), i_psz_bytelength
- 1 );
385 uint16_t i_end
= GetWBE(it
.p_payload
+ 2); /* index is past last char */
386 if( i_start
< i_end
)
388 i_end
= VLC_CLIP( i_end
- 1, i_start
, i_psz_bytelength
- 1 );
390 text_style_t
*p_style
= text_style_Create( STYLE_NO_DEFAULTS
);
393 if( (p_style
->i_style_flags
= ConvertToVLCFlags( it
.p_payload
[6] )) )
394 p_style
->i_features
|= STYLE_HAS_FLAGS
;
395 p_style
->i_font_size
= it
.p_payload
[7];
396 p_style
->i_font_color
= GetDWBE(&it
.p_payload
[8]) >> 8;// RGBA -> RGB
397 p_style
->i_font_alpha
= GetDWBE(&it
.p_payload
[8]) & 0xFF;
398 p_style
->i_features
|= STYLE_HAS_FONT_COLOR
| STYLE_HAS_FONT_ALPHA
;
399 ApplySegmentStyle( &p_segment3g
, i_start
, i_end
, p_style
);
400 text_style_Delete( p_style
);
404 it
.p_payload
+= 12; it
.i_payload
-= 12;
415 p_spu
->i_start
= p_block
->i_pts
;
416 p_spu
->i_stop
= p_block
->i_pts
+ p_block
->i_length
;
417 p_spu
->b_ephemer
= (p_block
->i_length
== 0);
418 p_spu
->b_absolute
= false;
420 p_spu_sys
->region
.align
= SUBPICTURE_ALIGN_BOTTOM
;
422 text_style_Merge( p_spu_sys
->p_default_style
, p_root_style
, true );
423 FontSizeConvert( p_root_style
, p_spu_sys
->p_default_style
);
426 text_segment_t
*p_text_segments
= p_segment3g
->s
;
427 text_segment_t
*p_cur
= p_text_segments
;
430 FontSizeConvert( p_root_style
, p_segment3g
->s
->style
);
432 tx3g_segment_t
* p_old
= p_segment3g
;
433 p_segment3g
= p_segment3g
->p_next3g
;
436 p_cur
->p_next
= p_segment3g
->s
;
437 p_cur
= p_cur
->p_next
;
440 p_spu_sys
->region
.p_segments
= p_text_segments
;
442 block_Release( p_block
);
444 decoder_QueueSub( p_dec
, p_spu
);
445 return VLCDEC_SUCCESS
;
448 /*****************************************************************************
450 *****************************************************************************/
451 static void ParseExtradataTx3g( decoder_t
*p_dec
)
453 text_style_t
*p_style
= (text_style_t
*) p_dec
->p_sys
;
454 const uint8_t *p_extra
= p_dec
->fmt_in
.p_extra
;
456 if( p_dec
->fmt_in
.i_extra
< 32 )
463 p_style
->i_background_color
= GetDWBE(&p_extra
[6]) >> 8;
464 p_style
->i_background_alpha
= p_extra
[9];
465 p_style
->i_features
|= STYLE_HAS_BACKGROUND_COLOR
|STYLE_HAS_BACKGROUND_ALPHA
;
469 /* StyleRecord @18 */
470 p_style
->i_style_flags
= ConvertToVLCFlags( p_extra
[24] );
471 if( p_style
->i_style_flags
)
472 p_style
->i_features
|= STYLE_HAS_FLAGS
;
473 p_style
->i_font_size
= p_extra
[25];
474 p_style
->i_font_color
= GetDWBE(&p_extra
[26]) >> 8;// RGBA -> RGB
475 p_style
->i_font_alpha
= p_extra
[29];
476 p_style
->i_features
|= STYLE_HAS_FONT_COLOR
| STYLE_HAS_FONT_ALPHA
;
478 /* FontTableBox @30 */
481 static void ParseExtradataTextMedia( decoder_t
*p_dec
)
483 text_style_t
*p_style
= (text_style_t
*) p_dec
->p_sys
;
484 const uint8_t *p_extra
= p_dec
->fmt_in
.p_extra
;
486 if( p_dec
->fmt_in
.i_extra
< 44 )
492 /* BGColor @8, read top of 16 bits */
493 p_style
->i_background_color
= (p_extra
[8] << 16) |
496 p_style
->i_features
|= STYLE_HAS_BACKGROUND_COLOR
;
499 /* Reserved 64 @22 */
503 p_style
->i_style_flags
= ConvertToVLCFlags( GetWBE(&p_extra
[32]) );
504 if( p_style
->i_style_flags
)
505 p_style
->i_features
|= STYLE_HAS_FLAGS
;
507 /* Reserved 16 @35 */
509 p_style
->i_font_color
= (p_extra
[37] << 16) |
512 p_style
->i_features
|= STYLE_HAS_FONT_COLOR
;
514 /* FontName Pascal (8 + string) @43 */
516 /*****************************************************************************
517 * Decoder entry/exit points
518 *****************************************************************************/
519 static void CloseDecoder( vlc_object_t
*p_this
)
521 decoder_t
*p_dec
= (decoder_t
*) p_this
;
522 text_style_Delete( (text_style_t
*) p_dec
->p_sys
);
525 static int OpenDecoder( vlc_object_t
*p_this
)
527 decoder_t
*p_dec
= (decoder_t
*) p_this
;
529 if( p_dec
->fmt_in
.i_codec
!= VLC_CODEC_TX3G
&&
530 p_dec
->fmt_in
.i_codec
!= VLC_CODEC_QTXT
)
533 p_dec
->pf_decode
= Decode
;
535 p_dec
->p_sys
= text_style_Create( STYLE_NO_DEFAULTS
);
539 if( p_dec
->fmt_in
.i_codec
== VLC_CODEC_TX3G
)
540 ParseExtradataTx3g( p_dec
);
542 ParseExtradataTextMedia( p_dec
);
544 p_dec
->fmt_out
.i_codec
= VLC_CODEC_TEXT
;
549 /*****************************************************************************
551 *****************************************************************************/
553 static void FillExtradataTx3g( void **pp_extra
, int *pi_extra
)
555 size_t i_extra
= 32 + 37;
556 uint8_t *p_extra
= calloc( 1, i_extra
);
559 p_extra
[4] = 0x01;/* 1 center, horizontal */
560 p_extra
[5] = 0xFF;/* -1 bottom, vertical */
561 SetDWBE( &p_extra
[6], 0x000000FFU
); /* bgcolor */
562 p_extra
[25] = STYLE_DEFAULT_FONT_SIZE
;
563 SetDWBE( &p_extra
[26], 0xFFFFFFFFU
); /* fgcolor */
566 SetDWBE(&p_extra
[32], 8 + 2 + 6 + 11 + 10);
567 memcpy(&p_extra
[36], "ftab", 4);
569 SetWBE(&p_extra
[40], 3); /* entry count */
572 memcpy(&p_extra
[42], "Serif", 5);
575 memcpy(&p_extra
[48], "Sans-serif", 10);
578 memcpy(&p_extra
[59], "Monospace", 9);
585 static int OpenEncoder( vlc_object_t
*p_this
)
587 encoder_t
*p_enc
= (encoder_t
*)p_this
;
589 if( p_enc
->fmt_out
.i_codec
!= VLC_CODEC_TX3G
)
592 p_enc
->fmt_in
.i_codec
= VLC_CODEC_TEXT
;
596 p_enc
->pf_encode_sub
= Encode
;
597 p_enc
->fmt_out
.i_cat
= SPU_ES
;
599 if( !p_enc
->fmt_out
.i_extra
)
600 FillExtradataTx3g( &p_enc
->fmt_out
.p_extra
, &p_enc
->fmt_out
.i_extra
);
605 static int ConvertFromVLCFlags( const text_style_t
*p_style
)
608 if( p_style
->i_features
& STYLE_HAS_FLAGS
)
610 if ( p_style
->i_style_flags
& STYLE_BOLD
)
611 i_atomflags
|= FONT_FACE_BOLD
;
612 if ( p_style
->i_style_flags
& STYLE_ITALIC
)
613 i_atomflags
|= FONT_FACE_ITALIC
;
614 if ( p_style
->i_style_flags
& STYLE_UNDERLINE
)
615 i_atomflags
|= FONT_FACE_UNDERLINE
;
620 static uint32_t ConvertFromVLCColor( const text_style_t
*p_style
)
623 if( p_style
->i_features
& STYLE_HAS_FONT_COLOR
)
624 rgba
= ((uint32_t)p_style
->i_font_color
) << 8;
627 if( p_style
->i_features
& STYLE_HAS_FONT_ALPHA
)
628 rgba
|= p_style
->i_font_alpha
;
634 static bool NeedStyling( const text_segment_t
*p_segment
)
636 const text_style_t
*p_style
= p_segment
->style
;
640 if( p_style
->i_features
& STYLE_HAS_FLAGS
)
642 if( p_style
->i_style_flags
& (STYLE_BOLD
|STYLE_ITALIC
|STYLE_UNDERLINE
) )
646 if( p_style
->i_features
& (STYLE_HAS_FONT_COLOR
|STYLE_HAS_FONT_ALPHA
) )
652 static block_t
*GetStylBlock( const text_segment_t
*p_segment
, size_t i_styles
)
655 block_t
*p_styl
= block_Alloc( 10 + 12 * i_styles
);
658 SetDWBE( p_styl
->p_buffer
, p_styl
->i_buffer
);
659 memcpy( &p_styl
->p_buffer
[4], "styl", 4 );
660 SetWBE( &p_styl
->p_buffer
[8], i_styles
);
661 p_styl
->i_buffer
= 10;
662 for( ; p_segment
; p_segment
= p_segment
->p_next
)
664 size_t i_len
= str8len( p_segment
->psz_text
);
665 if( NeedStyling( p_segment
) )
667 uint8_t *p
= &p_styl
->p_buffer
[p_styl
->i_buffer
];
668 SetWBE( &p
[0], i_start
);
669 SetWBE( &p
[2], i_start
+ i_len
);
671 p
[6] = ConvertFromVLCFlags( p_segment
->style
);
672 p
[7] = STYLE_DEFAULT_FONT_SIZE
;
673 SetDWBE(&p
[8], ConvertFromVLCColor( p_segment
->style
) );
674 p_styl
->i_buffer
+= 12;
682 static block_t
* Encode( encoder_t
*p_enc
, subpicture_t
*p_spu
)
685 const text_segment_t
*p_segments
= (p_spu
->p_region
)
686 ? p_spu
->p_region
->p_text
691 for(const text_segment_t
*p_segment
= p_segments
;
692 p_segment
; p_segment
= p_segment
->p_next
)
694 if( p_segment
->style
)
696 i_len
+= strlen( p_segment
->psz_text
);
699 block_t
*p_block
= block_Alloc( i_len
+ 2 );
703 SetWBE(p_block
->p_buffer
, i_len
);
704 p_block
->i_buffer
= 2;
705 for(const text_segment_t
*p_segment
= p_segments
;
706 p_segment
; p_segment
= p_segment
->p_next
)
708 size_t i_seglen
= strlen(p_segment
->psz_text
);
709 memcpy(&p_block
->p_buffer
[p_block
->i_buffer
],
710 p_segment
->psz_text
, i_seglen
);
711 p_block
->i_buffer
+= i_seglen
;
713 p_block
->i_dts
= p_block
->i_pts
= p_spu
->i_start
;
714 p_block
->i_length
= p_spu
->i_stop
- p_spu
->i_start
;
717 p_block
->p_next
= GetStylBlock( p_segments
, i_styles
);
719 return block_ChainGather( p_block
);