1 /*****************************************************************************
2 * subtitle.c: Demux for subtitle text files.
3 *****************************************************************************
4 * Copyright (C) 1999-2007 VLC authors and VideoLAN
7 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
8 * Derk-Jan Hartman <hartman at videolan dot org>
9 * Jean-Baptiste Kempf <jb@videolan.org>
11 * This program is free software; you can redistribute it and/or modify it
12 * under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation; either version 2.1 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Lesser General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public License
22 * along with this program; if not, write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24 *****************************************************************************/
26 /*****************************************************************************
28 *****************************************************************************/
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
36 #include <vlc_input.h>
42 #include <vlc_demux.h>
43 #include <vlc_charset.h>
45 /*****************************************************************************
47 *****************************************************************************/
48 static int Open ( vlc_object_t
*p_this
);
49 static void Close( vlc_object_t
*p_this
);
51 #define SUB_TYPE_LONGTEXT \
52 N_("Force the subtiles format. Selecting \"auto\" means autodetection and should always work.")
53 #define SUB_DESCRIPTION_LONGTEXT \
54 N_("Override the default track description.")
56 static const char *const ppsz_sub_type
[] =
58 "auto", "microdvd", "subrip", "subviewer", "ssa1",
59 "ssa2-4", "ass", "vplayer", "sami", "dvdsubtitle", "mpl2",
60 "aqt", "pjs", "mpsub", "jacosub", "psb", "realtext", "dks",
65 set_shortname( N_("Subtitles"))
66 set_description( N_("Text subtitle parser") )
67 set_capability( "demux", 0 )
68 set_category( CAT_INPUT
)
69 set_subcategory( SUBCAT_INPUT_DEMUX
)
70 add_string( "sub-type", "auto", N_("Subtitle format"),
71 SUB_TYPE_LONGTEXT
, true )
72 change_string_list( ppsz_sub_type
, ppsz_sub_type
)
73 add_string( "sub-description", NULL
, N_("Subtitle description"),
74 SUB_DESCRIPTION_LONGTEXT
, true )
75 set_callbacks( Open
, Close
)
77 add_shortcut( "subtitle" )
80 /*****************************************************************************
82 *****************************************************************************/
85 SUB_TYPE_UNKNOWN
= -1,
93 SUB_TYPE_SUBVIEWER
, /* SUBVIEWER 2 */
94 SUB_TYPE_DVDSUBTITLE
, /* Mplayer calls it subviewer2 */
103 SUB_TYPE_SUBVIEW1
, /* SUBVIEWER 1 - mplayer calls it subrip09,
104 and Gnome subtitles SubViewer 1.0 */
106 SUB_TYPE_SCC
, /* Scenarist Closed Caption */
116 static int TextLoad( text_t
*, stream_t
*s
);
117 static void TextUnload( text_t
* );
129 enum subtitle_type_e i_type
;
130 vlc_tick_t i_microsecperframe
;
132 char *psz_header
; /* SSA */
139 int i_time_resolution
;
153 const char *psz_start
;
165 vlc_tick_t i_next_demux_date
;
177 subs_properties_t props
;
179 block_t
* (*pf_convert
)( const subtitle_t
* );
182 static int ParseMicroDvd ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
183 static int ParseSubRip ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
184 static int ParseSubViewer ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
185 static int ParseSSA ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
186 static int ParseVplayer ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
187 static int ParseSami ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
188 static int ParseDVDSubtitle( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
189 static int ParseMPL2 ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
190 static int ParseAQT ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
191 static int ParsePJS ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
192 static int ParseMPSub ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
193 static int ParseJSS ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
194 static int ParsePSB ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
195 static int ParseRealText ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
196 static int ParseDKS ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
197 static int ParseSubViewer1 ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
198 static int ParseCommonSBV ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
199 static int ParseSCC ( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
203 const char *psz_type_name
;
205 const char *psz_name
;
206 int (*pf_read
)( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
207 } sub_read_subtitle_function
[] =
209 { "microdvd", SUB_TYPE_MICRODVD
, "MicroDVD", ParseMicroDvd
},
210 { "subrip", SUB_TYPE_SUBRIP
, "SubRIP", ParseSubRip
},
211 { "subviewer", SUB_TYPE_SUBVIEWER
, "SubViewer", ParseSubViewer
},
212 { "ssa1", SUB_TYPE_SSA1
, "SSA-1", ParseSSA
},
213 { "ssa2-4", SUB_TYPE_SSA2_4
, "SSA-2/3/4", ParseSSA
},
214 { "ass", SUB_TYPE_ASS
, "SSA/ASS", ParseSSA
},
215 { "vplayer", SUB_TYPE_VPLAYER
, "VPlayer", ParseVplayer
},
216 { "sami", SUB_TYPE_SAMI
, "SAMI", ParseSami
},
217 { "dvdsubtitle",SUB_TYPE_DVDSUBTITLE
, "DVDSubtitle", ParseDVDSubtitle
},
218 { "mpl2", SUB_TYPE_MPL2
, "MPL2", ParseMPL2
},
219 { "aqt", SUB_TYPE_AQT
, "AQTitle", ParseAQT
},
220 { "pjs", SUB_TYPE_PJS
, "PhoenixSub", ParsePJS
},
221 { "mpsub", SUB_TYPE_MPSUB
, "MPSub", ParseMPSub
},
222 { "jacosub", SUB_TYPE_JACOSUB
, "JacoSub", ParseJSS
},
223 { "psb", SUB_TYPE_PSB
, "PowerDivx", ParsePSB
},
224 { "realtext", SUB_TYPE_RT
, "RealText", ParseRealText
},
225 { "dks", SUB_TYPE_DKS
, "DKS", ParseDKS
},
226 { "subviewer1", SUB_TYPE_SUBVIEW1
, "Subviewer 1", ParseSubViewer1
},
227 { "sbv", SUB_TYPE_SBV
, "SBV", ParseCommonSBV
},
228 { "scc", SUB_TYPE_SCC
, "SCC", ParseSCC
},
229 { NULL
, SUB_TYPE_UNKNOWN
, "Unknown", NULL
}
231 /* When adding support for more formats, be sure to add their file extension
232 * to src/input/subtitles.c to enable auto-detection.
235 static int Demux( demux_t
* );
236 static int Control( demux_t
*, int, va_list );
238 static void Fix( demux_t
* );
239 static char * get_language_from_filename( const char * );
241 /*****************************************************************************
242 * Decoder format output function
243 *****************************************************************************/
245 static block_t
*ToTextBlock( const subtitle_t
*p_subtitle
)
248 size_t i_len
= strlen( p_subtitle
->psz_text
) + 1;
250 if( i_len
<= 1 || !(p_block
= block_Alloc( i_len
)) )
253 memcpy( p_block
->p_buffer
, p_subtitle
->psz_text
, i_len
);
258 static block_t
*ToEIA608Block( const subtitle_t
*p_subtitle
)
261 const size_t i_len
= strlen( p_subtitle
->psz_text
);
262 const size_t i_block
= (1 + i_len
/ 5) * 3;
264 if( i_len
< 4 || !(p_block
= block_Alloc( i_block
)) )
267 p_block
->i_buffer
= 0;
269 char *saveptr
= NULL
;
270 char *psz_tok
= strtok_r( p_subtitle
->psz_text
, " ", &saveptr
);
273 sscanf( psz_tok
, "%2x%2x", &a
, &b
) == 2 &&
274 i_block
- p_block
->i_buffer
>= 3 )
276 uint8_t *p_data
= &p_block
->p_buffer
[p_block
->i_buffer
];
280 p_block
->i_buffer
+= 3;
281 psz_tok
= strtok_r( NULL
, " ", &saveptr
);
287 /*****************************************************************************
289 *****************************************************************************/
290 static int Open ( vlc_object_t
*p_this
)
292 demux_t
*p_demux
= (demux_t
*)p_this
;
297 int (*pf_read
)( vlc_object_t
*, subs_properties_t
*, text_t
*, subtitle_t
*, size_t );
299 if( !p_demux
->obj
.force
)
301 msg_Dbg( p_demux
, "subtitle demux discarded" );
305 p_demux
->pf_demux
= Demux
;
306 p_demux
->pf_control
= Control
;
307 p_demux
->p_sys
= p_sys
= malloc( sizeof( demux_sys_t
) );
311 p_sys
->b_slave
= false;
312 p_sys
->b_first_time
= true;
313 p_sys
->i_next_demux_date
= 0;
316 p_sys
->pf_convert
= ToTextBlock
;
318 p_sys
->subtitles
.i_current
= 0;
319 p_sys
->subtitles
.i_count
= 0;
320 p_sys
->subtitles
.p_array
= NULL
;
322 p_sys
->props
.psz_header
= NULL
;
323 p_sys
->props
.i_microsecperframe
= VLC_TICK_FROM_MS(40);
324 p_sys
->props
.jss
.b_inited
= false;
325 p_sys
->props
.mpsub
.b_inited
= false;
326 p_sys
->props
.sami
.psz_start
= NULL
;
329 f_fps
= var_CreateGetFloat( p_demux
, "sub-original-fps" );
332 p_sys
->props
.i_microsecperframe
= llroundf( (float)CLOCK_FREQ
/ f_fps
);
333 msg_Dbg( p_demux
, "Override subtitle fps %f", (double) f_fps
);
336 /* Get or probe the type */
337 p_sys
->props
.i_type
= SUB_TYPE_UNKNOWN
;
338 psz_type
= var_CreateGetString( p_demux
, "sub-type" );
339 if( psz_type
&& *psz_type
)
341 for( int i
= 0; ; i
++ )
343 if( sub_read_subtitle_function
[i
].psz_type_name
== NULL
)
346 if( !strcmp( sub_read_subtitle_function
[i
].psz_type_name
,
349 p_sys
->props
.i_type
= sub_read_subtitle_function
[i
].i_type
;
357 const uint64_t i_start_pos
= vlc_stream_Tell( p_demux
->s
);
361 const uint8_t *p_peek
;
362 if( vlc_stream_Peek( p_demux
->s
, &p_peek
, 16 ) < 16 )
375 const char *psz_bom
= NULL
;
378 /* Detect Unicode while skipping the UTF-8 Byte Order Mark */
379 if( !memcmp( p_peek
, "\xEF\xBB\xBF", 3 ) )
384 else if( !memcmp( p_peek
, "\xFF\xFE", 2 ) )
387 psz_bom
= "UTF-16LE";
390 else if( !memcmp( p_peek
, "\xFE\xFF", 2 ) )
393 psz_bom
= "UTF-16BE";
398 msg_Dbg( p_demux
, "detected %s Byte Order Mark", psz_bom
);
400 i_peek
= vlc_stream_Peek( p_demux
->s
, &p_peek
, i_peek
);
401 if( unlikely(i_peek
< 16) )
407 stream_t
*p_probestream
= NULL
;
408 if( e_bom
!= UTF8BOM
&& e_bom
!= NOBOM
)
412 char *p_outbuf
= FromCharset( psz_bom
, p_peek
, i_peek
);
413 if( p_outbuf
!= NULL
)
414 p_probestream
= vlc_stream_MemoryNew( p_demux
, (uint8_t *)p_outbuf
,
416 false ); /* free p_outbuf on release */
421 const size_t i_skip
= (e_bom
== UTF8BOM
) ? 3 : 0;
422 p_probestream
= vlc_stream_MemoryNew( p_demux
, (uint8_t *) &p_peek
[i_skip
],
423 i_peek
- i_skip
, true );
426 if( p_probestream
== NULL
)
432 /* Probe if unknown type */
433 if( p_sys
->props
.i_type
== SUB_TYPE_UNKNOWN
)
438 msg_Dbg( p_demux
, "autodetecting subtitle format" );
439 for( i_try
= 0; i_try
< 256; i_try
++ )
444 if( (s
= vlc_stream_ReadLine( p_probestream
) ) == NULL
)
447 if( strcasestr( s
, "<SAMI>" ) )
449 p_sys
->props
.i_type
= SUB_TYPE_SAMI
;
452 else if( sscanf( s
, "{%d}{%d}", &i_dummy
, &i_dummy
) == 2 ||
453 sscanf( s
, "{%d}{}", &i_dummy
) == 1)
455 p_sys
->props
.i_type
= SUB_TYPE_MICRODVD
;
458 else if( sscanf( s
, "%d:%d:%d,%d --> %d:%d:%d,%d",
459 &i_dummy
,&i_dummy
,&i_dummy
,&i_dummy
,
460 &i_dummy
,&i_dummy
,&i_dummy
,&i_dummy
) == 8 ||
461 sscanf( s
, "%d:%d:%d --> %d:%d:%d,%d",
462 &i_dummy
,&i_dummy
,&i_dummy
,&i_dummy
,
463 &i_dummy
,&i_dummy
,&i_dummy
) == 7 ||
464 sscanf( s
, "%d:%d:%d,%d --> %d:%d:%d",
465 &i_dummy
,&i_dummy
,&i_dummy
,&i_dummy
,
466 &i_dummy
,&i_dummy
,&i_dummy
) == 7 ||
467 sscanf( s
, "%d:%d:%d.%d --> %d:%d:%d.%d",
468 &i_dummy
,&i_dummy
,&i_dummy
,&i_dummy
,
469 &i_dummy
,&i_dummy
,&i_dummy
,&i_dummy
) == 8 ||
470 sscanf( s
, "%d:%d:%d --> %d:%d:%d.%d",
471 &i_dummy
,&i_dummy
,&i_dummy
,&i_dummy
,
472 &i_dummy
,&i_dummy
,&i_dummy
) == 7 ||
473 sscanf( s
, "%d:%d:%d.%d --> %d:%d:%d",
474 &i_dummy
,&i_dummy
,&i_dummy
,&i_dummy
,
475 &i_dummy
,&i_dummy
,&i_dummy
) == 7 ||
476 sscanf( s
, "%d:%d:%d --> %d:%d:%d",
477 &i_dummy
,&i_dummy
,&i_dummy
,
478 &i_dummy
,&i_dummy
,&i_dummy
) == 6 )
480 p_sys
->props
.i_type
= SUB_TYPE_SUBRIP
;
483 else if( !strncasecmp( s
, "!: This is a Sub Station Alpha v1", 33 ) )
485 p_sys
->props
.i_type
= SUB_TYPE_SSA1
;
488 else if( !strncasecmp( s
, "ScriptType: v4.00+", 18 ) )
490 p_sys
->props
.i_type
= SUB_TYPE_ASS
;
493 else if( !strncasecmp( s
, "ScriptType: v4.00", 17 ) )
495 p_sys
->props
.i_type
= SUB_TYPE_SSA2_4
;
498 else if( !strncasecmp( s
, "Dialogue: Marked", 16 ) )
500 p_sys
->props
.i_type
= SUB_TYPE_SSA2_4
;
503 else if( !strncasecmp( s
, "Dialogue:", 9 ) )
505 p_sys
->props
.i_type
= SUB_TYPE_ASS
;
508 else if( strcasestr( s
, "[INFORMATION]" ) )
510 p_sys
->props
.i_type
= SUB_TYPE_SUBVIEWER
; /* I hope this will work */
513 else if( sscanf( s
, "%d:%d:%d.%d %d:%d:%d",
514 &i_dummy
, &i_dummy
, &i_dummy
, &i_dummy
,
515 &i_dummy
, &i_dummy
, &i_dummy
) == 7 ||
516 sscanf( s
, "@%d @%d", &i_dummy
, &i_dummy
) == 2)
518 p_sys
->props
.i_type
= SUB_TYPE_JACOSUB
;
521 else if( sscanf( s
, "%d:%d:%d.%d,%d:%d:%d.%d",
522 &i_dummy
, &i_dummy
, &i_dummy
, &i_dummy
,
523 &i_dummy
, &i_dummy
, &i_dummy
, &i_dummy
) == 8 )
525 p_sys
->props
.i_type
= SUB_TYPE_SBV
;
528 else if( sscanf( s
, "%d:%d:%d:", &i_dummy
, &i_dummy
, &i_dummy
) == 3 ||
529 sscanf( s
, "%d:%d:%d ", &i_dummy
, &i_dummy
, &i_dummy
) == 3 )
531 p_sys
->props
.i_type
= SUB_TYPE_VPLAYER
;
534 else if( sscanf( s
, "{T %d:%d:%d:%d", &i_dummy
, &i_dummy
,
535 &i_dummy
, &i_dummy
) == 4 )
537 p_sys
->props
.i_type
= SUB_TYPE_DVDSUBTITLE
;
540 else if( sscanf( s
, "[%d:%d:%d]%c",
541 &i_dummy
, &i_dummy
, &i_dummy
, &p_dummy
) == 4 )
543 p_sys
->props
.i_type
= SUB_TYPE_DKS
;
546 else if( strstr( s
, "*** START SCRIPT" ) )
548 p_sys
->props
.i_type
= SUB_TYPE_SUBVIEW1
;
551 else if( sscanf( s
, "[%d][%d]", &i_dummy
, &i_dummy
) == 2 ||
552 sscanf( s
, "[%d][]", &i_dummy
) == 1)
554 p_sys
->props
.i_type
= SUB_TYPE_MPL2
;
557 else if( sscanf (s
, "FORMAT=%d", &i_dummy
) == 1 ||
558 ( sscanf (s
, "FORMAT=TIM%c", &p_dummy
) == 1
561 p_sys
->props
.i_type
= SUB_TYPE_MPSUB
;
564 else if( sscanf( s
, "-->> %d", &i_dummy
) == 1 )
566 p_sys
->props
.i_type
= SUB_TYPE_AQT
;
569 else if( sscanf( s
, "%d,%d,", &i_dummy
, &i_dummy
) == 2 )
571 p_sys
->props
.i_type
= SUB_TYPE_PJS
;
574 else if( sscanf( s
, "{%d:%d:%d}",
575 &i_dummy
, &i_dummy
, &i_dummy
) == 3 )
577 p_sys
->props
.i_type
= SUB_TYPE_PSB
;
580 else if( strcasestr( s
, "<time" ) )
582 p_sys
->props
.i_type
= SUB_TYPE_RT
;
585 else if( !strncasecmp( s
, "WEBVTT",6 ) )
590 else if( !strncasecmp( s
, "Scenarist_SCC V1.0", 18 ) )
592 p_sys
->props
.i_type
= SUB_TYPE_SCC
;
593 p_sys
->pf_convert
= ToEIA608Block
;
604 vlc_stream_Delete( p_probestream
);
606 /* Quit on unknown subtitles */
607 if( p_sys
->props
.i_type
== SUB_TYPE_UNKNOWN
)
610 /* Ensure it will work with non seekable streams */
611 assert( i_start_pos
== vlc_stream_Tell( p_demux
->s
) );
613 msg_Warn( p_demux
, "failed to recognize subtitle type" );
618 for( int i
= 0; ; i
++ )
620 if( sub_read_subtitle_function
[i
].i_type
== p_sys
->props
.i_type
)
622 msg_Dbg( p_demux
, "detected %s format",
623 sub_read_subtitle_function
[i
].psz_name
);
624 pf_read
= sub_read_subtitle_function
[i
].pf_read
;
629 msg_Dbg( p_demux
, "loading all subtitles..." );
631 if( e_bom
== UTF8BOM
&& /* skip BOM */
632 vlc_stream_Read( p_demux
->s
, NULL
, 3 ) != 3 )
638 /* Load the whole file */
640 TextLoad( &txtlines
, p_demux
->s
);
643 for( size_t i_max
= 0; i_max
< SIZE_MAX
- 500 * sizeof(subtitle_t
); )
645 if( p_sys
->subtitles
.i_count
>= i_max
)
648 subtitle_t
*p_realloc
= realloc( p_sys
->subtitles
.p_array
, sizeof(subtitle_t
) * i_max
);
649 if( p_realloc
== NULL
)
651 TextUnload( &txtlines
);
655 p_sys
->subtitles
.p_array
= p_realloc
;
658 if( pf_read( VLC_OBJECT(p_demux
), &p_sys
->props
, &txtlines
,
659 &p_sys
->subtitles
.p_array
[p_sys
->subtitles
.i_count
],
660 p_sys
->subtitles
.i_count
) )
663 p_sys
->subtitles
.i_count
++;
666 TextUnload( &txtlines
);
668 msg_Dbg(p_demux
, "loaded %zu subtitles", p_sys
->subtitles
.i_count
);
670 /* *** add subtitle ES *** */
671 if( p_sys
->props
.i_type
== SUB_TYPE_SSA1
||
672 p_sys
->props
.i_type
== SUB_TYPE_SSA2_4
||
673 p_sys
->props
.i_type
== SUB_TYPE_ASS
)
676 es_format_Init( &fmt
, SPU_ES
, VLC_CODEC_SSA
);
678 else if( p_sys
->props
.i_type
== SUB_TYPE_SCC
)
680 es_format_Init( &fmt
, SPU_ES
, VLC_CODEC_CEA608
);
681 fmt
.subs
.cc
.i_reorder_depth
= -1;
684 es_format_Init( &fmt
, SPU_ES
, VLC_CODEC_SUBT
);
686 p_sys
->subtitles
.i_current
= 0;
688 if( p_sys
->subtitles
.i_count
> 0 )
689 p_sys
->i_length
= p_sys
->subtitles
.p_array
[p_sys
->subtitles
.i_count
-1].i_stop
;
691 /* Stupid language detection in the filename */
692 char * psz_language
= get_language_from_filename( p_demux
->psz_filepath
);
696 fmt
.psz_language
= psz_language
;
697 msg_Dbg( p_demux
, "detected language %s of subtitle: %s", psz_language
,
698 p_demux
->psz_location
);
701 char *psz_description
= var_InheritString( p_demux
, "sub-description" );
702 if( psz_description
&& *psz_description
)
703 fmt
.psz_description
= psz_description
;
705 free( psz_description
);
706 if( p_sys
->props
.psz_header
!= NULL
&&
707 (fmt
.p_extra
= strdup( p_sys
->props
.psz_header
)) )
709 fmt
.i_extra
= strlen( p_sys
->props
.psz_header
) + 1;
712 p_sys
->es
= es_out_Add( p_demux
->out
, &fmt
);
713 es_format_Clean( &fmt
);
714 if( p_sys
->es
== NULL
)
723 /*****************************************************************************
724 * Close: Close subtitle demux
725 *****************************************************************************/
726 static void Close( vlc_object_t
*p_this
)
728 demux_t
*p_demux
= (demux_t
*)p_this
;
729 demux_sys_t
*p_sys
= p_demux
->p_sys
;
731 for( size_t i
= 0; i
< p_sys
->subtitles
.i_count
; i
++ )
732 free( p_sys
->subtitles
.p_array
[i
].psz_text
);
733 free( p_sys
->subtitles
.p_array
);
734 free( p_sys
->props
.psz_header
);
740 ResetCurrentIndex( demux_t
*p_demux
)
742 demux_sys_t
*p_sys
= p_demux
->p_sys
;
743 for( size_t i
= 0; i
< p_sys
->subtitles
.i_count
; i
++ )
745 if( p_sys
->subtitles
.p_array
[i
].i_start
* p_sys
->f_rate
>
746 p_sys
->i_next_demux_date
&& i
> 0 )
748 p_sys
->subtitles
.i_current
= i
;
752 /*****************************************************************************
754 *****************************************************************************/
755 static int Control( demux_t
*p_demux
, int i_query
, va_list args
)
757 demux_sys_t
*p_sys
= p_demux
->p_sys
;
763 *va_arg( args
, bool * ) = true;
766 case DEMUX_GET_LENGTH
:
767 *va_arg( args
, vlc_tick_t
* ) = p_sys
->i_length
;
771 *va_arg( args
, vlc_tick_t
* ) = p_sys
->i_next_demux_date
;
776 p_sys
->b_first_time
= true;
777 p_sys
->i_next_demux_date
= va_arg( args
, vlc_tick_t
);
778 ResetCurrentIndex( p_demux
);
782 case DEMUX_GET_POSITION
:
783 pf
= va_arg( args
, double * );
784 if( p_sys
->subtitles
.i_current
>= p_sys
->subtitles
.i_count
)
788 else if( p_sys
->subtitles
.i_count
> 0 && p_sys
->i_length
)
790 *pf
= p_sys
->i_next_demux_date
;
791 *pf
/= p_sys
->i_length
;
799 case DEMUX_SET_POSITION
:
800 f
= va_arg( args
, double );
801 if( p_sys
->subtitles
.i_count
&& p_sys
->i_length
)
803 vlc_tick_t i64
= VLC_TICK_0
+ f
* p_sys
->i_length
;
804 return demux_Control( p_demux
, DEMUX_SET_TIME
, i64
);
808 case DEMUX_CAN_CONTROL_RATE
:
809 *va_arg( args
, bool * ) = true;
812 p_sys
->f_rate
= (double)INPUT_RATE_DEFAULT
/ *va_arg( args
, int * );
813 ResetCurrentIndex( p_demux
);
815 case DEMUX_SET_NEXT_DEMUX_TIME
:
816 p_sys
->b_slave
= true;
817 p_sys
->i_next_demux_date
= va_arg( args
, vlc_tick_t
) - VLC_TICK_0
;
820 case DEMUX_CAN_PAUSE
:
821 case DEMUX_SET_PAUSE_STATE
:
822 case DEMUX_CAN_CONTROL_PACE
:
823 return demux_vaControlHelper( p_demux
->s
, 0, -1, 0, 1, i_query
, args
);
825 case DEMUX_GET_PTS_DELAY
:
828 case DEMUX_GET_ATTACHMENTS
:
829 case DEMUX_GET_TITLE_INFO
:
830 case DEMUX_HAS_UNSUPPORTED_META
:
831 case DEMUX_CAN_RECORD
:
839 /*****************************************************************************
840 * Demux: Send subtitle to decoder
841 *****************************************************************************/
842 static int Demux( demux_t
*p_demux
)
844 demux_sys_t
*p_sys
= p_demux
->p_sys
;
846 vlc_tick_t i_barrier
= p_sys
->i_next_demux_date
;
848 while( p_sys
->subtitles
.i_current
< p_sys
->subtitles
.i_count
&&
849 ( p_sys
->subtitles
.p_array
[p_sys
->subtitles
.i_current
].i_start
*
850 p_sys
->f_rate
) <= i_barrier
)
852 const subtitle_t
*p_subtitle
= &p_sys
->subtitles
.p_array
[p_sys
->subtitles
.i_current
];
854 if ( !p_sys
->b_slave
&& p_sys
->b_first_time
)
856 es_out_SetPCR( p_demux
->out
, VLC_TICK_0
+ i_barrier
);
857 p_sys
->b_first_time
= false;
860 if( p_subtitle
->i_start
>= 0 )
862 block_t
*p_block
= p_sys
->pf_convert( p_subtitle
);
866 p_block
->i_pts
= VLC_TICK_0
+ p_subtitle
->i_start
* p_sys
->f_rate
;
867 if( p_subtitle
->i_stop
>= 0 && p_subtitle
->i_stop
>= p_subtitle
->i_start
)
868 p_block
->i_length
= (p_subtitle
->i_stop
- p_subtitle
->i_start
) * p_sys
->f_rate
;
870 es_out_Send( p_demux
->out
, p_sys
->es
, p_block
);
874 p_sys
->subtitles
.i_current
++;
877 if ( !p_sys
->b_slave
)
879 es_out_SetPCR( p_demux
->out
, VLC_TICK_0
+ i_barrier
);
880 p_sys
->i_next_demux_date
+= VLC_TICK_FROM_MS(125);
883 if( p_sys
->subtitles
.i_current
>= p_sys
->subtitles
.i_count
)
884 return VLC_DEMUXER_EOF
;
886 return VLC_DEMUXER_SUCCESS
;
890 static int subtitle_cmp( const void *first
, const void *second
)
892 vlc_tick_t result
= ((subtitle_t
*)(first
))->i_start
- ((subtitle_t
*)(second
))->i_start
;
893 /* Return -1, 0 ,1, and not directly subtraction
894 * as result can be > INT_MAX */
895 return result
== 0 ? 0 : result
> 0 ? 1 : -1;
897 /*****************************************************************************
898 * Fix: fix time stamp and order of subtitle
899 *****************************************************************************/
900 static void Fix( demux_t
*p_demux
)
902 demux_sys_t
*p_sys
= p_demux
->p_sys
;
904 /* *** fix order (to be sure...) *** */
905 qsort( p_sys
->subtitles
.p_array
, p_sys
->subtitles
.i_count
, sizeof( p_sys
->subtitles
.p_array
[0] ), subtitle_cmp
);
908 static int TextLoad( text_t
*txt
, stream_t
*s
)
914 txt
->i_line_count
= 0;
916 txt
->line
= calloc( i_line_max
, sizeof( char * ) );
920 /* load the complete file */
923 char *psz
= vlc_stream_ReadLine( s
);
928 txt
->line
[txt
->i_line_count
] = psz
;
929 if( txt
->i_line_count
+ 1 >= i_line_max
)
932 char **p_realloc
= realloc( txt
->line
, i_line_max
* sizeof( char * ) );
933 if( p_realloc
== NULL
)
935 txt
->line
= p_realloc
;
940 if( txt
->i_line_count
== 0 )
948 static void TextUnload( text_t
*txt
)
950 if( txt
->i_line_count
)
952 for( size_t i
= 0; i
< txt
->i_line_count
; i
++ )
953 free( txt
->line
[i
] );
957 txt
->i_line_count
= 0;
960 static char *TextGetLine( text_t
*txt
)
962 if( txt
->i_line
>= txt
->i_line_count
)
965 return txt
->line
[txt
->i_line
++];
967 static void TextPreviousLine( text_t
*txt
)
969 if( txt
->i_line
> 0 )
973 /*****************************************************************************
974 * Specific Subtitle function
975 *****************************************************************************/
978 * {n1}{n2}Line1|Line2|Line3....
979 * where n1 and n2 are the video frame number (n2 can be empty)
981 static int ParseMicroDvd( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
982 text_t
*txt
, subtitle_t
*p_subtitle
,
993 const char *s
= TextGetLine( txt
);
997 psz_text
= malloc( strlen(s
) + 1 );
1003 if( sscanf( s
, "{%d}{}%[^\r\n]", &i_start
, psz_text
) == 2 ||
1004 sscanf( s
, "{%d}{%d}%[^\r\n]", &i_start
, &i_stop
, psz_text
) == 3)
1006 if( i_start
!= 1 || i_stop
!= 1 )
1009 /* We found a possible setting of the framerate "{1}{1}23.976" */
1010 /* Check if it's usable, and if the sub-original-fps is not set */
1011 float f_fps
= us_strtof( psz_text
, NULL
);
1012 if( f_fps
> 0.f
&& var_GetFloat( p_obj
, "sub-original-fps" ) <= 0.f
)
1013 p_props
->i_microsecperframe
= llroundf((float)CLOCK_FREQ
/ f_fps
);
1018 /* replace | by \n */
1019 for( i
= 0; psz_text
[i
] != '\0'; i
++ )
1021 if( psz_text
[i
] == '|' )
1026 p_subtitle
->i_start
= i_start
* p_props
->i_microsecperframe
;
1027 p_subtitle
->i_stop
= i_stop
>= 0 ? (i_stop
* p_props
->i_microsecperframe
) : -1;
1028 p_subtitle
->psz_text
= psz_text
;
1032 /* ParseSubRipSubViewer
1035 * h1:m1:s1,d1 --> h2:m2:s2,d2
1040 * Format SubViewer v1/v2
1041 * h1:m1:s1.d1,h2:m2:s2.d2
1046 * We ignore line number for SubRip
1048 static int ParseSubRipSubViewer( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1049 text_t
*txt
, subtitle_t
*p_subtitle
,
1050 int (* pf_parse_timing
)(subtitle_t
*, const char *),
1054 VLC_UNUSED(p_props
);
1059 const char *s
= TextGetLine( txt
);
1062 return VLC_EGENERIC
;
1064 if( pf_parse_timing( p_subtitle
, s
) == VLC_SUCCESS
&&
1065 p_subtitle
->i_start
< p_subtitle
->i_stop
)
1071 /* Now read text until an empty line */
1072 psz_text
= strdup("");
1078 const char *s
= TextGetLine( txt
);
1082 i_len
= s
? strlen( s
) : 0;
1085 p_subtitle
->psz_text
= psz_text
;
1089 i_old
= strlen( psz_text
);
1090 psz_text
= realloc_or_free( psz_text
, i_old
+ i_len
+ 1 + 1 );
1095 strcat( psz_text
, s
);
1096 strcat( psz_text
, "\n" );
1098 /* replace [br] by \n */
1103 while( ( p
= strstr( psz_text
, "[br]" ) ) )
1106 memmove( p
, &p
[3], strlen(&p
[3])+1 );
1112 /* subtitle_ParseSubRipTimingValue
1113 * Parses SubRip timing value.
1115 static int subtitle_ParseSubRipTimingValue(vlc_tick_t
*timing_value
,
1118 int h1
, m1
, s1
, d1
= 0;
1120 if ( sscanf( s
, "%d:%d:%d,%d",
1121 &h1
, &m1
, &s1
, &d1
) == 4 ||
1122 sscanf( s
, "%d:%d:%d.%d",
1123 &h1
, &m1
, &s1
, &d1
) == 4 ||
1124 sscanf( s
, "%d:%d:%d",
1125 &h1
, &m1
, &s1
) == 3 )
1127 (*timing_value
) = vlc_tick_from_sec( h1
* 3600 + m1
* 60 + s1
) +
1128 VLC_TICK_FROM_MS( d1
);
1133 return VLC_EGENERIC
;
1136 /* subtitle_ParseSubRipTiming
1137 * Parses SubRip timing.
1139 static int subtitle_ParseSubRipTiming( subtitle_t
*p_subtitle
,
1142 int i_result
= VLC_EGENERIC
;
1143 char *psz_start
, *psz_stop
;
1144 psz_start
= malloc( strlen(s
) + 1 );
1145 psz_stop
= malloc( strlen(s
) + 1 );
1147 if( sscanf( s
, "%s --> %s", psz_start
, psz_stop
) == 2 &&
1148 subtitle_ParseSubRipTimingValue( &p_subtitle
->i_start
, psz_start
) == VLC_SUCCESS
&&
1149 subtitle_ParseSubRipTimingValue( &p_subtitle
->i_stop
, psz_stop
) == VLC_SUCCESS
)
1151 i_result
= VLC_SUCCESS
;
1161 static int ParseSubRip( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1162 text_t
*txt
, subtitle_t
*p_subtitle
,
1165 VLC_UNUSED( i_idx
);
1166 return ParseSubRipSubViewer( p_obj
, p_props
, txt
, p_subtitle
,
1167 &subtitle_ParseSubRipTiming
,
1171 /* subtitle_ParseSubViewerTiming
1172 * Parses SubViewer timing.
1174 static int subtitle_ParseSubViewerTiming( subtitle_t
*p_subtitle
,
1177 int h1
, m1
, s1
, d1
, h2
, m2
, s2
, d2
;
1179 if( sscanf( s
, "%d:%d:%d.%d,%d:%d:%d.%d",
1180 &h1
, &m1
, &s1
, &d1
, &h2
, &m2
, &s2
, &d2
) == 8 )
1182 p_subtitle
->i_start
= vlc_tick_from_sec( h1
* 3600 + m1
* 60 + s1
) +
1183 VLC_TICK_FROM_MS( d1
);
1185 p_subtitle
->i_stop
= vlc_tick_from_sec( h2
* 3600 + m2
* 60 + s2
) +
1186 VLC_TICK_FROM_MS( d2
);
1189 return VLC_EGENERIC
;
1194 static int ParseSubViewer( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1195 text_t
*txt
, subtitle_t
*p_subtitle
,
1198 VLC_UNUSED( i_idx
);
1200 return ParseSubRipSubViewer( p_obj
, p_props
, txt
, p_subtitle
,
1201 &subtitle_ParseSubViewerTiming
,
1207 static int ParseSSA( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1208 text_t
*txt
, subtitle_t
*p_subtitle
,
1212 size_t header_len
= 0;
1216 const char *s
= TextGetLine( txt
);
1217 int h1
, m1
, s1
, c1
, h2
, m2
, s2
, c2
;
1218 char *psz_text
, *psz_temp
;
1222 return VLC_EGENERIC
;
1224 /* We expect (SSA2-4):
1225 * Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
1226 * Dialogue: Marked=0,0:02:40.65,0:02:41.79,Wolf main,Cher,0000,0000,0000,,Et les enregistrements de ses ondes delta ?
1228 * SSA-1 is similar but only has 8 commas up untill the subtitle text. Probably the Effect field is no present, but not 100 % sure.
1232 * Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
1233 * Dialogue: Layer#,0:02:40.65,0:02:41.79,Wolf main,Cher,0000,0000,0000,,Et les enregistrements de ses ondes delta ?
1236 /* The output text is - at least, not removing numbers - 18 chars shorter than the input text. */
1237 psz_text
= malloc( strlen(s
) );
1242 "Dialogue: %15[^,],%d:%d:%d.%d,%d:%d:%d.%d,%[^\r\n]",
1248 /* The dec expects: ReadOrder, Layer, Style, Name, MarginL, MarginR, MarginV, Effect, Text */
1249 /* (Layer comes from ASS specs ... it's empty for SSA.) */
1250 if( p_props
->i_type
== SUB_TYPE_SSA1
)
1252 /* SSA1 has only 8 commas before the text starts, not 9 */
1253 memmove( &psz_text
[1], psz_text
, strlen(psz_text
)+1 );
1258 int i_layer
= ( p_props
->i_type
== SUB_TYPE_ASS
) ? atoi( temp
) : 0;
1260 /* ReadOrder, Layer, %s(rest of fields) */
1261 if( asprintf( &psz_temp
, "%zu,%d,%s", i_idx
, i_layer
, psz_text
) == -1 )
1268 psz_text
= psz_temp
;
1271 p_subtitle
->i_start
= vlc_tick_from_sec( h1
* 3600 + m1
* 60 + s1
) +
1272 VLC_TICK_FROM_MS( c1
* 10 );
1273 p_subtitle
->i_stop
= vlc_tick_from_sec( h2
* 3600 + m2
* 60 + s2
) +
1274 VLC_TICK_FROM_MS( c2
* 10 );
1275 p_subtitle
->psz_text
= psz_text
;
1280 /* All the other stuff we add to the header field */
1281 if( header_len
== 0 && p_props
->psz_header
)
1282 header_len
= strlen( p_props
->psz_header
);
1284 size_t s_len
= strlen( s
);
1285 p_props
->psz_header
= realloc_or_free( p_props
->psz_header
, header_len
+ s_len
+ 2 );
1286 if( !p_props
->psz_header
)
1288 snprintf( p_props
->psz_header
+ header_len
, s_len
+ 2, "%s\n", s
);
1289 header_len
+= s_len
+ 1;
1295 * h:m:s:Line1|Line2|Line3....
1297 * h:m:s Line1|Line2|Line3....
1299 static int ParseVplayer( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1300 text_t
*txt
, subtitle_t
*p_subtitle
,
1304 VLC_UNUSED(p_props
);
1305 VLC_UNUSED( i_idx
);
1310 const char *s
= TextGetLine( txt
);
1314 return VLC_EGENERIC
;
1316 psz_text
= malloc( strlen( s
) + 1 );
1320 if( sscanf( s
, "%d:%d:%d%*c%[^\r\n]",
1321 &h1
, &m1
, &s1
, psz_text
) == 4 )
1323 p_subtitle
->i_start
= vlc_tick_from_sec( h1
* 3600 + m1
* 60 + s1
);
1324 p_subtitle
->i_stop
= -1;
1330 /* replace | by \n */
1331 for( size_t i
= 0; psz_text
[i
] != '\0'; i
++ )
1333 if( psz_text
[i
] == '|' )
1336 p_subtitle
->psz_text
= psz_text
;
1342 static const char *ParseSamiSearch( text_t
*txt
,
1343 const char *psz_start
, const char *psz_str
)
1345 if( psz_start
&& strcasestr( psz_start
, psz_str
) )
1347 const char *s
= strcasestr( psz_start
, psz_str
);
1348 return &s
[strlen( psz_str
)];
1353 const char *p
= TextGetLine( txt
);
1357 const char *s
= strcasestr( p
, psz_str
);
1359 return &s
[strlen( psz_str
)];
1362 static int ParseSami( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1363 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
1366 VLC_UNUSED(p_props
);
1367 VLC_UNUSED( i_idx
);
1371 unsigned int i_text
;
1372 char text
[8192]; /* Arbitrary but should be long enough */
1374 /* search "Start=" */
1375 s
= ParseSamiSearch( txt
, p_props
->sami
.psz_start
, "Start=" );
1376 p_props
->sami
.psz_start
= NULL
;
1378 return VLC_EGENERIC
;
1380 /* get start value */
1382 i_start
= strtol( s
, &psz_end
, 0 );
1386 if( !( s
= ParseSamiSearch( txt
, s
, "<P" ) ) )
1387 return VLC_EGENERIC
;
1390 if( !( s
= ParseSamiSearch( txt
, s
, ">" ) ) )
1391 return VLC_EGENERIC
;
1395 /* now get all txt until a "Start=" line */
1399 /* Search non empty line */
1400 while( s
&& *s
== '\0' )
1401 s
= TextGetLine( txt
);
1407 if( !strncasecmp( s
, "<br", 3 ) )
1411 else if( strcasestr( s
, "Start=" ) )
1413 p_props
->sami
.psz_start
= s
;
1416 s
= ParseSamiSearch( txt
, s
, ">" );
1418 else if( !strncmp( s
, " ", 6 ) )
1423 else if( *s
== '\t' )
1433 if( c
!= '\0' && i_text
+1 < sizeof(text
) )
1436 text
[i_text
] = '\0';
1440 p_subtitle
->i_start
= VLC_TICK_FROM_MS(i_start
);
1441 p_subtitle
->i_stop
= -1;
1442 p_subtitle
->psz_text
= strdup( text
);
1454 * TODO it can have a header
1461 * LANG support would be cool
1462 * CODEPAGE is probably mandatory FIXME
1464 static int ParseDVDSubtitle(vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1465 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
1468 VLC_UNUSED(p_props
);
1469 VLC_UNUSED( i_idx
);
1474 const char *s
= TextGetLine( txt
);
1478 return VLC_EGENERIC
;
1482 &h1
, &m1
, &s1
, &c1
) == 4 )
1484 p_subtitle
->i_start
= vlc_tick_from_sec( h1
* 3600 + m1
* 60 + s1
) +
1485 VLC_TICK_FROM_MS( c1
* 10 );
1486 p_subtitle
->i_stop
= -1;
1491 /* Now read text until a line containing "}" */
1492 psz_text
= strdup("");
1497 const char *s
= TextGetLine( txt
);
1504 return VLC_EGENERIC
;
1507 i_len
= strlen( s
);
1508 if( i_len
== 1 && s
[0] == '}')
1510 p_subtitle
->psz_text
= psz_text
;
1514 i_old
= strlen( psz_text
);
1515 psz_text
= realloc_or_free( psz_text
, i_old
+ i_len
+ 1 + 1 );
1518 strcat( psz_text
, s
);
1519 strcat( psz_text
, "\n" );
1525 * [n1][n2]Line1|Line2|Line3...
1526 * where n1 and n2 are the video frame number (n2 can be empty)
1528 static int ParseMPL2(vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1529 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
1532 VLC_UNUSED(p_props
);
1533 VLC_UNUSED( i_idx
);
1539 const char *s
= TextGetLine( txt
);
1544 return VLC_EGENERIC
;
1546 psz_text
= malloc( strlen(s
) + 1 );
1552 if( sscanf( s
, "[%d][] %[^\r\n]", &i_start
, psz_text
) == 2 ||
1553 sscanf( s
, "[%d][%d] %[^\r\n]", &i_start
, &i_stop
, psz_text
) == 3)
1555 p_subtitle
->i_start
= VLC_TICK_FROM_MS(i_start
* 100);
1556 p_subtitle
->i_stop
= i_stop
>= 0 ? VLC_TICK_FROM_MS(i_stop
* 100) : -1;
1562 for( i
= 0; psz_text
[i
] != '\0'; )
1564 /* replace | by \n */
1565 if( psz_text
[i
] == '|' )
1569 if( psz_text
[i
] == '/' && ( i
== 0 || psz_text
[i
-1] == '\n' ) )
1570 memmove( &psz_text
[i
], &psz_text
[i
+1], strlen(&psz_text
[i
+1])+1 );
1574 p_subtitle
->psz_text
= psz_text
;
1578 static int ParseAQT(vlc_object_t
*p_obj
, subs_properties_t
*p_props
, text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
1581 VLC_UNUSED(p_props
);
1582 VLC_UNUSED( i_idx
);
1584 char *psz_text
= strdup( "" );
1586 int i_firstline
= 1;
1592 const char *s
= TextGetLine( txt
);
1597 return VLC_EGENERIC
;
1601 if( sscanf (s
, "-->> %d", &t
) == 1)
1603 p_subtitle
->i_start
= (int64_t)t
; /* * FPS*/
1604 p_subtitle
->i_stop
= -1;
1606 /* Starting of a subtitle */
1611 /* We have been too far: end of the subtitle, begin of next */
1614 TextPreviousLine( txt
);
1621 i_old
= strlen( psz_text
) + 1;
1622 psz_text
= realloc_or_free( psz_text
, i_old
+ strlen( s
) + 1 );
1625 strcat( psz_text
, s
);
1626 strcat( psz_text
, "\n" );
1627 if( txt
->i_line
== txt
->i_line_count
)
1631 p_subtitle
->psz_text
= psz_text
;
1635 static int ParsePJS(vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1636 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
1639 VLC_UNUSED(p_props
);
1640 VLC_UNUSED( i_idx
);
1647 const char *s
= TextGetLine( txt
);
1651 return VLC_EGENERIC
;
1653 psz_text
= malloc( strlen(s
) + 1 );
1658 if( sscanf (s
, "%d,%d,\"%[^\n\r]", &t1
, &t2
, psz_text
) == 3 )
1660 /* 1/10th of second ? Frame based ? FIXME */
1661 p_subtitle
->i_start
= 10 * t1
;
1662 p_subtitle
->i_stop
= 10 * t2
;
1663 /* Remove latest " */
1664 psz_text
[ strlen(psz_text
) - 1 ] = '\0';
1671 /* replace | by \n */
1672 for( i
= 0; psz_text
[i
] != '\0'; i
++ )
1674 if( psz_text
[i
] == '|' )
1678 p_subtitle
->psz_text
= psz_text
;
1679 msg_Dbg( p_obj
, "%s", psz_text
);
1683 static int ParseMPSub( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1684 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
1686 VLC_UNUSED( i_idx
);
1688 char *psz_text
= strdup( "" );
1690 if( !p_props
->mpsub
.b_inited
)
1692 p_props
->mpsub
.f_total
= 0.0;
1693 p_props
->mpsub
.i_factor
= 0;
1695 p_props
->mpsub
.b_inited
= true;
1703 const char *s
= TextGetLine( txt
);
1707 return VLC_EGENERIC
;
1710 if( strstr( s
, "FORMAT" ) )
1712 if( sscanf (s
, "FORMAT=TIM%c", &p_dummy
) == 1 && p_dummy
== 'E')
1714 p_props
->mpsub
.i_factor
= 100;
1718 psz_temp
= malloc( strlen(s
) );
1725 if( sscanf( s
, "FORMAT=%[^\r\n]", psz_temp
) )
1727 float f_fps
= us_strtof( psz_temp
, NULL
);
1729 if( f_fps
> 0.f
&& var_GetFloat( p_obj
, "sub-original-fps" ) <= 0.f
)
1730 var_SetFloat( p_obj
, "sub-original-fps", f_fps
);
1732 p_props
->mpsub
.i_factor
= 1;
1740 float f1
= us_strtof( s
, &psz_temp
);
1743 float f2
= us_strtof( psz_temp
, NULL
);
1744 p_props
->mpsub
.f_total
+= f1
* p_props
->mpsub
.i_factor
;
1745 p_subtitle
->i_start
= llroundf(10000.f
* p_props
->mpsub
.f_total
);
1746 p_props
->mpsub
.f_total
+= f2
* p_props
->mpsub
.i_factor
;
1747 p_subtitle
->i_stop
= llroundf(10000.f
* p_props
->mpsub
.f_total
);
1754 const char *s
= TextGetLine( txt
);
1759 return VLC_EGENERIC
;
1762 size_t i_len
= strlen( s
);
1766 size_t i_old
= strlen( psz_text
);
1768 psz_text
= realloc_or_free( psz_text
, i_old
+ i_len
+ 1 + 1 );
1772 strcat( psz_text
, s
);
1773 strcat( psz_text
, "\n" );
1776 p_subtitle
->psz_text
= psz_text
;
1780 static int ParseJSS( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
1781 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
1783 VLC_UNUSED( i_idx
);
1784 char *psz_text
, *psz_orig
;
1785 char *psz_text2
, *psz_orig2
;
1787 if( !p_props
->jss
.b_inited
)
1789 p_props
->jss
.i_comment
= 0;
1790 p_props
->jss
.i_time_resolution
= 30;
1791 p_props
->jss
.i_time_shift
= 0;
1793 p_props
->jss
.b_inited
= true;
1796 /* Parse the main lines */
1799 const char *s
= TextGetLine( txt
);
1801 return VLC_EGENERIC
;
1803 size_t line_length
= strlen( s
);
1804 psz_orig
= malloc( line_length
+ 1 );
1807 psz_text
= psz_orig
;
1809 /* Complete time lines */
1810 int h1
, h2
, m1
, m2
, s1
, s2
, f1
, f2
;
1811 if( sscanf( s
, "%d:%d:%d.%d %d:%d:%d.%d %[^\n\r]",
1812 &h1
, &m1
, &s1
, &f1
, &h2
, &m2
, &s2
, &f2
, psz_text
) == 9 )
1814 p_subtitle
->i_start
= vlc_tick_from_sec( ( h1
*3600 + m1
* 60 + s1
) +
1815 (int64_t)( ( f1
+ p_props
->jss
.i_time_shift
) / p_props
->jss
.i_time_resolution
) );
1816 p_subtitle
->i_stop
= vlc_tick_from_sec( ( h2
*3600 + m2
* 60 + s2
) +
1817 (int64_t)( ( f2
+ p_props
->jss
.i_time_shift
) / p_props
->jss
.i_time_resolution
) );
1820 /* Short time lines */
1821 else if( sscanf( s
, "@%d @%d %[^\n\r]", &f1
, &f2
, psz_text
) == 3 )
1823 p_subtitle
->i_start
=
1824 vlc_tick_from_sec( (f1
+ p_props
->jss
.i_time_shift
) / p_props
->jss
.i_time_resolution
);
1825 p_subtitle
->i_stop
=
1826 vlc_tick_from_sec( (f2
+ p_props
->jss
.i_time_shift
) / p_props
->jss
.i_time_resolution
);
1829 /* General Directive lines */
1830 /* Only TIME and SHIFT are supported so far */
1831 else if( s
[0] == '#' )
1833 int h
= 0, m
=0, sec
= 1, f
= 1;
1837 strcpy( psz_text
, s
);
1839 switch( toupper( (unsigned char)psz_text
[1] ) )
1842 shift
= isalpha( (unsigned char)psz_text
[2] ) ? 6 : 2 ;
1843 if ( shift
> line_length
)
1846 if( sscanf( &psz_text
[shift
], "%d", &h
) )
1848 /* Negative shifting */
1855 if( sscanf( &psz_text
[shift
], "%*d:%d", &m
) )
1857 if( sscanf( &psz_text
[shift
], "%*d:%*d:%d", &sec
) )
1859 sscanf( &psz_text
[shift
], "%*d:%*d:%*d.%d", &f
);
1864 sscanf( &psz_text
[shift
], "%d:%d.%d",
1872 sscanf( &psz_text
[shift
], "%d.%d", &sec
, &f
);
1875 p_props
->jss
.i_time_shift
= ( ( h
* 3600 + m
* 60 + sec
)
1876 * p_props
->jss
.i_time_resolution
+ f
) * inv
;
1881 shift
= isalpha( (unsigned char)psz_text
[2] ) ? 8 : 2 ;
1882 if ( shift
> line_length
)
1885 sscanf( &psz_text
[shift
], "%d", &p_props
->jss
.i_time_resolution
);
1886 if( !p_props
->jss
.i_time_resolution
)
1887 p_props
->jss
.i_time_resolution
= 30;
1894 /* Unkown type line, probably a comment */
1901 while( psz_text
[ strlen( psz_text
) - 1 ] == '\\' )
1903 const char *s2
= TextGetLine( txt
);
1908 return VLC_EGENERIC
;
1911 size_t i_len
= strlen( s2
);
1915 size_t i_old
= strlen( psz_text
);
1917 psz_text
= realloc_or_free( psz_text
, i_old
+ i_len
+ 1 );
1921 psz_orig
= psz_text
;
1922 strcat( psz_text
, s2
);
1925 /* Skip the blanks */
1926 while( *psz_text
== ' ' || *psz_text
== '\t' ) psz_text
++;
1928 /* Parse the directives */
1929 if( isalpha( (unsigned char)*psz_text
) || *psz_text
== '[' )
1931 while( *psz_text
&& *psz_text
!= ' ' )
1934 /* Directives are NOT parsed yet */
1935 /* This has probably a better place in a decoder ? */
1936 /* directive = malloc( strlen( psz_text ) + 1 );
1937 if( sscanf( psz_text, "%s %[^\n\r]", directive, psz_text2 ) == 2 )*/
1940 /* Skip the blanks after directives */
1941 while( *psz_text
== ' ' || *psz_text
== '\t' ) psz_text
++;
1943 /* Clean all the lines from inline comments and other stuffs */
1944 psz_orig2
= calloc( strlen( psz_text
) + 1, 1 );
1945 psz_text2
= psz_orig2
;
1947 for( ; *psz_text
!= '\0' && *psz_text
!= '\n' && *psz_text
!= '\r'; )
1952 p_props
->jss
.i_comment
++;
1955 if( p_props
->jss
.i_comment
)
1957 p_props
->jss
.i_comment
= 0;
1958 if( (*(psz_text
+ 1 ) ) == ' ' ) psz_text
++;
1962 if( !p_props
->jss
.i_comment
)
1970 if( (*(psz_text
+ 1 ) ) == ' ' || (*(psz_text
+ 1 ) ) == '\t' )
1972 if( !p_props
->jss
.i_comment
)
1979 if( (*(psz_text
+ 1 ) ) == 'n' )
1986 if( ( toupper((unsigned char)*(psz_text
+ 1 ) ) == 'C' ) ||
1987 ( toupper((unsigned char)*(psz_text
+ 1 ) ) == 'F' ) )
1992 if( (*(psz_text
+ 1 ) ) == 'B' || (*(psz_text
+ 1 ) ) == 'b' ||
1993 (*(psz_text
+ 1 ) ) == 'I' || (*(psz_text
+ 1 ) ) == 'i' ||
1994 (*(psz_text
+ 1 ) ) == 'U' || (*(psz_text
+ 1 ) ) == 'u' ||
1995 (*(psz_text
+ 1 ) ) == 'D' || (*(psz_text
+ 1 ) ) == 'N' )
2000 if( (*(psz_text
+ 1 ) ) == '~' || (*(psz_text
+ 1 ) ) == '{' ||
2001 (*(psz_text
+ 1 ) ) == '\\' )
2003 else if( ( *(psz_text
+ 1 ) == '\r' || *(psz_text
+ 1 ) == '\n' ) &&
2004 *(psz_text
+ 1 ) != '\0' )
2010 if( !p_props
->jss
.i_comment
)
2012 *psz_text2
= *psz_text
;
2019 p_subtitle
->psz_text
= psz_orig2
;
2020 msg_Dbg( p_obj
, "%s", p_subtitle
->psz_text
);
2025 static int ParsePSB( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
2026 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
2029 VLC_UNUSED(p_props
);
2030 VLC_UNUSED( i_idx
);
2039 const char *s
= TextGetLine( txt
);
2042 return VLC_EGENERIC
;
2044 psz_text
= malloc( strlen( s
) + 1 );
2048 if( sscanf( s
, "{%d:%d:%d}{%d:%d:%d}%[^\r\n]",
2049 &h1
, &m1
, &s1
, &h2
, &m2
, &s2
, psz_text
) == 7 )
2051 p_subtitle
->i_start
= vlc_tick_from_sec( h1
* 3600 + m1
* 60 + s1
);
2052 p_subtitle
->i_stop
= vlc_tick_from_sec( h2
* 3600 + m2
* 60 + s2
);
2058 /* replace | by \n */
2059 for( i
= 0; psz_text
[i
] != '\0'; i
++ )
2061 if( psz_text
[i
] == '|' )
2064 p_subtitle
->psz_text
= psz_text
;
2068 static int64_t ParseRealTime( char *psz
, int *h
, int *m
, int *s
, int *f
)
2070 if( *psz
== '\0' ) return 0;
2071 if( sscanf( psz
, "%d:%d:%d.%d", h
, m
, s
, f
) == 4 ||
2072 sscanf( psz
, "%d:%d.%d", m
, s
, f
) == 3 ||
2073 sscanf( psz
, "%d.%d", s
, f
) == 2 ||
2074 sscanf( psz
, "%d:%d", m
, s
) == 2 ||
2075 sscanf( psz
, "%d", s
) == 1 )
2077 return vlc_tick_from_sec((( *h
* 60 + *m
) * 60 ) + *s
)
2078 + VLC_TICK_FROM_MS(*f
* 10);
2080 else return VLC_EGENERIC
;
2083 static int ParseRealText( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
2084 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
2087 VLC_UNUSED(p_props
);
2088 VLC_UNUSED( i_idx
);
2089 char *psz_text
= NULL
;
2093 int h1
= 0, m1
= 0, s1
= 0, f1
= 0;
2094 int h2
= 0, m2
= 0, s2
= 0, f2
= 0;
2095 const char *s
= TextGetLine( txt
);
2099 return VLC_EGENERIC
;
2101 psz_text
= malloc( strlen( s
) + 1 );
2105 /* Find the good begining. This removes extra spaces at the beginning
2107 char *psz_temp
= strcasestr( s
, "<time");
2108 if( psz_temp
!= NULL
)
2110 char psz_end
[12], psz_begin
[12];
2111 /* Line has begin and end */
2112 if( ( sscanf( psz_temp
,
2113 "<%*[t|T]ime %*[b|B]egin=\"%11[^\"]\" %*[e|E]nd=\"%11[^\"]%*[^>]%[^\n\r]",
2114 psz_begin
, psz_end
, psz_text
) != 3 ) &&
2115 /* Line has begin and no end */
2117 "<%*[t|T]ime %*[b|B]egin=\"%11[^\"]\"%*[^>]%[^\n\r]",
2118 psz_begin
, psz_text
) != 2) )
2119 /* Line is not recognized */
2125 int64_t i_time
= ParseRealTime( psz_begin
, &h1
, &m1
, &s1
, &f1
);
2126 p_subtitle
->i_start
= i_time
>= 0 ? i_time
: 0;
2128 i_time
= ParseRealTime( psz_end
, &h2
, &m2
, &s2
, &f2
);
2129 p_subtitle
->i_stop
= i_time
>= 0 ? i_time
: -1;
2134 /* Get the following Lines */
2137 const char *s
= TextGetLine( txt
);
2142 return VLC_EGENERIC
;
2145 size_t i_len
= strlen( s
);
2146 if( i_len
== 0 ) break;
2148 if( strcasestr( s
, "<time" ) ||
2149 strcasestr( s
, "<clear/") )
2151 TextPreviousLine( txt
);
2155 size_t i_old
= strlen( psz_text
);
2157 psz_text
= realloc_or_free( psz_text
, i_old
+ i_len
+ 1 + 1 );
2161 strcat( psz_text
, s
);
2162 strcat( psz_text
, "\n" );
2165 /* Remove the starting ">" that remained after the sscanf */
2166 memmove( &psz_text
[0], &psz_text
[1], strlen( psz_text
) );
2168 p_subtitle
->psz_text
= psz_text
;
2173 static int ParseDKS( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
2174 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
2177 VLC_UNUSED(p_props
);
2178 VLC_UNUSED( i_idx
);
2186 char *s
= TextGetLine( txt
);
2189 return VLC_EGENERIC
;
2191 psz_text
= malloc( strlen( s
) + 1 );
2195 if( sscanf( s
, "[%d:%d:%d]%[^\r\n]",
2196 &h1
, &m1
, &s1
, psz_text
) == 4 )
2198 p_subtitle
->i_start
= vlc_tick_from_sec( h1
* 3600 + m1
* 60 + s1
);
2200 s
= TextGetLine( txt
);
2204 return VLC_EGENERIC
;
2207 if( sscanf( s
, "[%d:%d:%d]", &h2
, &m2
, &s2
) == 3 )
2208 p_subtitle
->i_stop
= vlc_tick_from_sec(h2
* 3600 + m2
* 60 + s2
);
2210 p_subtitle
->i_stop
= -1;
2216 /* replace [br] by \n */
2218 while( ( p
= strstr( psz_text
, "[br]" ) ) )
2221 memmove( p
, &p
[3], strlen(&p
[3])+1 );
2224 p_subtitle
->psz_text
= psz_text
;
2228 static int ParseSubViewer1( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
2229 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
2232 VLC_UNUSED(p_props
);
2233 VLC_UNUSED( i_idx
);
2240 char *s
= TextGetLine( txt
);
2243 return VLC_EGENERIC
;
2245 if( sscanf( s
, "[%d:%d:%d]", &h1
, &m1
, &s1
) == 3 )
2247 p_subtitle
->i_start
= vlc_tick_from_sec( h1
* 3600 + m1
* 60 + s1
);
2249 s
= TextGetLine( txt
);
2251 return VLC_EGENERIC
;
2253 psz_text
= strdup( s
);
2257 s
= TextGetLine( txt
);
2261 return VLC_EGENERIC
;
2264 if( sscanf( s
, "[%d:%d:%d]", &h2
, &m2
, &s2
) == 3 )
2265 p_subtitle
->i_stop
= vlc_tick_from_sec( h2
* 3600 + m2
* 60 + s2
);
2267 p_subtitle
->i_stop
= -1;
2273 p_subtitle
->psz_text
= psz_text
;
2278 static int ParseCommonSBV( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
2279 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
2282 VLC_UNUSED( i_idx
);
2283 VLC_UNUSED( p_props
);
2288 const char *s
= TextGetLine( txt
);
2289 int h1
= 0, m1
= 0, s1
= 0, d1
= 0;
2290 int h2
= 0, m2
= 0, s2
= 0, d2
= 0;
2293 return VLC_EGENERIC
;
2295 if( sscanf( s
,"%d:%d:%d.%d,%d:%d:%d.%d",
2297 &h2
, &m2
, &s2
, &d2
) == 8 )
2299 p_subtitle
->i_start
= vlc_tick_from_sec( h1
* 3600 + m1
* 60 + s1
) +
2300 VLC_TICK_FROM_MS( d1
);
2302 p_subtitle
->i_stop
= vlc_tick_from_sec( h2
* 3600 + m2
* 60 + s2
) +
2303 VLC_TICK_FROM_MS( d2
);
2304 if( p_subtitle
->i_start
< p_subtitle
->i_stop
)
2309 /* Now read text until an empty line */
2310 psz_text
= strdup("");
2316 const char *s
= TextGetLine( txt
);
2320 i_len
= s
? strlen( s
) : 0;
2323 p_subtitle
->psz_text
= psz_text
;
2327 i_old
= strlen( psz_text
);
2328 psz_text
= realloc_or_free( psz_text
, i_old
+ i_len
+ 1 + 1 );
2332 strcat( psz_text
, s
);
2333 strcat( psz_text
, "\n" );
2337 static int ParseSCC( vlc_object_t
*p_obj
, subs_properties_t
*p_props
,
2338 text_t
*txt
, subtitle_t
*p_subtitle
, size_t i_idx
)
2341 VLC_UNUSED( i_idx
);
2342 VLC_UNUSED( p_props
);
2344 static const struct rates
2347 vlc_rational_t rate
;
2348 bool b_drop_allowed
;
2350 { 2398, { 24000, 1001 }, false },
2351 { 2400, { 24, 1 }, false },
2352 { 2500, { 25, 1 }, false },
2353 { 2997, { 30000, 1001 }, true }, /* encoding rate */
2354 { 3000, { 30, 1 }, false },
2355 { 5000, { 50, 1 }, false },
2356 { 5994, { 60000, 1001 }, true },
2357 { 6000, { 60, 1 }, false },
2359 const struct rates
*p_rate
= &framerates
[3];
2360 float f_fps
= var_GetFloat( p_obj
, "sub-original-fps" );
2363 for( size_t i
=0; i
<ARRAY_SIZE(framerates
); i
++ )
2365 if( (unsigned)(f_fps
* 100) == framerates
[i
].val
)
2367 p_rate
= &framerates
[i
];
2375 const char *psz_line
= TextGetLine( txt
);
2377 return VLC_EGENERIC
;
2379 unsigned h
, m
, s
, f
;
2381 if( sscanf( psz_line
, "%u:%u:%u%c%u ", &h
, &m
, &s
, &c
, &f
) != 5 ||
2382 ( c
!= ':' && c
!= ';' ) )
2385 /* convert everything to seconds */
2386 uint64_t i_frames
= h
* 3600 + m
* 60 + s
;
2388 if( c
== ';' && p_rate
->b_drop_allowed
) /* dropframe */
2390 /* convert to frame # to be accurate between inter drop drift
2391 * of 18 frames see http://andrewduncan.net/timecodes/ */
2392 const unsigned i_mins
= h
* 60 + m
;
2393 i_frames
= i_frames
* p_rate
[+1].rate
.num
+ f
2394 - (p_rate
[+1].rate
.den
* 2 * (i_mins
- i_mins
% 10));
2398 /* convert to frame # at 29.97 */
2399 i_frames
= i_frames
* framerates
[3].rate
.num
/ framerates
[3].rate
.den
+ f
;
2401 p_subtitle
->i_start
= VLC_TICK_0
+ vlc_tick_from_sec(i_frames
)*
2402 p_rate
->rate
.den
/ p_rate
->rate
.num
;
2403 p_subtitle
->i_stop
= -1;
2405 const char *psz_text
= strchr( psz_line
, '\t' );
2406 if( !psz_text
&& !(psz_text
= strchr( psz_line
, ' ' )) )
2409 if ( psz_text
[1] == '\0' )
2412 p_subtitle
->psz_text
= strdup( psz_text
+ 1 );
2413 if( !p_subtitle
->psz_text
)
2422 /* Matches filename.xx.srt */
2423 static char * get_language_from_filename( const char * psz_sub_file
)
2425 char *psz_ret
= NULL
;
2426 char *psz_tmp
, *psz_language_begin
;
2428 if( !psz_sub_file
) return NULL
;
2429 char *psz_work
= strdup( psz_sub_file
);
2431 /* Removing extension, but leaving the dot */
2432 psz_tmp
= strrchr( psz_work
, '.' );
2436 psz_language_begin
= strrchr( psz_work
, '.' );
2437 if( psz_language_begin
)
2438 psz_ret
= strdup(++psz_language_begin
);